Chromium Code Reviews| 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 463a300aba2498a5aca63e6438cf046cc7c816c3..8e4c352a388b99232615cbd4a24f7e744911944b 100644 |
| --- a/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc |
| +++ b/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc |
| @@ -7,20 +7,26 @@ |
| #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" |
| @@ -112,9 +118,6 @@ static int* kAlsaDirDontCare = nullptr; |
| 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) { |
| @@ -167,6 +170,12 @@ bool GetSwitchValueAsNonNegativeInt(const std::string& switch_name, |
| 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 { |
| public: |
| StreamMixerAlsaInstance() {} |
| @@ -245,11 +254,22 @@ StreamMixerAlsa::StreamMixerAlsa() |
| ? kLowSampleRateCutoff |
| : 0; |
| - // Create filters |
| - pre_loopback_filter_ = AudioFilterFactory::MakeAudioFilter( |
| - AudioFilterFactory::PRE_LOOPBACK_FILTER); |
| - post_loopback_filter_ = AudioFilterFactory::MakeAudioFilter( |
| - AudioFilterFactory::POST_LOOPBACK_FILTER); |
| + // Create filter groups. |
| + 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)); |
| DefineAlsaParameters(); |
| } |
| @@ -529,14 +549,9 @@ void StreamMixerAlsa::Start() { |
| } |
| // Initialize filters |
| - 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); |
| + for (auto&& filter_group : filter_groups_) { |
| + filter_group->Initialize(output_samples_per_second_, |
| + ::media::SampleFormat::kSampleFormatS32); |
| } |
| RETURN_REPORT_ERROR(PcmPrepare, pcm_); |
| @@ -635,6 +650,9 @@ void StreamMixerAlsa::AddInput(std::unique_ptr<InputQueue> input) { |
| } |
| DCHECK(input); |
| + |
| + LOG(INFO) << __FUNCTION__; |
|
kmackay
2017/02/24 18:03:55
Necessary logging?
bshaya
2017/02/27 17:00:15
Done.
|
| + |
| // 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. |
| @@ -644,17 +662,31 @@ void StreamMixerAlsa::AddInput(std::unique_ptr<InputQueue> input) { |
| } |
| check_close_timer_->Stop(); |
| - 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)); |
| + bool found_filter_group = false; |
|
kmackay
2017/02/24 18:03:55
Move down into the case statement? (you'll need br
bshaya
2017/02/27 17:00:15
Done.
|
| + switch (state_) { |
| + case kStateUninitialized: |
| + requested_output_samples_per_second_ = input->input_samples_per_second(); |
| + Start(); |
| + // Fallthrough intended |
| + case kStateNormalPlayback: |
| + 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(); |
| } |
| } |
| @@ -762,6 +794,8 @@ void StreamMixerAlsa::WriteFrames() { |
| bool StreamMixerAlsa::TryWriteFrames() { |
| DCHECK(mixer_task_runner_->BelongsToCurrentThread()); |
| + DCHECK_GE(filter_groups_.size(), 1u); |
| + |
| if (state_ != kStateNormalPlayback) { |
| return false; |
| } |
| @@ -769,12 +803,14 @@ bool StreamMixerAlsa::TryWriteFrames() { |
| const int min_frames_in_buffer = |
| output_samples_per_second_ * kMinBufferedDataMs / 1000; |
| int chunk_size = output_samples_per_second_ * kMaxWriteSizeMs / 1000; |
| - std::vector<InputQueue*> active_inputs; |
| + bool is_silence = true; |
| for (auto&& input : inputs_) { |
| int read_size = input->MaxReadSize(); |
| if (read_size > 0) { |
| - active_inputs.push_back(input.get()); |
| + DCHECK(input->filter_group()); |
| + input->filter_group()->AddActiveInput(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"; |
| @@ -801,57 +837,52 @@ bool StreamMixerAlsa::TryWriteFrames() { |
| } |
| } |
| - if (active_inputs.empty()) { |
| - // No inputs have any data to provide. Fill with silence to avoid underrun. |
| + if (is_silence) { |
| + // No inputs have any data to provide. Push silence to prevent underrun. |
| chunk_size = kPreventUnderrunChunkSize; |
| - 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); |
| + // 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())); |
| + } |
| + } |
| } |
| - 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)); |
| - } |
| + if (!interleaved) { |
| + // No group has any data, write empty buffer. |
| + filter_groups_[0]->ClearInterleaved(chunk_size); |
| + interleaved = filter_groups_[0]->GetInterleaved(); |
| } |
| - WriteMixedPcm(*mixed_, chunk_size, false /* is_silence */); |
| + WriteMixedPcm(interleaved, chunk_size); |
| 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(const ::media::AudioBus& mixed, |
| - int frames, bool is_silence) { |
| +void StreamMixerAlsa::WriteMixedPcm(std::vector<uint8_t>* interleaved, |
| + int frames) { |
| DCHECK(mixer_task_runner_->BelongsToCurrentThread()); |
| CHECK_PCM_INITIALIZED(); |
| - |
| - size_t interleaved_size = static_cast<size_t>(frames * kNumOutputChannels) * |
| - BytesPerOutputFormatSample(); |
| - if (interleaved_.size() < interleaved_size) { |
| - interleaved_.resize(interleaved_size); |
| - } |
| + DCHECK(interleaved); |
| + DCHECK_GE(interleaved->size(), InterleavedSize(frames)); |
| int64_t expected_playback_time; |
| if (rendering_delay_.timestamp_microseconds == kNoTimestamp) { |
| @@ -861,37 +892,10 @@ void StreamMixerAlsa::WriteMixedPcm(const ::media::AudioBus& mixed, |
| 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(), interleaved_size); |
| - } |
| - |
| - if (post_loopback_filter_ && filter_frames) { |
| - post_loopback_filter_->ProcessInterleaved(interleaved_.data(), frames); |
| + interleaved->data(), InterleavedSize(frames)); |
| } |
| // If the PCM has been drained it will be in SND_PCM_STATE_SETUP and need |
| @@ -901,7 +905,7 @@ void StreamMixerAlsa::WriteMixedPcm(const ::media::AudioBus& mixed, |
| } |
| int frames_left = frames; |
| - uint8_t* data = &interleaved_[0]; |
| + uint8_t* data = interleaved->data(); |
| while (frames_left) { |
| int frames_or_error; |
| while ((frames_or_error = alsa_->PcmWritei(pcm_, data, frames_left)) < 0) { |