Chromium Code Reviews| Index: chromecast/media/cma/backend/alsa/audio_decoder_alsa.cc |
| diff --git a/chromecast/media/cma/backend/alsa/audio_decoder_alsa.cc b/chromecast/media/cma/backend/alsa/audio_decoder_alsa.cc |
| index cb6014f4b641d5e6b795a3083f63fb2a522f3046..e03c4c9932ca1e1f117be748abd5629aafe819a2 100644 |
| --- a/chromecast/media/cma/backend/alsa/audio_decoder_alsa.cc |
| +++ b/chromecast/media/cma/backend/alsa/audio_decoder_alsa.cc |
| @@ -15,8 +15,15 @@ |
| #include "base/trace_event/trace_event.h" |
| #include "chromecast/base/task_runner_impl.h" |
| #include "chromecast/media/cma/backend/alsa/media_pipeline_backend_alsa.h" |
| +#include "chromecast/media/cma/base/decoder_buffer_adapter.h" |
| #include "chromecast/media/cma/base/decoder_buffer_base.h" |
| #include "chromecast/public/media/cast_decoder_buffer.h" |
| +#include "media/base/audio_buffer.h" |
| +#include "media/base/audio_bus.h" |
| +#include "media/base/channel_layout.h" |
| +#include "media/base/decoder_buffer.h" |
| +#include "media/base/sample_format.h" |
| +#include "media/filters/audio_renderer_algorithm.h" |
| #define TRACE_FUNCTION_ENTRY0() TRACE_EVENT0("cma", __FUNCTION__) |
| @@ -31,32 +38,43 @@ namespace media { |
| namespace { |
| +const int kBitsPerSample = 32; |
| +const int kDefaultFramesPerBuffer = 1024; |
| +const int kSilenceBufferFrames = 2048; |
| +const int kMaxOutputMs = 20; |
| + |
| +const double kPlaybackRateEpsilon = 0.001; |
| + |
| const CastAudioDecoder::OutputFormat kDecoderSampleFormat = |
| CastAudioDecoder::kOutputPlanarFloat; |
| -const int64_t kInvalidDelayTimestamp = std::numeric_limits<int64_t>::min(); |
| - |
| -AudioDecoderAlsa::RenderingDelay kInvalidRenderingDelay() { |
| - AudioDecoderAlsa::RenderingDelay delay; |
| - delay.timestamp_microseconds = kInvalidDelayTimestamp; |
| - delay.delay_microseconds = 0; |
| - return delay; |
| -} |
| +const int64_t kInvalidTimestamp = std::numeric_limits<int64_t>::min(); |
| } // namespace |
| +AudioDecoderAlsa::RateShifterInfo::RateShifterInfo(float playback_rate) |
| + : rate(playback_rate), input_frames(0), output_frames(0) {} |
| + |
| AudioDecoderAlsa::AudioDecoderAlsa(MediaPipelineBackendAlsa* backend) |
| : backend_(backend), |
| task_runner_(backend->GetTaskRunner()), |
| delegate_(nullptr), |
| - is_eos_(false), |
| + pending_write_pcm_(false), |
| + pending_buffer_complete_(false), |
| + got_eos_(false), |
| + pushed_eos_(false), |
| error_(false), |
| + rate_shifter_output_( |
| + ::media::AudioBus::Create(2, kDefaultFramesPerBuffer)), |
|
slan
2016/12/07 00:22:27
nit: Can we do "2 /* num_channels */" or kNumChann
kmackay
2016/12/07 22:59:57
Done.
|
| + current_pts_(kInvalidTimestamp), |
| + pending_output_frames_(0), |
| volume_multiplier_(1.0f), |
| weak_factory_(this) { |
| TRACE_FUNCTION_ENTRY0(); |
| DCHECK(backend_); |
| DCHECK(task_runner_.get()); |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| + rate_shifter_info_.push_back(RateShifterInfo(1.0f)); |
|
slan
2016/12/07 00:22:27
Why do we need this call? The queue is cleared whe
kmackay
2016/12/07 22:59:57
We don't; removed
|
| } |
| AudioDecoderAlsa::~AudioDecoderAlsa() { |
| @@ -74,10 +92,14 @@ void AudioDecoderAlsa::Initialize() { |
| TRACE_FUNCTION_ENTRY0(); |
| DCHECK(delegate_); |
| stats_ = Statistics(); |
| - is_eos_ = false; |
| - last_buffer_pts_ = std::numeric_limits<int64_t>::min(); |
| - |
| - last_known_delay_.timestamp_microseconds = kInvalidDelayTimestamp; |
| + pending_write_pcm_ = false; |
| + pending_buffer_complete_ = false; |
| + got_eos_ = false; |
| + pushed_eos_ = false; |
| + current_pts_ = kInvalidTimestamp; |
| + pending_output_frames_ = 0; |
| + |
| + last_known_delay_.timestamp_microseconds = kInvalidTimestamp; |
| last_known_delay_.delay_microseconds = 0; |
| } |
| @@ -90,8 +112,12 @@ bool AudioDecoderAlsa::Start(int64_t start_pts) { |
| mixer_input_->SetVolumeMultiplier(volume_multiplier_); |
| // Create decoder_ if necessary. This can happen if Stop() was called, and |
| // SetConfig() was not called since then. |
| - if (!decoder_) |
| + if (!decoder_) { |
| CreateDecoder(); |
| + } |
| + if (!rate_shifter_) { |
| + CreateRateShifter(config_.samples_per_second); |
| + } |
| return true; |
| } |
| @@ -99,6 +125,8 @@ void AudioDecoderAlsa::Stop() { |
| TRACE_FUNCTION_ENTRY0(); |
| decoder_.reset(); |
| mixer_input_.reset(); |
| + rate_shifter_.reset(); |
| + weak_factory_.InvalidateWeakPtrs(); |
| Initialize(); |
| } |
| @@ -117,20 +145,34 @@ bool AudioDecoderAlsa::Resume() { |
| return true; |
| } |
| +bool AudioDecoderAlsa::SetPlaybackRate(float rate) { |
| + if (std::abs(rate - 1.0) < kPlaybackRateEpsilon) { |
|
slan
2016/12/07 00:22:27
Why this check? Are we worried about apps setting
kmackay
2016/12/07 22:59:57
AudioRendererAlgorithm treats values close to 1 as
|
| + rate = 1.0f; |
| + } |
| + LOG(INFO) << "SetPlaybackRate to " << rate; |
| + |
| + while (!rate_shifter_info_.empty() && |
| + rate_shifter_info_.back().input_frames == 0) { |
| + rate_shifter_info_.pop_back(); |
| + } |
| + rate_shifter_info_.push_back(RateShifterInfo(rate)); |
| + return true; |
| +} |
| + |
| AudioDecoderAlsa::BufferStatus AudioDecoderAlsa::PushBuffer( |
| CastDecoderBuffer* buffer) { |
| TRACE_FUNCTION_ENTRY0(); |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| DCHECK(buffer); |
| - DCHECK(!is_eos_); |
| + DCHECK(!got_eos_); |
| DCHECK(!error_); |
| + DCHECK(!pending_buffer_complete_); |
| uint64_t input_bytes = buffer->end_of_stream() ? 0 : buffer->data_size(); |
| scoped_refptr<DecoderBufferBase> buffer_base( |
| static_cast<DecoderBufferBase*>(buffer)); |
| if (!buffer->end_of_stream()) { |
| - last_buffer_pts_ = buffer->timestamp(); |
| - current_pts_ = std::min(current_pts_, last_buffer_pts_); |
| + current_pts_ = buffer->timestamp(); |
| } |
| // If the buffer is already decoded, do not attempt to decode. Call |
| @@ -174,6 +216,11 @@ bool AudioDecoderAlsa::SetConfig(const AudioConfig& config) { |
| return false; |
| } |
| + if (!rate_shifter_ || |
| + config.samples_per_second != config_.samples_per_second) { |
| + CreateRateShifter(config.samples_per_second); |
| + } |
| + |
| if (mixer_input_ && config.samples_per_second != config_.samples_per_second) { |
| // Destroy the old input first to ensure that the mixer output sample rate |
| // is updated. |
| @@ -181,11 +228,18 @@ bool AudioDecoderAlsa::SetConfig(const AudioConfig& config) { |
| mixer_input_.reset(new StreamMixerAlsaInput( |
| this, config.samples_per_second, backend_->Primary())); |
| mixer_input_->SetVolumeMultiplier(volume_multiplier_); |
| + pending_write_pcm_ = false; |
| + pending_output_frames_ = 0; |
| } |
| config_ = config; |
| decoder_.reset(); |
| CreateDecoder(); |
| + |
| + if (pending_buffer_complete_ && !rate_shifter_->IsQueueFull()) { |
|
slan
2016/12/07 00:22:27
Why do we need to check the rate_shifter queue her
kmackay
2016/12/07 22:59:57
We need to have flow control, so the app doesn't p
slan
2016/12/09 00:05:31
Acknowledged.
|
| + pending_buffer_complete_ = false; |
| + delegate_->OnPushBufferComplete(MediaPipelineBackendAlsa::kBufferSuccess); |
| + } |
| return true; |
| } |
| @@ -208,6 +262,17 @@ void AudioDecoderAlsa::CreateDecoder() { |
| weak_factory_.GetWeakPtr())); |
| } |
| +void AudioDecoderAlsa::CreateRateShifter(int samples_per_second) { |
|
slan
2016/12/07 00:22:27
Do we want to DCHECK that rate_shifter_ is flushed
kmackay
2016/12/07 22:59:58
No; it might not be in some cases (eg, sample rate
slan
2016/12/09 00:05:31
In that case, wouldn't we want to play out the aud
kmackay
2016/12/09 00:21:16
Ideally yes, but we're already resetting the mixer
|
| + rate_shifter_info_.clear(); |
| + rate_shifter_info_.push_back(RateShifterInfo(1.0f)); |
| + |
| + rate_shifter_.reset(new ::media::AudioRendererAlgorithm()); |
| + rate_shifter_->Initialize(::media::AudioParameters( |
| + ::media::AudioParameters::AUDIO_PCM_LINEAR, |
| + ::media::CHANNEL_LAYOUT_STEREO, samples_per_second, kBitsPerSample, |
| + kDefaultFramesPerBuffer)); |
| +} |
| + |
| bool AudioDecoderAlsa::SetVolume(float multiplier) { |
| TRACE_FUNCTION_ENTRY1(multiplier); |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| @@ -219,7 +284,19 @@ bool AudioDecoderAlsa::SetVolume(float multiplier) { |
| AudioDecoderAlsa::RenderingDelay AudioDecoderAlsa::GetRenderingDelay() { |
| TRACE_FUNCTION_ENTRY0(); |
| - return last_known_delay_; |
| + AudioDecoderAlsa::RenderingDelay delay = last_known_delay_; |
| + if (delay.timestamp_microseconds != kInvalidTimestamp) { |
| + double usec_per_sample = 1000000.0 / config_.samples_per_second; |
| + for (const RateShifterInfo& info : rate_shifter_info_) { |
|
slan
2016/12/07 00:22:27
This is dense. Could you put a simple comment insi
kmackay
2016/12/07 22:59:58
Added comments. The last_known_delay_ is the last
slan
2016/12/09 00:05:31
OK, I get it now.
|
| + double queued_output_frames = |
| + (info.input_frames / info.rate) - info.output_frames; |
| + delay.delay_microseconds += queued_output_frames * usec_per_sample; |
| + } |
| + |
| + delay.delay_microseconds += pending_output_frames_ * usec_per_sample; |
| + } |
| + |
| + return delay; |
| } |
| void AudioDecoderAlsa::OnDecoderInitialized(bool success) { |
| @@ -237,29 +314,156 @@ void AudioDecoderAlsa::OnBufferDecoded( |
| const scoped_refptr<DecoderBufferBase>& decoded) { |
| TRACE_FUNCTION_ENTRY0(); |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| - DCHECK(!is_eos_); |
| - |
| - Statistics delta = Statistics(); |
| + DCHECK(!got_eos_); |
| + DCHECK(!pending_buffer_complete_); |
| + DCHECK(rate_shifter_); |
| if (status == CastAudioDecoder::Status::kDecodeError) { |
| LOG(ERROR) << "Decode error"; |
| - task_runner_->PostTask(FROM_HERE, |
| - base::Bind(&AudioDecoderAlsa::OnWritePcmCompletion, |
| - weak_factory_.GetWeakPtr(), |
| - MediaPipelineBackendAlsa::kBufferFailed, |
| - kInvalidRenderingDelay())); |
| - UpdateStatistics(delta); |
| + delegate_->OnPushBufferComplete(MediaPipelineBackendAlsa::kBufferFailed); |
| + return; |
| + } |
| + if (error_) { |
|
slan
2016/12/07 00:22:27
Can we rename this variable to mixer_error_? This
kmackay
2016/12/07 22:59:58
Done.
|
| + delegate_->OnPushBufferComplete(MediaPipelineBackendAlsa::kBufferFailed); |
| return; |
| } |
| + Statistics delta; |
| delta.decoded_bytes = input_bytes; |
| UpdateStatistics(delta); |
| - if (decoded->end_of_stream()) |
| - is_eos_ = true; |
| + if (decoded->end_of_stream()) { |
| + got_eos_ = true; |
| + } else { |
| + int input_frames = decoded->data_size() / (2 * sizeof(float)); |
|
slan
2016/12/07 00:22:27
nit: kNumChannels
kmackay
2016/12/07 22:59:57
Done.
|
| + |
| + RateShifterInfo* rate_info = &rate_shifter_info_.front(); |
| + // Bypass rate shifter if the rate is 1.0. |
| + if (rate_info->rate == 1.0 && rate_shifter_->frames_buffered() == 0 && |
| + !pending_write_pcm_) { |
| + DCHECK_EQ(rate_info->output_frames, rate_info->input_frames); |
| + pending_buffer_complete_ = true; |
| + pending_write_pcm_ = true; |
| + pending_output_frames_ = input_frames; |
| + if (got_eos_) { |
|
halliwell
2016/12/06 17:27:59
I don't think this branch can be hit? DCHECK(!got
slan
2016/12/07 00:22:27
got_eos_ can mutate above (line 336)
meganit, tho
kmackay
2016/12/07 22:59:57
I prefer to set the state variables before calling
slan
2016/12/09 00:05:31
Yes, I suppose that's fair...
|
| + DCHECK(!pushed_eos_); |
| + pushed_eos_ = true; |
| + } |
| + mixer_input_->WritePcm(decoded); |
| + return; |
| + } |
| + |
| + const uint8_t* channels[2] = { |
|
slan
2016/12/07 00:22:27
Add comment:
// Otherwise, if the rate is not 1.0
kmackay
2016/12/07 22:59:57
Done.
|
| + decoded->data(), decoded->data() + input_frames * sizeof(float)}; |
| + scoped_refptr<::media::AudioBuffer> buffer = ::media::AudioBuffer::CopyFrom( |
| + ::media::kSampleFormatPlanarF32, ::media::CHANNEL_LAYOUT_STEREO, 2, |
| + config_.samples_per_second, input_frames, channels, base::TimeDelta()); |
| + rate_shifter_->EnqueueBuffer(buffer); |
| + rate_shifter_info_.back().input_frames += input_frames; |
| + } |
| + PushRateShifted(); |
| + if (decoded->end_of_stream() || (!rate_shifter_->IsQueueFull() && |
|
slan
2016/12/07 00:22:27
Why not check got_eos_?
kmackay
2016/12/07 22:59:57
Added comment.
|
| + rate_shifter_info_.front().rate != 1.0)) { |
| + delegate_->OnPushBufferComplete(MediaPipelineBackendAlsa::kBufferSuccess); |
| + } else { |
| + pending_buffer_complete_ = true; |
| + } |
| +} |
| + |
| +void AudioDecoderAlsa::PushRateShifted() { |
| DCHECK(mixer_input_); |
| - mixer_input_->WritePcm(decoded); |
| + |
| + if (pending_write_pcm_) { |
| + return; |
| + } |
| + |
| + if (got_eos_) { |
| + // Push some silence into the rate shifter so we can get out any remaining |
| + // rate-shifted data. |
|
slan
2016/12/07 00:22:27
Perhaps make this an anonymous function that takes
kmackay
2016/12/07 22:59:57
Done.
|
| + scoped_refptr<::media::AudioBuffer> silence_buffer = |
| + ::media::AudioBuffer::CreateBuffer( |
| + ::media::kSampleFormatPlanarF32, ::media::CHANNEL_LAYOUT_STEREO, 2, |
| + config_.samples_per_second, kSilenceBufferFrames); |
| + for (uint8_t* channel : silence_buffer->channel_data()) { |
| + float* real_channel = reinterpret_cast<float*>(channel); |
| + std::fill_n(real_channel, kSilenceBufferFrames, 0.0f); |
| + } |
| + |
| + rate_shifter_->EnqueueBuffer(silence_buffer); |
| + } |
| + |
| + RateShifterInfo* rate_info = &rate_shifter_info_.front(); |
|
slan
2016/12/07 00:22:27
It seems to be an implicit assumption everywhere t
kmackay
2016/12/07 22:59:57
Done.
|
| + int64_t possible_output_frames = rate_info->input_frames / rate_info->rate; |
| + DCHECK_GE(possible_output_frames, rate_info->output_frames); |
| + |
| + int desired_output_frames = possible_output_frames - rate_info->output_frames; |
| + if (desired_output_frames == 0) { |
| + if (got_eos_) { |
| + DCHECK(!pushed_eos_); |
| + pending_write_pcm_ = true; |
| + pushed_eos_ = true; |
| + |
| + scoped_refptr<DecoderBufferBase> eos_buffer( |
| + new DecoderBufferAdapter(::media::DecoderBuffer::CreateEOSBuffer())); |
| + mixer_input_->WritePcm(eos_buffer); |
| + } |
| + return; |
| + } |
| + // Don't push too many frames at a time. |
| + desired_output_frames = std::min( |
| + desired_output_frames, config_.samples_per_second * kMaxOutputMs / 1000); |
|
slan
2016/12/07 00:22:27
base::kMillisecondsPerSecond
kmackay
2016/12/07 22:59:57
Added constant.
|
| + |
| + if (desired_output_frames > rate_shifter_output_->frames()) { |
| + rate_shifter_output_ = ::media::AudioBus::Create(2, desired_output_frames); |
|
slan
2016/12/07 00:22:27
Is seems inefficient for rate_shifter_ouptut_ to b
kmackay
2016/12/07 22:59:57
It already does that? It only reallocates if it ne
slan
2016/12/09 00:05:31
Yuuuupppp, lgtm
|
| + } |
| + |
| + int out_frames = rate_shifter_->FillBuffer( |
| + rate_shifter_output_.get(), 0, desired_output_frames, rate_info->rate); |
| + if (out_frames <= 0) { |
| + return; |
| + } |
| + |
| + rate_info->output_frames += out_frames; |
| + DCHECK_GE(possible_output_frames, rate_info->output_frames); |
| + |
| + int channel_data_size = out_frames * sizeof(float); |
|
slan
2016/12/07 00:22:27
Do we have a more conventional way of going from A
kmackay
2016/12/07 22:59:57
No, we don't. Usually we convert from ::media::Dec
slan
2016/12/09 00:05:31
Acknowledged.
|
| + scoped_refptr<DecoderBufferBase> output_buffer(new DecoderBufferAdapter( |
| + new ::media::DecoderBuffer(channel_data_size * 2))); |
| + for (int c = 0; c < 2; ++c) { |
| + memcpy(output_buffer->writable_data() + c * channel_data_size, |
| + rate_shifter_output_->channel(c), channel_data_size); |
| + } |
| + pending_write_pcm_ = true; |
| + pending_output_frames_ = out_frames; |
| + mixer_input_->WritePcm(output_buffer); |
| + |
| + if (rate_shifter_info_.size() > 1 && |
| + possible_output_frames == rate_info->output_frames) { |
| + double remaining_input_frames = |
| + rate_info->input_frames - (rate_info->output_frames * rate_info->rate); |
| + rate_shifter_info_.pop_front(); |
| + |
| + rate_info = &rate_shifter_info_.front(); |
| + LOG(INFO) << "New playback rate in effect: " << rate_info->rate; |
| + rate_info->input_frames += remaining_input_frames; |
| + DCHECK_EQ(0, rate_info->output_frames); |
| + |
| + // If new playback rate is 1.0, clear out 'extra' data in the rate shifter. |
| + if (rate_info->rate == 1.0) { |
| + int extra_frames = rate_shifter_->frames_buffered() - |
| + static_cast<int>(rate_info->input_frames); |
| + if (extra_frames > 0) { |
|
slan
2016/12/07 00:22:27
When this condition hits, what has happened? Shoul
kmackay
2016/12/07 22:59:57
They already were. I added more comments.
|
| + // Clear out extra buffered data. |
| + std::unique_ptr<::media::AudioBus> dropped = |
| + ::media::AudioBus::Create(2, extra_frames); |
| + int cleared_frames = |
| + rate_shifter_->FillBuffer(dropped.get(), 0, extra_frames, 1.0f); |
| + DCHECK_EQ(extra_frames, cleared_frames); |
| + } |
| + rate_info->input_frames = rate_shifter_->frames_buffered(); |
| + } |
| + } |
| } |
| bool AudioDecoderAlsa::BypassDecoder() const { |
| @@ -273,13 +477,32 @@ void AudioDecoderAlsa::OnWritePcmCompletion(BufferStatus status, |
| const RenderingDelay& delay) { |
| TRACE_FUNCTION_ENTRY0(); |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| - if (status == MediaPipelineBackendAlsa::kBufferSuccess && !is_eos_) |
| - current_pts_ = last_buffer_pts_; |
| - if (delay.timestamp_microseconds != kInvalidDelayTimestamp) |
| - last_known_delay_ = delay; |
| - delegate_->OnPushBufferComplete(status); |
| - if (is_eos_) |
| + pending_write_pcm_ = false; |
| + pending_output_frames_ = 0; |
| + last_known_delay_ = delay; |
| + |
| + if (pushed_eos_) { |
| + if (pending_buffer_complete_) { |
| + pending_buffer_complete_ = false; |
| + delegate_->OnPushBufferComplete(MediaPipelineBackendAlsa::kBufferSuccess); |
| + } |
| delegate_->OnEndOfStream(); |
| + } else { |
| + task_runner_->PostTask(FROM_HERE, base::Bind(&AudioDecoderAlsa::PushMorePcm, |
| + weak_factory_.GetWeakPtr())); |
| + } |
| +} |
| + |
| +void AudioDecoderAlsa::PushMorePcm() { |
| + PushRateShifted(); |
| + |
| + double rate = rate_shifter_info_.front().rate; |
|
slan
2016/12/07 00:22:27
nit: Move inside if (pending_buffer_complete_), an
kmackay
2016/12/07 22:59:58
Done.
|
| + if (pending_buffer_complete_ && |
| + ((rate == 1.0 && !pending_write_pcm_) || |
| + (rate != 1.0 && !rate_shifter_->IsQueueFull()))) { |
| + pending_buffer_complete_ = false; |
| + delegate_->OnPushBufferComplete(MediaPipelineBackendAlsa::kBufferSuccess); |
| + } |
| } |
| void AudioDecoderAlsa::OnMixerError(MixerError error) { |