Commit 40c53224 authored by Yuwei Huang's avatar Yuwei Huang Committed by Commit Bot

[CRD iOS] Implement AudioPlaybackSinkIos

This is part of the work of refactoring the iOS audio playback logic.

This CL implements the AudioPlaybackSink for iOS using the AudioQueue
API. The implementation is following Apple's guide for the playback
process:
https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/AboutAudioQueues/AboutAudioQueues.html#//apple_ref/doc/uid/TP40005343-CH5-SW22

In the next CL there will be an AudioPlaybackStream class that
connects the AudioJitterBuffer with AudioPlaybackSinkIos.

Bug: 868088
Change-Id: Ifbaa08bfcf5f983587bfb71418c681001ab7cdca
Reviewed-on: https://chromium-review.googlesource.com/1166246
Commit-Queue: Yuwei Huang <yuweih@chromium.org>
Reviewed-by: default avatarJoe Downing <joedow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#581800}
parent 6fdbc7b8
...@@ -10,6 +10,7 @@ source_set("audio") { ...@@ -10,6 +10,7 @@ source_set("audio") {
"audio_frame_supplier.h", "audio_frame_supplier.h",
"audio_jitter_buffer.cc", "audio_jitter_buffer.cc",
"audio_jitter_buffer.h", "audio_jitter_buffer.h",
"audio_playback_sink.h",
"audio_player.cc", "audio_player.cc",
"audio_player.h", "audio_player.h",
"audio_player_android.cc", "audio_player_android.cc",
...@@ -18,6 +19,8 @@ source_set("audio") { ...@@ -18,6 +19,8 @@ source_set("audio") {
"audio_player_buffer.h", "audio_player_buffer.h",
"audio_stream_consumer.cc", "audio_stream_consumer.cc",
"audio_stream_consumer.h", "audio_stream_consumer.h",
"audio_stream_format.cc",
"audio_stream_format.h",
] ]
configs += [ "//remoting/build/config:version" ] configs += [ "//remoting/build/config:version" ]
...@@ -37,6 +40,14 @@ source_set("audio") { ...@@ -37,6 +40,14 @@ source_set("audio") {
} }
} }
source_set("test_support") {
testonly = true
sources = [
"fake_async_audio_data_supplier.cc",
"fake_async_audio_data_supplier.h",
]
}
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
......
...@@ -12,7 +12,8 @@ namespace remoting { ...@@ -12,7 +12,8 @@ namespace remoting {
// This interface allows caller to asynchronously request for audio data. // This interface allows caller to asynchronously request for audio data.
class AsyncAudioDataSupplier { class AsyncAudioDataSupplier {
public: public:
struct GetDataRequest { class GetDataRequest {
public:
// |data| must outlive |this|. // |data| must outlive |this|.
GetDataRequest(void* data, size_t bytes_needed); GetDataRequest(void* data, size_t bytes_needed);
virtual ~GetDataRequest(); virtual ~GetDataRequest();
......
...@@ -36,17 +36,6 @@ constexpr base::TimeDelta kOverrunRecoveryLatency = ...@@ -36,17 +36,6 @@ constexpr base::TimeDelta kOverrunRecoveryLatency =
namespace remoting { namespace remoting {
bool AudioJitterBuffer::StreamFormat::operator==(
const StreamFormat& other) const {
return bytes_per_sample == other.bytes_per_sample &&
channels == other.channels && sample_rate == other.sample_rate;
}
bool AudioJitterBuffer::StreamFormat::operator!=(
const StreamFormat& other) const {
return !(*this == other);
}
AudioJitterBuffer::AudioJitterBuffer( AudioJitterBuffer::AudioJitterBuffer(
OnFormatChangedCallback on_format_changed) { OnFormatChangedCallback on_format_changed) {
DETACH_FROM_THREAD(thread_checker_); DETACH_FROM_THREAD(thread_checker_);
...@@ -61,7 +50,7 @@ void AudioJitterBuffer::AddAudioPacket(std::unique_ptr<AudioPacket> packet) { ...@@ -61,7 +50,7 @@ void AudioJitterBuffer::AddAudioPacket(std::unique_ptr<AudioPacket> packet) {
DCHECK_EQ(AudioPacket::ENCODING_RAW, packet->encoding()); DCHECK_EQ(AudioPacket::ENCODING_RAW, packet->encoding());
DCHECK_NE(AudioPacket::SAMPLING_RATE_INVALID, packet->sampling_rate()); DCHECK_NE(AudioPacket::SAMPLING_RATE_INVALID, packet->sampling_rate());
StreamFormat stream_format; AudioStreamFormat stream_format;
stream_format.bytes_per_sample = packet->bytes_per_sample(); stream_format.bytes_per_sample = packet->bytes_per_sample();
stream_format.channels = packet->channels(); stream_format.channels = packet->channels();
stream_format.sample_rate = packet->sampling_rate(); stream_format.sample_rate = packet->sampling_rate();
...@@ -103,13 +92,13 @@ void AudioJitterBuffer::ClearGetDataRequests() { ...@@ -103,13 +92,13 @@ void AudioJitterBuffer::ClearGetDataRequests() {
queued_requests_.clear(); queued_requests_.clear();
} }
void AudioJitterBuffer::ResetBuffer(const StreamFormat& new_format) { void AudioJitterBuffer::ResetBuffer(const AudioStreamFormat& new_format) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
queued_packets_.clear(); queued_packets_.clear();
queued_bytes_ = 0; queued_bytes_ = 0;
first_packet_offset_ = 0; first_packet_offset_ = 0;
ClearGetDataRequests(); ClearGetDataRequests();
stream_format_ = std::make_unique<StreamFormat>(new_format); stream_format_ = std::make_unique<AudioStreamFormat>(new_format);
underrun_protection_mode_ = true; underrun_protection_mode_ = true;
if (on_format_changed_) { if (on_format_changed_) {
on_format_changed_.Run(*stream_format_); on_format_changed_.Run(*stream_format_);
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "remoting/client/audio/async_audio_data_supplier.h" #include "remoting/client/audio/async_audio_data_supplier.h"
#include "remoting/client/audio/audio_stream_format.h"
#include "remoting/proto/audio.pb.h" #include "remoting/proto/audio.pb.h"
namespace remoting { namespace remoting {
...@@ -22,17 +23,8 @@ namespace remoting { ...@@ -22,17 +23,8 @@ namespace remoting {
// and feeds the requests with the data when the buffer has enough data. // and feeds the requests with the data when the buffer has enough data.
class AudioJitterBuffer : public AsyncAudioDataSupplier { class AudioJitterBuffer : public AsyncAudioDataSupplier {
public: public:
struct StreamFormat {
bool operator==(const StreamFormat& other) const;
bool operator!=(const StreamFormat& other) const;
int bytes_per_sample = 0;
int channels = 0;
int sample_rate = 0;
};
using OnFormatChangedCallback = using OnFormatChangedCallback =
base::RepeatingCallback<void(const StreamFormat& format)>; base::RepeatingCallback<void(const AudioStreamFormat& format)>;
// |callback| is called once the jitter buffer gets the first packet or the // |callback| is called once the jitter buffer gets the first packet or the
// stream format has been changed. // stream format has been changed.
...@@ -52,7 +44,7 @@ class AudioJitterBuffer : public AsyncAudioDataSupplier { ...@@ -52,7 +44,7 @@ class AudioJitterBuffer : public AsyncAudioDataSupplier {
// Clears the jitter buffer, drops all pending requests, and notify // Clears the jitter buffer, drops all pending requests, and notify
// |on_format_changed_| that the format has been changed. // |on_format_changed_| that the format has been changed.
void ResetBuffer(const StreamFormat& new_format); void ResetBuffer(const AudioStreamFormat& new_format);
// Feeds data from the jitter buffer into the pending requests. OnDataFilled() // Feeds data from the jitter buffer into the pending requests. OnDataFilled()
// will be called and request will be removed from the queue when a request // will be called and request will be removed from the queue when a request
...@@ -69,7 +61,7 @@ class AudioJitterBuffer : public AsyncAudioDataSupplier { ...@@ -69,7 +61,7 @@ class AudioJitterBuffer : public AsyncAudioDataSupplier {
// The stream format of the last audio packet. This is nullptr if the buffer // The stream format of the last audio packet. This is nullptr if the buffer
// has never received any packet. // has never received any packet.
std::unique_ptr<StreamFormat> stream_format_; std::unique_ptr<AudioStreamFormat> stream_format_;
// AudioPackets queued up by the jitter buffer before they are consumed by // AudioPackets queued up by the jitter buffer before they are consumed by
// GetDataRequests. // GetDataRequests.
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/callback.h" #include "base/callback.h"
#include "remoting/client/audio/audio_jitter_buffer.h" #include "remoting/client/audio/audio_jitter_buffer.h"
#include "remoting/client/audio/audio_stream_format.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace remoting { namespace remoting {
...@@ -77,15 +78,15 @@ class AudioJitterBufferTest : public ::testing::Test { ...@@ -77,15 +78,15 @@ class AudioJitterBufferTest : public ::testing::Test {
std::list<std::unique_ptr<uint8_t[]>> consumer_buffers_; std::list<std::unique_ptr<uint8_t[]>> consumer_buffers_;
private: private:
struct SimpleGetDataRequest; class SimpleGetDataRequest;
void OnFormatChanged(const AudioJitterBuffer::StreamFormat& format); void OnFormatChanged(const AudioStreamFormat& format);
AudioPacket::SamplingRate sample_rate_; AudioPacket::SamplingRate sample_rate_;
std::unique_ptr<AudioJitterBuffer::StreamFormat> stream_format_; std::unique_ptr<AudioStreamFormat> stream_format_;
}; };
struct AudioJitterBufferTest::SimpleGetDataRequest class AudioJitterBufferTest::SimpleGetDataRequest
: public AsyncAudioDataSupplier::GetDataRequest { : public AsyncAudioDataSupplier::GetDataRequest {
public: public:
SimpleGetDataRequest(AudioJitterBufferTest* test, size_t bytes_to_write); SimpleGetDataRequest(AudioJitterBufferTest* test, size_t bytes_to_write);
...@@ -164,9 +165,8 @@ int AudioJitterBufferTest::GetNumQueuedTime() const { ...@@ -164,9 +165,8 @@ int AudioJitterBufferTest::GetNumQueuedTime() const {
size_t AudioJitterBufferTest::GetNumQueuedRequests() const { size_t AudioJitterBufferTest::GetNumQueuedRequests() const {
return audio_->queued_requests_.size(); return audio_->queued_requests_.size();
} }
void AudioJitterBufferTest::OnFormatChanged( void AudioJitterBufferTest::OnFormatChanged(const AudioStreamFormat& format) {
const AudioJitterBuffer::StreamFormat& format) { stream_format_ = std::make_unique<AudioStreamFormat>(format);
stream_format_ = std::make_unique<AudioJitterBuffer::StreamFormat>(format);
} }
// SimpleGetDataRequest definitions // SimpleGetDataRequest definitions
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_SINK_H_
#define REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_SINK_H_
#include "base/macros.h"
namespace remoting {
class AsyncAudioDataSupplier;
struct AudioStreamFormat;
// This is an interface acting as the downstream of AsyncAudioDataSupplier.
class AudioPlaybackSink {
public:
AudioPlaybackSink() = default;
virtual ~AudioPlaybackSink() = default;
// Sets the data supplier to be used by the sink to request for more audio
// data.
// |supplier| must outlive |this|.
virtual void SetDataSupplier(AsyncAudioDataSupplier* supplier) = 0;
// Called whenever the stream format is first received or has been changed.
virtual void ResetStreamFormat(const AudioStreamFormat& format) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(AudioPlaybackSink);
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_AUDIO_PLAYBACK_SINK_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/client/audio/audio_stream_format.h"
namespace remoting {
bool AudioStreamFormat::operator==(const AudioStreamFormat& other) const {
return bytes_per_sample == other.bytes_per_sample &&
channels == other.channels && sample_rate == other.sample_rate;
}
bool AudioStreamFormat::operator!=(const AudioStreamFormat& other) const {
return !(*this == other);
}
} // namespace remoting
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_AUDIO_STREAM_FORMAT_H_
#define REMOTING_CLIENT_AUDIO_AUDIO_STREAM_FORMAT_H_
namespace remoting {
struct AudioStreamFormat {
bool operator==(const AudioStreamFormat& other) const;
bool operator!=(const AudioStreamFormat& other) const;
int bytes_per_sample = 0;
int channels = 0;
int sample_rate = 0;
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_AUDIO_STREAM_FORMAT_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/client/audio/fake_async_audio_data_supplier.h"
#include <utility>
#include "base/logging.h"
namespace remoting {
FakeAsyncAudioDataSupplier::FakeAsyncAudioDataSupplier() = default;
FakeAsyncAudioDataSupplier::~FakeAsyncAudioDataSupplier() = default;
void FakeAsyncAudioDataSupplier::AsyncGetData(
std::unique_ptr<GetDataRequest> request) {
pending_requests_.push_back(std::move(request));
if (fulfill_requests_immediately_) {
FulfillAllRequests();
}
}
void FakeAsyncAudioDataSupplier::ClearGetDataRequests() {
pending_requests_.clear();
}
void FakeAsyncAudioDataSupplier::FulfillNextRequest() {
DCHECK_GT(pending_requests_count(), 0u);
auto& request = pending_requests_.front();
memset(request->data, kDummyAudioData, request->bytes_needed);
request->OnDataFilled();
pending_requests_.pop_front();
fulfilled_requests_count_++;
}
void FakeAsyncAudioDataSupplier::FulfillAllRequests() {
while (pending_requests_count() > 0) {
FulfillNextRequest();
}
}
void FakeAsyncAudioDataSupplier::ResetFulfilledRequestsCounter() {
fulfilled_requests_count_ = 0u;
}
} // namespace remoting
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_CLIENT_AUDIO_FAKE_ASYNC_AUDIO_DATA_SUPPLIER_H_
#define REMOTING_CLIENT_AUDIO_FAKE_ASYNC_AUDIO_DATA_SUPPLIER_H_
#include <cstdint>
#include <list>
#include <memory>
#include "base/macros.h"
#include "remoting/client/audio/async_audio_data_supplier.h"
namespace remoting {
// A fake AsyncAudioDataSupplier implementation for testing.
class FakeAsyncAudioDataSupplier : public AsyncAudioDataSupplier {
public:
// Dummy audio data that will be filled into pending requests.
const uint8_t kDummyAudioData = 0x8b;
FakeAsyncAudioDataSupplier();
~FakeAsyncAudioDataSupplier() override;
// AsyncAudioDataSupplier implementations.
void AsyncGetData(std::unique_ptr<GetDataRequest> request) override;
void ClearGetDataRequests() override;
// Fulfills the next pending request by filling it with |kDummyAudioData|.
void FulfillNextRequest();
// Fulfills all pending requests.
void FulfillAllRequests();
// Resets fulfilled_requests_count() to 0.
void ResetFulfilledRequestsCounter();
// Returns number of requests that are not fulfilled.
size_t pending_requests_count() const { return pending_requests_.size(); }
// Returns number of requests that have been fulfilled. Can be reset to 0 by
// calling ResetFulfilledRequestsCounter().
size_t fulfilled_requests_count() const { return fulfilled_requests_count_; }
// If this is true, the instance will immediately fulfill get-data requests
// when AsyncGetData is called, otherwise the caller needs to call
// FulfillNextRequest() or FulfillAllRequests() to fulfill the requests.
// The default value is false.
void set_fulfill_requests_immediately(bool fulfill_requests_immediately) {
fulfill_requests_immediately_ = fulfill_requests_immediately;
}
private:
bool fulfill_requests_immediately_ = false;
std::list<std::unique_ptr<GetDataRequest>> pending_requests_;
size_t fulfilled_requests_count_ = 0u;
DISALLOW_COPY_AND_ASSIGN(FakeAsyncAudioDataSupplier);
};
} // namespace remoting
#endif // REMOTING_CLIENT_AUDIO_FAKE_ASYNC_AUDIO_DATA_SUPPLIER_H_
...@@ -78,7 +78,8 @@ test("ios_remoting_unittests") { ...@@ -78,7 +78,8 @@ test("ios_remoting_unittests") {
deps = [ deps = [
"//base/test:run_all_unittests", "//base/test:run_all_unittests",
"//base/test:test_support", "//base/test:test_support",
"//remoting//ios/facade:unit_tests", "//remoting/ios/audio:unit_tests",
"//remoting/ios/facade:unit_tests",
"//remoting/ios/persistence:unit_tests", "//remoting/ios/persistence:unit_tests",
] ]
......
...@@ -8,6 +8,8 @@ import("//remoting/build/config/remoting_build.gni") ...@@ -8,6 +8,8 @@ import("//remoting/build/config/remoting_build.gni")
source_set("audio") { source_set("audio") {
sources = [ sources = [
"audio_playback_sink_ios.cc",
"audio_playback_sink_ios.h",
"audio_player_ios.h", "audio_player_ios.h",
"audio_player_ios.mm", "audio_player_ios.mm",
"audio_player_ios_wrapper.h", "audio_player_ios_wrapper.h",
...@@ -21,5 +23,26 @@ source_set("audio") { ...@@ -21,5 +23,26 @@ source_set("audio") {
"//remoting/client/audio", "//remoting/client/audio",
] ]
public_deps = [
"//remoting/proto",
]
libs = [ "AudioToolbox.framework" ]
configs += [ "//build/config/compiler:enable_arc" ]
}
source_set("unit_tests") {
testonly = true
sources = [
"audio_playback_sink_ios_unittest.cc",
]
deps = [
":audio",
"//remoting/client/audio:test_support",
"//testing/gmock",
"//testing/gtest",
]
configs += [ "//build/config/compiler:enable_arc" ] configs += [ "//build/config/compiler:enable_arc" ]
} }
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/ios/audio/audio_playback_sink_ios.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "remoting/client/audio/async_audio_data_supplier.h"
#include "remoting/client/audio/audio_stream_format.h"
namespace remoting {
namespace {
// Once we receive the stream format, we create
// |kRequiredBufferCountForPlayback| buffers from the audio queue, each with a
// duration of |kBufferLength|. The buffers will then be transferred to the
// supplier for priming. Once a buffer is filled up, we put it back to the audio
// queue and start running the queue. Buffer that has been consumed by the audio
// queue will be transferred back to the supplier for priming new audio data. We
// stop running the audio queue once all buffers have been transferred to the
// supplier.
constexpr base::TimeDelta kBufferLength = base::TimeDelta::FromMilliseconds(10);
constexpr int kRequiredBufferCountForPlayback = 5;
class AudioQueueGetDataRequest : public AsyncAudioDataSupplier::GetDataRequest {
public:
AudioQueueGetDataRequest(
AudioQueueBufferRef buffer,
base::OnceCallback<void(AudioQueueBufferRef)> on_data_received);
~AudioQueueGetDataRequest() override;
void OnDataFilled() override;
private:
AudioQueueBufferRef buffer_;
base::OnceCallback<void(AudioQueueBufferRef)> on_data_received_;
};
AudioQueueGetDataRequest::AudioQueueGetDataRequest(
AudioQueueBufferRef buffer,
base::OnceCallback<void(AudioQueueBufferRef)> on_data_received)
: GetDataRequest(buffer->mAudioData, buffer->mAudioDataBytesCapacity),
buffer_(buffer),
on_data_received_(std::move(on_data_received)) {
buffer_->mAudioDataByteSize = buffer_->mAudioDataBytesCapacity;
}
// Note that disposing of the output queue also disposes all of its buffers,
// so no cleanup is needed here.
AudioQueueGetDataRequest::~AudioQueueGetDataRequest() = default;
void AudioQueueGetDataRequest::OnDataFilled() {
std::move(on_data_received_).Run(buffer_);
}
} // namespace
AudioPlaybackSinkIos::AudioPlaybackSinkIos() : weak_factory_(this) {
DETACH_FROM_THREAD(thread_checker_);
}
AudioPlaybackSinkIos::~AudioPlaybackSinkIos() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DisposeOutputQueue();
}
void AudioPlaybackSinkIos::SetDataSupplier(AsyncAudioDataSupplier* supplier) {
DCHECK(supplier);
supplier_ = supplier;
}
void AudioPlaybackSinkIos::ResetStreamFormat(const AudioStreamFormat& format) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
stream_format_.mSampleRate = format.sample_rate;
stream_format_.mFormatID = kAudioFormatLinearPCM;
stream_format_.mFormatFlags =
kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
stream_format_.mBitsPerChannel = 8 * format.bytes_per_sample;
stream_format_.mChannelsPerFrame = format.channels;
stream_format_.mBytesPerPacket = format.bytes_per_sample * format.channels;
stream_format_.mBytesPerFrame = stream_format_.mBytesPerPacket;
stream_format_.mFramesPerPacket = 1;
stream_format_.mReserved = 0;
ResetOutputQueue();
}
void AudioPlaybackSinkIos::AsyncGetAudioData(AudioQueueBufferRef buffer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(supplier_);
priming_buffers_count_++;
supplier_->AsyncGetData(std::make_unique<AudioQueueGetDataRequest>(
buffer, base::BindOnce(&AudioPlaybackSinkIos::OnAudioDataReceived,
weak_factory_.GetWeakPtr())));
if (state_ == State::RUNNING &&
priming_buffers_count_ == kRequiredBufferCountForPlayback) {
// Buffer underrun. Stop playback immediately.
StopPlayback();
return;
}
}
void AudioPlaybackSinkIos::OnAudioDataReceived(AudioQueueBufferRef buffer) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
AudioQueueEnqueueBuffer(output_queue_, buffer, 0, nullptr);
priming_buffers_count_--;
if (state_ == State::STOPPED) {
StartPlayback();
}
}
// static
void AudioPlaybackSinkIos::OnBufferDequeued(void* context,
AudioQueueRef outAQ,
AudioQueueBufferRef buffer) {
AudioPlaybackSinkIos* instance =
reinterpret_cast<AudioPlaybackSinkIos*>(context);
DCHECK(instance);
instance->AsyncGetAudioData(buffer);
}
void AudioPlaybackSinkIos::StartPlayback() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (state_ != State::STOPPED) {
return;
}
DCHECK(output_queue_);
OSStatus err = AudioQueueStart(output_queue_, nullptr);
if (err) {
// This could be a transient failure when we try to start playback while the
// app is resuming from the background. We can reset the queue for now and
// wait for new audio data to trigger StartPlayback() again.
LOG(ERROR) << "AudioQueueStart failed: " << err;
// StartPlayback() may be called from inside GetDataRequest::OnDataFilled().
// In this case ResetOutputQueue() must be called in a separate task because
// it alters the pending requests in |supplier_|.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&AudioPlaybackSinkIos::ResetOutputQueue,
weak_factory_.GetWeakPtr()));
state_ = State::SCHEDULED_TO_RESET;
} else {
state_ = State::RUNNING;
}
}
void AudioPlaybackSinkIos::StopPlayback() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(output_queue_);
if (state_ != State::RUNNING) {
return;
}
// Note that AudioQueueStop() will immediately return all enqueued buffers to
// us, which calls AsyncGetAudioData(). We change the state to STOPPED before
// AudioQueueStop() so that the buffers are immediately transferred to the
// supplier.
state_ = State::STOPPED;
OSStatus err = AudioQueueStop(output_queue_, /* Immediate */ true);
HandleError(err, "AudioQueueStop");
}
void AudioPlaybackSinkIos::ResetOutputQueue() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DisposeOutputQueue();
OSStatus err = AudioQueueNewOutput(&stream_format_, OnBufferDequeued, this,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes, 0, &output_queue_);
if (HandleError(err, "AudioQueueNewOutput")) {
return;
}
// Create buffers.
size_t buffer_byte_size = stream_format_.mSampleRate *
stream_format_.mBytesPerPacket *
kBufferLength.InSecondsF();
for (int i = 0; i < kRequiredBufferCountForPlayback; i++) {
AudioQueueBufferRef buffer;
OSStatus err =
AudioQueueAllocateBuffer(output_queue_, buffer_byte_size, &buffer);
if (HandleError(err, "AudioQueueAllocateBuffer")) {
return;
}
// Immediately request for audio data.
AsyncGetAudioData(buffer);
}
}
void AudioPlaybackSinkIos::DisposeOutputQueue() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(supplier_);
if (!output_queue_) {
return;
}
AudioQueueDispose(output_queue_, /* Immediate */ true);
supplier_->ClearGetDataRequests();
priming_buffers_count_ = 0;
output_queue_ = nullptr;
state_ = State::STOPPED;
}
bool AudioPlaybackSinkIos::HandleError(OSStatus err,
const char* function_name) {
if (err) {
LOG(DFATAL) << "Failed to call " << function_name
<< ", error code: " << err;
DisposeOutputQueue();
return true;
}
return false;
}
} // namespace remoting
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_IOS_AUDIO_AUDIO_PLAYBACK_SINK_IOS_H_
#define REMOTING_IOS_AUDIO_AUDIO_PLAYBACK_SINK_IOS_H_
#include <AudioToolbox/AudioToolbox.h>
#include <list>
#include <memory>
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "remoting/client/audio/audio_playback_sink.h"
namespace remoting {
// This is the iOS AudioPlaybackSink implementation that uses AudioQueue for
// playback.
class AudioPlaybackSinkIos : public AudioPlaybackSink {
public:
AudioPlaybackSinkIos();
~AudioPlaybackSinkIos() override;
// AudioPlaybackSink implementations.
void SetDataSupplier(AsyncAudioDataSupplier* supplier) override;
void ResetStreamFormat(const AudioStreamFormat& format) override;
private:
// STOPPED <-----------------------------+------------+
// | Received packet | |
// (Start playback)--------+ | |
// | Failed | Succeeded | Buffer |
// v v | Underrun |
// SCHEDULED_TO_RESET RUNNING---------+ |
// | | |
// | | Sink destructing or | Audio queue
// | | format resetting | destroyed
// +--------------------+--------------------------+
enum class State {
STOPPED,
SCHEDULED_TO_RESET,
RUNNING,
};
// Asks |supplier_| to fill audio data into the given buffer.
void AsyncGetAudioData(AudioQueueBufferRef buffer);
// Callback called when |supplier_| has finished filling data for |buffer|.
void OnAudioDataReceived(AudioQueueBufferRef buffer);
// Callback called when the AudioQueue API finished consuming the audio data.
static void OnBufferDequeued(void* context,
AudioQueueRef outAQ,
AudioQueueBufferRef buffer);
// Starts playback immediately. Posts task to reset the output queue if it
// fails to start.
void StartPlayback();
// Stops playback immediately.
void StopPlayback();
// Disposes the current output queue and its buffers, creates a new queue
// and buffers, and immediately request for audio data from |supplier_|.
void ResetOutputQueue();
// Disposes the current output queue and its buffers.
void DisposeOutputQueue();
// If |err| is not no-error, prints an error log at DFATAL level and disposes
// the current output queue. The sink will then not be running until
// ResetStreamFormat() is called again.
// Returns true if error occurs and the output queue has been disposed.
bool HandleError(OSStatus err, const char* function_name);
THREAD_CHECKER(thread_checker_);
AsyncAudioDataSupplier* supplier_ = nullptr;
// Number of buffers that are currently transferred to |supplier_| for
// priming.
size_t priming_buffers_count_ = 0;
// The current stream format.
AudioStreamBasicDescription stream_format_;
// The output queue. nullptr if ResetStreamFormat() has not been called.
AudioQueueRef output_queue_ = nullptr;
// The current state.
State state_ = State::STOPPED;
base::WeakPtrFactory<AudioPlaybackSinkIos> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(AudioPlaybackSinkIos);
};
} // namespace remoting
#endif // REMOTING_IOS_AUDIO_AUDIO_PLAYBACK_SINK_IOS_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/ios/audio/audio_playback_sink_ios.h"
#include "base/bind.h"
#include "base/test/bind_test_util.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "remoting/client/audio/audio_stream_format.h"
#include "remoting/client/audio/fake_async_audio_data_supplier.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace {
constexpr AudioStreamFormat kStreamFormat = {2, 2, 44100};
constexpr base::TimeDelta kBufferPlaybackTimeout =
base::TimeDelta::FromMilliseconds(500);
} // namespace
class AudioPlaybackSinkIosTest : public ::testing::Test {
protected:
void SetUp() override;
void TearDown() override;
void FeedStreamFormat();
void BlockAndRunOnAudioThread(base::OnceClosure closure);
void Sleep();
base::Thread audio_thread_{"Chromoting Audio"};
std::unique_ptr<FakeAsyncAudioDataSupplier> supplier_;
std::unique_ptr<AudioPlaybackSinkIos> sink_;
};
// Test fixture definitions
void AudioPlaybackSinkIosTest::SetUp() {
audio_thread_.StartAndWaitForTesting();
supplier_ = std::make_unique<FakeAsyncAudioDataSupplier>();
sink_ = std::make_unique<AudioPlaybackSinkIos>();
sink_->SetDataSupplier(supplier_.get());
}
void AudioPlaybackSinkIosTest::TearDown() {
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
sink_.reset();
supplier_.reset();
}));
audio_thread_.Stop();
}
void AudioPlaybackSinkIosTest::FeedStreamFormat() {
sink_->ResetStreamFormat(kStreamFormat);
}
void AudioPlaybackSinkIosTest::BlockAndRunOnAudioThread(
base::OnceClosure closure) {
audio_thread_.task_runner()->PostTask(FROM_HERE, std::move(closure));
audio_thread_.FlushForTesting();
}
void AudioPlaybackSinkIosTest::Sleep() {
base::PlatformThread::Sleep(kBufferPlaybackTimeout);
}
// Test cases
TEST_F(AudioPlaybackSinkIosTest, Init) {
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
ASSERT_EQ(0u, supplier_->pending_requests_count());
FeedStreamFormat();
// New requests have been enqueued immediately.
ASSERT_GT(supplier_->pending_requests_count(), 0u);
}));
}
TEST_F(AudioPlaybackSinkIosTest, NoLingeringRequestsAfterDestruction) {
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
FeedStreamFormat();
ASSERT_GT(supplier_->pending_requests_count(), 0u);
// Delete the audio sink.
sink_.reset();
// No lingering pending requests.
ASSERT_EQ(0u, supplier_->pending_requests_count());
}));
}
TEST_F(AudioPlaybackSinkIosTest, DestroyWhenPlaying) {
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
FeedStreamFormat();
ASSERT_GT(supplier_->pending_requests_count(), 0u);
supplier_->FulfillAllRequests();
// Delete the audio sink.
sink_.reset();
// No lingering pending requests.
ASSERT_EQ(0u, supplier_->pending_requests_count());
}));
}
TEST_F(AudioPlaybackSinkIosTest, BufferUnderrunScenario) {
size_t max_number_of_requests;
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
ASSERT_EQ(0u, supplier_->pending_requests_count());
FeedStreamFormat();
max_number_of_requests = supplier_->pending_requests_count();
ASSERT_GT(max_number_of_requests, 0u);
supplier_->FulfillAllRequests();
// Old buffers are returned. New request has not come yet.
ASSERT_EQ(0u, supplier_->pending_requests_count());
}));
// Wait for the sink to consume all buffers and return them to the supplier.
Sleep();
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
// Audio buffers should now be returned to the supplier. The AudioQueue
// should be stopped because of buffer underrun.
ASSERT_EQ(max_number_of_requests, supplier_->pending_requests_count());
supplier_->FulfillAllRequests();
ASSERT_EQ(0u, supplier_->pending_requests_count());
}));
// Wait for the sink to consume all buffers.
Sleep();
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
// Audio buffers should now be returned to the supplier. Buffer underrun
// again.
ASSERT_EQ(max_number_of_requests, supplier_->pending_requests_count());
}));
}
TEST_F(AudioPlaybackSinkIosTest, KeepFulfillingRequestsOneByOne) {
size_t max_number_of_requests;
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
supplier_->set_fulfill_requests_immediately(true);
FeedStreamFormat();
max_number_of_requests = supplier_->pending_requests_count();
}));
size_t number_of_fulfilled_requests = 0;
// Keep the queue running and verify that the number of fulfilled requests
// keeps increasing.
for (int i = 0; i < 5; i++) {
Sleep();
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
// Make sure the number of pending requests does not exceed
// |max_number_of_requests|.
ASSERT_LE(supplier_->pending_requests_count(), max_number_of_requests);
size_t new_number_of_fulfilled_requests =
supplier_->fulfilled_requests_count();
ASSERT_GT(new_number_of_fulfilled_requests, number_of_fulfilled_requests);
number_of_fulfilled_requests = new_number_of_fulfilled_requests;
}));
}
}
TEST_F(AudioPlaybackSinkIosTest, ChangeStreamFormat_NoPendingRequests) {
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
ASSERT_EQ(0u, supplier_->pending_requests_count());
FeedStreamFormat();
size_t max_number_of_requests = supplier_->pending_requests_count();
ASSERT_GT(max_number_of_requests, 0u);
supplier_->FulfillAllRequests();
// Old buffers are returned. New request has not come yet.
ASSERT_EQ(0u, supplier_->pending_requests_count());
// Change the sample rate to 48000 now.
AudioStreamFormat new_stream_format = {2, 2, 48000};
sink_->ResetStreamFormat(new_stream_format);
// New pending requests are enqueued.
ASSERT_EQ(max_number_of_requests, supplier_->pending_requests_count());
}));
}
TEST_F(AudioPlaybackSinkIosTest, ChangeStreamFormat_WithPendingRequests) {
size_t max_number_of_requests;
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
ASSERT_EQ(0u, supplier_->pending_requests_count());
FeedStreamFormat();
max_number_of_requests = supplier_->pending_requests_count();
ASSERT_GT(max_number_of_requests, 0u);
supplier_->FulfillAllRequests();
// Old buffers are returned. New request has not come yet.
ASSERT_EQ(0u, supplier_->pending_requests_count());
}));
// Sleep until new requests are enqueued.
Sleep();
BlockAndRunOnAudioThread(base::BindLambdaForTesting([&]() {
// Verify that new requests are enqueued.
ASSERT_EQ(max_number_of_requests, supplier_->pending_requests_count());
// Change the sample rate to 48000 now.
AudioStreamFormat new_stream_format = {2, 2, 48000};
sink_->ResetStreamFormat(new_stream_format);
// Same number of enqueued requests.
ASSERT_EQ(max_number_of_requests, supplier_->pending_requests_count());
}));
}
} // namespace remoting
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment