| Index: chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc
|
| diff --git a/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc b/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc
|
| index e0ea0e0696b79c2b233f649b249943cc9b5eec6f..463a300aba2498a5aca63e6438cf046cc7c816c3 100644
|
| --- a/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc
|
| +++ b/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc
|
| @@ -7,26 +7,20 @@
|
| #include <algorithm>
|
| #include <cmath>
|
| #include <limits>
|
| -#include <unordered_set>
|
| #include <utility>
|
|
|
| #include "base/bind_helpers.h"
|
| #include "base/command_line.h"
|
| #include "base/lazy_instance.h"
|
| -#include "base/memory/ptr_util.h"
|
| #include "base/memory/weak_ptr.h"
|
| -#include "base/numerics/saturated_arithmetic.h"
|
| #include "base/single_thread_task_runner.h"
|
| #include "base/strings/string_number_conversions.h"
|
| #include "base/threading/platform_thread.h"
|
| #include "base/threading/thread_task_runner_handle.h"
|
| #include "chromecast/base/chromecast_switches.h"
|
| -#include "chromecast/media/base/audio_device_ids.h"
|
| #include "chromecast/media/cma/backend/alsa/alsa_wrapper.h"
|
| #include "chromecast/media/cma/backend/alsa/audio_filter_factory.h"
|
| -#include "chromecast/media/cma/backend/alsa/filter_group.h"
|
| #include "chromecast/media/cma/backend/alsa/stream_mixer_alsa_input_impl.h"
|
| -#include "media/audio/audio_device_description.h"
|
| #include "media/base/audio_bus.h"
|
| #include "media/base/media_switches.h"
|
|
|
| @@ -118,6 +112,9 @@
|
| const snd_pcm_format_t kPreferredSampleFormats[] = {SND_PCM_FORMAT_S32,
|
| SND_PCM_FORMAT_S16};
|
|
|
| +// How many seconds of silence should be passed to the filters to flush them.
|
| +const float kSilenceSecondsToFilter = 1.0f;
|
| +
|
| const int64_t kNoTimestamp = std::numeric_limits<int64_t>::min();
|
|
|
| int64_t TimespecToMicroseconds(struct timespec time) {
|
| @@ -168,12 +165,6 @@
|
| return false;
|
| }
|
| return true;
|
| -}
|
| -
|
| -void VectorAccumulate(const int32_t* source, size_t size, int32_t* dest) {
|
| - for (size_t i = 0; i < size; ++i) {
|
| - dest[i] = base::SaturatedAddition(source[i], dest[i]);
|
| - }
|
| }
|
|
|
| class StreamMixerAlsaInstance : public StreamMixerAlsa {
|
| @@ -254,23 +245,11 @@
|
| ? kLowSampleRateCutoff
|
| : 0;
|
|
|
| - // Create filter groups.
|
| - // TODO(bshaya): Switch to filter groups based on AudioContentType
|
| - filter_groups_.push_back(base::MakeUnique<FilterGroup>(
|
| - std::unordered_set<std::string>(
|
| - {::media::AudioDeviceDescription::kCommunicationsDeviceId}),
|
| - AudioFilterFactory::COMMUNICATION_AUDIO_FILTER));
|
| - filter_groups_.push_back(base::MakeUnique<FilterGroup>(
|
| - std::unordered_set<std::string>({kAlarmAudioDeviceId}),
|
| - AudioFilterFactory::ALARM_AUDIO_FILTER));
|
| - filter_groups_.push_back(base::MakeUnique<FilterGroup>(
|
| - std::unordered_set<std::string>({kTtsAudioDeviceId}),
|
| - AudioFilterFactory::TTS_AUDIO_FILTER));
|
| - filter_groups_.push_back(base::MakeUnique<FilterGroup>(
|
| - std::unordered_set<std::string>(
|
| - {::media::AudioDeviceDescription::kDefaultDeviceId,
|
| - kEarconAudioDeviceId}),
|
| - AudioFilterFactory::MEDIA_AUDIO_FILTER));
|
| + // Create filters
|
| + pre_loopback_filter_ = AudioFilterFactory::MakeAudioFilter(
|
| + AudioFilterFactory::PRE_LOOPBACK_FILTER);
|
| + post_loopback_filter_ = AudioFilterFactory::MakeAudioFilter(
|
| + AudioFilterFactory::POST_LOOPBACK_FILTER);
|
|
|
| DefineAlsaParameters();
|
| }
|
| @@ -550,9 +529,14 @@
|
| }
|
|
|
| // Initialize filters
|
| - for (auto&& filter_group : filter_groups_) {
|
| - filter_group->Initialize(output_samples_per_second_,
|
| - ::media::SampleFormat::kSampleFormatS32);
|
| + if (pre_loopback_filter_) {
|
| + pre_loopback_filter_->SetSampleRateAndFormat(
|
| + output_samples_per_second_, ::media::SampleFormat::kSampleFormatS32);
|
| + }
|
| +
|
| + if (post_loopback_filter_) {
|
| + post_loopback_filter_->SetSampleRateAndFormat(
|
| + output_samples_per_second_, ::media::SampleFormat::kSampleFormatS32);
|
| }
|
|
|
| RETURN_REPORT_ERROR(PcmPrepare, pcm_);
|
| @@ -651,7 +635,6 @@
|
| }
|
|
|
| DCHECK(input);
|
| -
|
| // If the new input is a primary one, we may need to change the output
|
| // sample rate to match its input sample rate.
|
| // We only change the output rate if it is not set to a fixed value.
|
| @@ -661,31 +644,17 @@
|
| }
|
|
|
| check_close_timer_->Stop();
|
| - switch (state_) {
|
| - case kStateUninitialized:
|
| - requested_output_samples_per_second_ = input->input_samples_per_second();
|
| - Start();
|
| - // Fallthrough intended
|
| - case kStateNormalPlayback: {
|
| - bool found_filter_group = false;
|
| - input->Initialize(rendering_delay_);
|
| - for (auto&& filter_group : filter_groups_) {
|
| - if (filter_group->CanProcessInput(input.get())) {
|
| - found_filter_group = true;
|
| - input->set_filter_group(filter_group.get());
|
| - break;
|
| - }
|
| - }
|
| - DCHECK(found_filter_group) << "Could not find a filter group for "
|
| - << input->device_id();
|
| - inputs_.push_back(std::move(input));
|
| - } break;
|
| - case kStateError:
|
| - input->SignalError(StreamMixerAlsaInput::MixerError::kInternalError);
|
| - ignored_inputs_.push_back(std::move(input));
|
| - break;
|
| - default:
|
| - NOTREACHED();
|
| + if (state_ == kStateUninitialized) {
|
| + requested_output_samples_per_second_ = input->input_samples_per_second();
|
| + Start();
|
| + input->Initialize(rendering_delay_);
|
| + inputs_.push_back(std::move(input));
|
| + } else if (state_ == kStateNormalPlayback) {
|
| + input->Initialize(rendering_delay_);
|
| + inputs_.push_back(std::move(input));
|
| + } else {
|
| + input->SignalError(StreamMixerAlsaInput::MixerError::kInternalError);
|
| + ignored_inputs_.push_back(std::move(input));
|
| }
|
| }
|
|
|
| @@ -793,8 +762,6 @@
|
|
|
| bool StreamMixerAlsa::TryWriteFrames() {
|
| DCHECK(mixer_task_runner_->BelongsToCurrentThread());
|
| - DCHECK_GE(filter_groups_.size(), 1u);
|
| -
|
| if (state_ != kStateNormalPlayback) {
|
| return false;
|
| }
|
| @@ -802,17 +769,12 @@
|
| const int min_frames_in_buffer =
|
| output_samples_per_second_ * kMinBufferedDataMs / 1000;
|
| int chunk_size = output_samples_per_second_ * kMaxWriteSizeMs / 1000;
|
| - bool is_silence = true;
|
| - for (auto&& filter_group : filter_groups_) {
|
| - filter_group->ClearActiveInputs();
|
| - }
|
| + std::vector<InputQueue*> active_inputs;
|
| for (auto&& input : inputs_) {
|
| int read_size = input->MaxReadSize();
|
| if (read_size > 0) {
|
| - DCHECK(input->filter_group());
|
| - input->filter_group()->AddActiveInput(input.get());
|
| + active_inputs.push_back(input.get());
|
| chunk_size = std::min(chunk_size, read_size);
|
| - is_silence = false;
|
| } else if (input->primary()) {
|
| if (alsa_->PcmStatus(pcm_, pcm_status_) != 0) {
|
| LOG(ERROR) << "Failed to get status";
|
| @@ -839,52 +801,57 @@
|
| }
|
| }
|
|
|
| - if (is_silence) {
|
| - // No inputs have any data to provide. Push silence to prevent underrun.
|
| + if (active_inputs.empty()) {
|
| + // No inputs have any data to provide. Fill with silence to avoid underrun.
|
| chunk_size = kPreventUnderrunChunkSize;
|
| - }
|
| -
|
| - // Mix and filter each group.
|
| - std::vector<uint8_t>* interleaved = nullptr;
|
| - for (auto&& filter_group : filter_groups_) {
|
| - if (filter_group->MixAndFilter(chunk_size)) {
|
| - if (!interleaved) {
|
| - interleaved = filter_group->GetInterleaved();
|
| - } else {
|
| - DCHECK_EQ(4, BytesPerOutputFormatSample());
|
| - VectorAccumulate(
|
| - reinterpret_cast<int32_t*>(filter_group->GetInterleaved()->data()),
|
| - chunk_size * kNumOutputChannels,
|
| - reinterpret_cast<int32_t*>(interleaved->data()));
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (!interleaved) {
|
| - // No group has any data, write empty buffer.
|
| - filter_groups_[0]->ClearInterleaved(chunk_size);
|
| - interleaved = filter_groups_[0]->GetInterleaved();
|
| - }
|
| -
|
| - WriteMixedPcm(interleaved, chunk_size);
|
| + if (!mixed_ || mixed_->frames() < chunk_size) {
|
| + mixed_ = ::media::AudioBus::Create(kNumOutputChannels, chunk_size);
|
| + }
|
| +
|
| + mixed_->Zero();
|
| + WriteMixedPcm(*mixed_, chunk_size, true /* is_silence */);
|
| + return true;
|
| + }
|
| +
|
| + // If |mixed_| has not been allocated, or it is too small, allocate a buffer.
|
| + if (!mixed_ || mixed_->frames() < chunk_size) {
|
| + mixed_ = ::media::AudioBus::Create(kNumOutputChannels, chunk_size);
|
| + }
|
| +
|
| + // If |temp_| has not been allocated, or is too small, allocate a buffer.
|
| + if (!temp_ || temp_->frames() < chunk_size) {
|
| + temp_ = ::media::AudioBus::Create(kNumOutputChannels, chunk_size);
|
| + }
|
| +
|
| + mixed_->ZeroFramesPartial(0, chunk_size);
|
| +
|
| + // Loop through active inputs, polling them for data, and mixing them.
|
| + for (InputQueue* input : active_inputs) {
|
| + input->GetResampledData(temp_.get(), chunk_size);
|
| + for (int c = 0; c < kNumOutputChannels; ++c) {
|
| + input->VolumeScaleAccumulate(c, temp_->channel(c), chunk_size,
|
| + mixed_->channel(c));
|
| + }
|
| + }
|
| +
|
| + WriteMixedPcm(*mixed_, chunk_size, false /* is_silence */);
|
| return true;
|
| -}
|
| -
|
| -size_t StreamMixerAlsa::InterleavedSize(int frames) {
|
| - return BytesPerOutputFormatSample() *
|
| - static_cast<size_t>(frames * kNumOutputChannels);
|
| }
|
|
|
| ssize_t StreamMixerAlsa::BytesPerOutputFormatSample() {
|
| return alsa_->PcmFormatSize(pcm_format_, 1);
|
| }
|
|
|
| -void StreamMixerAlsa::WriteMixedPcm(std::vector<uint8_t>* interleaved,
|
| - int frames) {
|
| +void StreamMixerAlsa::WriteMixedPcm(const ::media::AudioBus& mixed,
|
| + int frames, bool is_silence) {
|
| DCHECK(mixer_task_runner_->BelongsToCurrentThread());
|
| CHECK_PCM_INITIALIZED();
|
| - DCHECK(interleaved);
|
| - DCHECK_GE(interleaved->size(), InterleavedSize(frames));
|
| +
|
| + size_t interleaved_size = static_cast<size_t>(frames * kNumOutputChannels) *
|
| + BytesPerOutputFormatSample();
|
| + if (interleaved_.size() < interleaved_size) {
|
| + interleaved_.resize(interleaved_size);
|
| + }
|
|
|
| int64_t expected_playback_time;
|
| if (rendering_delay_.timestamp_microseconds == kNoTimestamp) {
|
| @@ -894,10 +861,37 @@
|
| rendering_delay_.delay_microseconds;
|
| }
|
|
|
| + mixed.ToInterleaved(frames, BytesPerOutputFormatSample(),
|
| + interleaved_.data());
|
| +
|
| + // Ensure that, on onset of silence, at least |kSilenceSecondsToFilter|
|
| + // second of audio get pushed through the filters to clear any memory.
|
| + bool filter_frames = true;
|
| + if (is_silence) {
|
| + int silence_frames_to_filter =
|
| + output_samples_per_second_ * kSilenceSecondsToFilter;
|
| + if (silence_frames_filtered_ < silence_frames_to_filter) {
|
| + silence_frames_filtered_ += frames;
|
| + } else {
|
| + filter_frames = false;
|
| + }
|
| + } else {
|
| + silence_frames_filtered_ = 0;
|
| + }
|
| +
|
| + // Filter, send to observers, and post filter
|
| + if (pre_loopback_filter_ && filter_frames) {
|
| + pre_loopback_filter_->ProcessInterleaved(interleaved_.data(), frames);
|
| + }
|
| +
|
| for (CastMediaShlib::LoopbackAudioObserver* observer : loopback_observers_) {
|
| observer->OnLoopbackAudio(expected_playback_time, kSampleFormatS32,
|
| output_samples_per_second_, kNumOutputChannels,
|
| - interleaved->data(), InterleavedSize(frames));
|
| + interleaved_.data(), interleaved_size);
|
| + }
|
| +
|
| + if (post_loopback_filter_ && filter_frames) {
|
| + post_loopback_filter_->ProcessInterleaved(interleaved_.data(), frames);
|
| }
|
|
|
| // If the PCM has been drained it will be in SND_PCM_STATE_SETUP and need
|
| @@ -907,7 +901,7 @@
|
| }
|
|
|
| int frames_left = frames;
|
| - uint8_t* data = interleaved->data();
|
| + uint8_t* data = &interleaved_[0];
|
| while (frames_left) {
|
| int frames_or_error;
|
| while ((frames_or_error = alsa_->PcmWritei(pcm_, data, frames_left)) < 0) {
|
|
|