Index: chromecast/media/audio/cast_audio_output_stream.cc |
diff --git a/chromecast/media/audio/cast_audio_output_stream.cc b/chromecast/media/audio/cast_audio_output_stream.cc |
index 0735211a93c3db6fb9a5648a101e59b29b6667f7..d0d7419c3d21341d4f96eccb9fa768e6e636d4a6 100644 |
--- a/chromecast/media/audio/cast_audio_output_stream.cc |
+++ b/chromecast/media/audio/cast_audio_output_stream.cc |
@@ -9,19 +9,30 @@ |
#include "base/bind.h" |
#include "base/callback_helpers.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/threading/thread_task_runner_handle.h" |
#include "chromecast/base/metrics/cast_metrics_helper.h" |
#include "chromecast/base/task_runner_impl.h" |
#include "chromecast/media/audio/cast_audio_manager.h" |
+#include "chromecast/media/cma/backend/media_pipeline_backend_factory.h" |
#include "chromecast/media/cma/base/decoder_buffer_adapter.h" |
#include "chromecast/public/media/decoder_config.h" |
#include "chromecast/public/media/media_pipeline_backend.h" |
#include "chromecast/public/media/media_pipeline_device_params.h" |
#include "chromecast/public/volume_control.h" |
#include "media/audio/audio_device_description.h" |
+#include "media/base/audio_timestamp_helper.h" |
#include "media/base/decoder_buffer.h" |
namespace { |
const int kMaxQueuedDataMs = 1000; |
+ |
+void SignalWaitableEvent(bool* success, |
slan
2017/05/30 16:26:01
nit: DCHECKs on success and waitable_event
alokp
2017/05/30 23:19:33
Is it worth DCHECK'ing every pointer, especially t
slan
2017/05/30 23:44:39
SGTM
|
+ base::WaitableEvent* waitable_event, |
+ bool result) { |
+ *success = result; |
+ waitable_event->Signal(); |
+} |
} // namespace |
namespace chromecast { |
@@ -33,17 +44,29 @@ namespace media { |
class CastAudioOutputStream::Backend |
: public MediaPipelineBackend::Decoder::Delegate { |
public: |
- using PushBufferCompletionCallback = base::Callback<void(bool)>; |
- |
- Backend() : decoder_(nullptr), first_start_(true), error_(false) { |
- thread_checker_.DetachFromThread(); |
+ using OpenCompletionCallback = base::OnceCallback<void(bool)>; |
+ |
+ Backend(const ::media::AudioParameters& audio_params) |
+ : audio_params_(audio_params), |
+ timestamp_helper_(audio_params_.sample_rate()), |
+ buffer_duration_(audio_params.GetBufferDuration()), |
+ first_start_(true), |
+ push_in_progress_(false), |
+ decoder_(nullptr), |
+ source_callback_(nullptr), |
+ weak_factory_(this) { |
+ DETACH_FROM_THREAD(thread_checker_); |
+ } |
+ ~Backend() override { |
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
+ if (backend_ && !first_start_) // Only stop the backend if it was started. |
+ backend_->Stop(); |
} |
- ~Backend() override {} |
- bool Open(const ::media::AudioParameters& audio_params, |
- CastAudioManager* audio_manager) { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- DCHECK(audio_manager); |
+ void Open(MediaPipelineBackendFactory* backend_factory, |
+ OpenCompletionCallback completion_cb) { |
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
+ DCHECK(backend_factory); |
DCHECK(backend_ == nullptr); |
backend_task_runner_.reset(new TaskRunnerImpl()); |
@@ -52,38 +75,44 @@ class CastAudioOutputStream::Backend |
MediaPipelineDeviceParams::kAudioStreamSoundEffects, |
backend_task_runner_.get(), AudioContentType::kMedia, |
::media::AudioDeviceDescription::kDefaultDeviceId); |
- backend_ = audio_manager->CreateMediaPipelineBackend(device_params); |
- if (!backend_) |
- return false; |
+ backend_ = backend_factory->CreateBackend(device_params); |
+ if (!backend_) { |
+ std::move(completion_cb).Run(false); |
+ return; |
+ } |
decoder_ = backend_->CreateAudioDecoder(); |
- if (!decoder_) |
- return false; |
+ if (!decoder_) { |
+ std::move(completion_cb).Run(false); |
+ return; |
+ } |
decoder_->SetDelegate(this); |
AudioConfig audio_config; |
audio_config.codec = kCodecPCM; |
audio_config.sample_format = kSampleFormatS16; |
- audio_config.bytes_per_channel = audio_params.bits_per_sample() / 8; |
- audio_config.channel_number = audio_params.channels(); |
- audio_config.samples_per_second = audio_params.sample_rate(); |
- if (!decoder_->SetConfig(audio_config)) |
- return false; |
- |
- return backend_->Initialize(); |
- } |
+ audio_config.bytes_per_channel = audio_params_.bits_per_sample() / 8; |
+ audio_config.channel_number = audio_params_.channels(); |
+ audio_config.samples_per_second = audio_params_.sample_rate(); |
+ if (!decoder_->SetConfig(audio_config)) { |
+ std::move(completion_cb).Run(false); |
+ return; |
+ } |
- void Close() { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (!backend_->Initialize()) { |
+ std::move(completion_cb).Run(false); |
+ return; |
+ } |
- if (backend_ && !first_start_) // Only stop the backend if it was started. |
- backend_->Stop(); |
- backend_.reset(); |
- backend_task_runner_.reset(); |
+ audio_bus_ = ::media::AudioBus::Create(audio_params_); |
+ decoder_buffer_ = new DecoderBufferAdapter( |
+ new ::media::DecoderBuffer(audio_params_.GetBytesPerBuffer())); |
+ timestamp_helper_.SetBaseTimestamp(base::TimeDelta()); |
+ std::move(completion_cb).Run(true); |
} |
- void Start() { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
+ void Start(AudioSourceCallback* source_callback) { |
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
DCHECK(backend_); |
if (first_start_) { |
@@ -92,66 +121,100 @@ class CastAudioOutputStream::Backend |
} else { |
backend_->Resume(); |
} |
+ |
+ source_callback_ = source_callback; |
+ next_push_time_ = base::TimeTicks::Now(); |
+ if (!push_in_progress_) { |
+ push_in_progress_ = true; |
+ PushBuffer(); |
+ } |
} |
- void Stop() { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
+ void Stop(base::OnceClosure completion_cb) { |
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
DCHECK(backend_); |
backend_->Pause(); |
+ source_callback_ = nullptr; |
+ std::move(completion_cb).Run(); |
} |
- void PushBuffer(scoped_refptr<media::DecoderBufferBase> decoder_buffer, |
- const PushBufferCompletionCallback& completion_cb) { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
+ void SetVolume(double volume) { |
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
DCHECK(decoder_); |
- DCHECK(!completion_cb.is_null()); |
- DCHECK(completion_cb_.is_null()); |
- if (error_) { |
- completion_cb.Run(false); |
+ decoder_->SetVolume(volume); |
+ } |
+ |
+ private: |
+ void PushBuffer() { |
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
+ DCHECK(push_in_progress_); |
+ |
+ if (!source_callback_) { |
+ push_in_progress_ = false; |
return; |
} |
- backend_buffer_ = decoder_buffer; |
- completion_cb_ = completion_cb; |
- BufferStatus status = decoder_->PushBuffer(backend_buffer_.get()); |
+ MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay = |
+ decoder_->GetRenderingDelay(); |
+ base::TimeDelta delay = |
+ base::TimeDelta::FromMicroseconds(rendering_delay.delay_microseconds); |
+ base::TimeTicks delay_timestamp = |
+ base::TimeTicks() + base::TimeDelta::FromMicroseconds( |
+ rendering_delay.timestamp_microseconds); |
+ int frame_count = source_callback_->OnMoreData(delay, delay_timestamp, 0, |
+ audio_bus_.get()); |
+ VLOG(3) << "frames_filled=" << frame_count << " with latency=" << delay; |
+ |
+ DCHECK_EQ(frame_count, audio_bus_->frames()); |
+ DCHECK_EQ(static_cast<int>(decoder_buffer_->data_size()), |
+ frame_count * audio_params_.GetBytesPerFrame()); |
+ audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8, |
+ decoder_buffer_->writable_data()); |
+ decoder_buffer_->set_timestamp(timestamp_helper_.GetTimestamp()); |
+ timestamp_helper_.AddFrames(frame_count); |
+ |
+ BufferStatus status = decoder_->PushBuffer(decoder_buffer_.get()); |
if (status != MediaPipelineBackend::kBufferPending) |
OnPushBufferComplete(status); |
} |
- void SetVolume(double volume) { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- DCHECK(decoder_); |
- decoder_->SetVolume(volume); |
- } |
- |
- MediaPipelineBackend::AudioDecoder::RenderingDelay GetRenderingDelay() { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- DCHECK(decoder_); |
- return decoder_->GetRenderingDelay(); |
- } |
- |
- private: |
// MediaPipelineBackend::Decoder::Delegate implementation |
void OnPushBufferComplete(BufferStatus status) override { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
DCHECK_NE(status, MediaPipelineBackend::kBufferPending); |
- // |completion_cb_| may be null if OnDecoderError was called. |
- if (completion_cb_.is_null()) |
+ DCHECK(push_in_progress_); |
+ push_in_progress_ = false; |
+ |
+ if (!source_callback_) |
+ return; |
+ |
+ if (status != MediaPipelineBackend::kBufferSuccess) { |
+ source_callback_->OnError(); |
return; |
+ } |
+ |
+ // Schedule next push buffer. We don't want to allow more than |
+ // kMaxQueuedDataMs of queued audio. |
+ const base::TimeTicks now = base::TimeTicks::Now(); |
+ next_push_time_ = std::max(now, next_push_time_ + buffer_duration_); |
- base::ResetAndReturn(&completion_cb_) |
- .Run(status == MediaPipelineBackend::kBufferSuccess); |
+ base::TimeDelta delay = (next_push_time_ - now) - |
+ base::TimeDelta::FromMilliseconds(kMaxQueuedDataMs); |
+ delay = std::max(delay, base::TimeDelta()); |
+ |
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
+ FROM_HERE, base::Bind(&Backend::PushBuffer, weak_factory_.GetWeakPtr()), |
+ delay); |
+ push_in_progress_ = true; |
} |
void OnEndOfStream() override {} |
void OnDecoderError() override { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- error_ = true; |
- if (!completion_cb_.is_null()) |
- OnPushBufferComplete(MediaPipelineBackend::kBufferFailed); |
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
+ OnPushBufferComplete(MediaPipelineBackend::kBufferFailed); |
} |
void OnKeyStatusChanged(const std::string& key_id, |
@@ -160,15 +223,21 @@ class CastAudioOutputStream::Backend |
void OnVideoResolutionChanged(const Size& size) override {} |
- std::unique_ptr<MediaPipelineBackend> backend_; |
+ const ::media::AudioParameters audio_params_; |
+ std::unique_ptr<::media::AudioBus> audio_bus_; |
+ scoped_refptr<media::DecoderBufferBase> decoder_buffer_; |
+ ::media::AudioTimestampHelper timestamp_helper_; |
+ const base::TimeDelta buffer_duration_; |
+ bool first_start_; |
+ bool push_in_progress_; |
+ base::TimeTicks next_push_time_; |
std::unique_ptr<TaskRunnerImpl> backend_task_runner_; |
+ std::unique_ptr<MediaPipelineBackend> backend_; |
MediaPipelineBackend::AudioDecoder* decoder_; |
- PushBufferCompletionCallback completion_cb_; |
- bool first_start_; |
- bool error_; |
- scoped_refptr<DecoderBufferBase> backend_buffer_; |
- base::ThreadChecker thread_checker_; |
+ AudioSourceCallback* source_callback_; |
+ THREAD_CHECKER(thread_checker_); |
+ base::WeakPtrFactory<Backend> weak_factory_; |
DISALLOW_COPY_AND_ASSIGN(Backend); |
}; |
@@ -176,23 +245,17 @@ class CastAudioOutputStream::Backend |
CastAudioOutputStream::CastAudioOutputStream( |
const ::media::AudioParameters& audio_params, |
CastAudioManager* audio_manager) |
- : audio_params_(audio_params), |
- audio_manager_(audio_manager), |
- volume_(1.0), |
- source_callback_(nullptr), |
- timestamp_helper_(audio_params_.sample_rate()), |
- backend_(new Backend()), |
- buffer_duration_(audio_params.GetBufferDuration()), |
- push_in_progress_(false), |
- weak_factory_(this) { |
+ : audio_params_(audio_params), audio_manager_(audio_manager), volume_(1.0) { |
VLOG(1) << "CastAudioOutputStream " << this << " created with " |
<< audio_params_.AsHumanReadableString(); |
} |
CastAudioOutputStream::~CastAudioOutputStream() { |
+ DCHECK(!backend_); |
} |
bool CastAudioOutputStream::Open() { |
+ VLOG(1) << this << ": " << __func__; |
slan
2017/05/30 16:26:01
rm
alokp
2017/05/30 23:19:33
Actually I want to log this event.
slan
2017/05/30 23:44:39
Acknowledged.
|
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread()); |
::media::AudioParameters::Format format = audio_params_.format(); |
@@ -208,60 +271,80 @@ bool CastAudioOutputStream::Open() { |
DCHECK_GE(audio_params_.channels(), 1); |
DCHECK_LE(audio_params_.channels(), 2); |
- if (!backend_->Open(audio_params_, audio_manager_)) { |
- LOG(WARNING) << "Failed to create media pipeline backend."; |
- return false; |
+ bool success = false; |
+ DCHECK(!backend_); |
+ backend_ = base::MakeUnique<Backend>(audio_params_); |
+ { |
+ base::WaitableEvent completion_event( |
+ base::WaitableEvent::ResetPolicy::AUTOMATIC, |
+ base::WaitableEvent::InitialState::NOT_SIGNALED); |
+ audio_manager_->backend_task_runner()->PostTask( |
+ FROM_HERE, |
+ base::BindOnce( |
+ &Backend::Open, base::Unretained(backend_.get()), |
+ audio_manager_->backend_factory(), |
+ base::BindOnce(&SignalWaitableEvent, &success, &completion_event))); |
+ completion_event.Wait(); |
} |
- audio_bus_ = ::media::AudioBus::Create(audio_params_); |
- decoder_buffer_ = new DecoderBufferAdapter( |
- new ::media::DecoderBuffer(audio_params_.GetBytesPerBuffer())); |
- timestamp_helper_.SetBaseTimestamp(base::TimeDelta()); |
- |
- VLOG(1) << __FUNCTION__ << " : " << this; |
- return true; |
+ if (!success) |
+ LOG(WARNING) << "Failed to open audio output stream."; |
+ return success; |
} |
void CastAudioOutputStream::Close() { |
+ VLOG(1) << this << ": " << __func__; |
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread()); |
- VLOG(1) << __FUNCTION__ << " : " << this; |
- backend_->Close(); |
+ DCHECK(backend_); |
+ audio_manager_->backend_task_runner()->DeleteSoon(FROM_HERE, |
+ backend_.release()); |
+ |
// Signal to the manager that we're closed and can be removed. |
// This should be the last call in the function as it deletes "this". |
audio_manager_->ReleaseOutputStream(this); |
} |
void CastAudioOutputStream::Start(AudioSourceCallback* source_callback) { |
+ VLOG(2) << this << ": " << __func__; |
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread()); |
DCHECK(source_callback); |
+ DCHECK(backend_); |
- source_callback_ = source_callback; |
- backend_->Start(); |
- |
- next_push_time_ = base::TimeTicks::Now(); |
- if (!push_in_progress_) { |
- audio_manager_->GetTaskRunner()->PostTask( |
- FROM_HERE, base::Bind(&CastAudioOutputStream::PushBuffer, |
- weak_factory_.GetWeakPtr())); |
- push_in_progress_ = true; |
- } |
+ audio_manager_->backend_task_runner()->PostTask( |
+ FROM_HERE, |
+ base::BindOnce(&Backend::Start, base::Unretained(backend_.get()), |
+ source_callback)); |
metrics::CastMetricsHelper::GetInstance()->LogTimeToFirstAudio(); |
} |
void CastAudioOutputStream::Stop() { |
+ VLOG(2) << this << ": " << __func__; |
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread()); |
- |
- source_callback_ = nullptr; |
- backend_->Stop(); |
+ DCHECK(backend_); |
+ |
+ base::WaitableEvent completion_event( |
+ base::WaitableEvent::ResetPolicy::AUTOMATIC, |
+ base::WaitableEvent::InitialState::NOT_SIGNALED); |
+ audio_manager_->backend_task_runner()->PostTask( |
+ FROM_HERE, |
+ base::BindOnce(&Backend::Stop, base::Unretained(backend_.get()), |
+ base::BindOnce(&base::WaitableEvent::Signal, |
+ base::Unretained(&completion_event)))); |
+ completion_event.Wait(); |
} |
void CastAudioOutputStream::SetVolume(double volume) { |
+ VLOG(2) << this << ": " << __func__ << "(" << volume << ")"; |
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread()); |
volume_ = volume; |
- backend_->SetVolume(volume); |
+ if (backend_) { |
+ audio_manager_->backend_task_runner()->PostTask( |
+ FROM_HERE, base::BindOnce(&Backend::SetVolume, |
+ base::Unretained(backend_.get()), volume_)); |
+ } |
} |
void CastAudioOutputStream::GetVolume(double* volume) { |
@@ -270,67 +353,5 @@ void CastAudioOutputStream::GetVolume(double* volume) { |
*volume = volume_; |
} |
-void CastAudioOutputStream::PushBuffer() { |
- DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread()); |
- DCHECK(push_in_progress_); |
- |
- if (!source_callback_) { |
- push_in_progress_ = false; |
- return; |
- } |
- |
- MediaPipelineBackend::AudioDecoder::RenderingDelay rendering_delay = |
- backend_->GetRenderingDelay(); |
- base::TimeDelta delay = |
- base::TimeDelta::FromMicroseconds(rendering_delay.delay_microseconds); |
- base::TimeTicks delay_timestamp = |
- base::TimeTicks() + |
- base::TimeDelta::FromMicroseconds(rendering_delay.timestamp_microseconds); |
- int frame_count = |
- source_callback_->OnMoreData(delay, delay_timestamp, 0, audio_bus_.get()); |
- VLOG(3) << "frames_filled=" << frame_count << " with latency=" << delay; |
- |
- DCHECK_EQ(frame_count, audio_bus_->frames()); |
- DCHECK_EQ(static_cast<int>(decoder_buffer_->data_size()), |
- frame_count * audio_params_.GetBytesPerFrame()); |
- audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8, |
- decoder_buffer_->writable_data()); |
- decoder_buffer_->set_timestamp(timestamp_helper_.GetTimestamp()); |
- timestamp_helper_.AddFrames(frame_count); |
- |
- auto completion_cb = base::Bind(&CastAudioOutputStream::OnPushBufferComplete, |
- weak_factory_.GetWeakPtr()); |
- backend_->PushBuffer(decoder_buffer_, completion_cb); |
-} |
- |
-void CastAudioOutputStream::OnPushBufferComplete(bool success) { |
- DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread()); |
- DCHECK(push_in_progress_); |
- |
- push_in_progress_ = false; |
- |
- if (!source_callback_) |
- return; |
- if (!success) { |
- source_callback_->OnError(); |
- return; |
- } |
- |
- // Schedule next push buffer. We don't want to allow more than |
- // kMaxQueuedDataMs of queued audio. |
- const base::TimeTicks now = base::TimeTicks::Now(); |
- next_push_time_ = std::max(now, next_push_time_ + buffer_duration_); |
- |
- base::TimeDelta delay = (next_push_time_ - now) - |
- base::TimeDelta::FromMilliseconds(kMaxQueuedDataMs); |
- delay = std::max(delay, base::TimeDelta()); |
- |
- audio_manager_->GetTaskRunner()->PostDelayedTask( |
- FROM_HERE, base::Bind(&CastAudioOutputStream::PushBuffer, |
- weak_factory_.GetWeakPtr()), |
- delay); |
- push_in_progress_ = true; |
-} |
- |
} // namespace media |
} // namespace chromecast |