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..6b508585a18f817204ea6d2396fa8aa2d7800de0 100644 |
| --- a/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc |
| +++ b/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc |
| @@ -7,20 +7,25 @@ |
| #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/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 +117,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 +169,23 @@ bool GetSwitchValueAsNonNegativeInt(const std::string& switch_name, |
| return true; |
| } |
| +// Computes |A| +|B| with clipping |
| +int32_t SaturateSum(int32_t a, int32_t b) { |
|
kmackay
2017/02/23 20:52:56
base/numerics/saturated_arithmetic.h
bshaya
2017/02/24 00:17:08
Done.
|
| + if (a > 0 && std::numeric_limits<int32_t>::max() - a < b) { |
| + return std::numeric_limits<int32_t>::max(); |
| + } |
| + if (a < 0 && std::numeric_limits<int32_t>::min() - a > b) { |
| + return std::numeric_limits<int32_t>::min(); |
| + } |
| + return a + b; |
| +} |
| + |
| +void VectorAccumulate(const int32_t* source, size_t size, int32_t* dest) { |
| + for (size_t i = 0; i < size; ++i) { |
| + dest[i] = SaturateSum(source[i], dest[i]); |
| + } |
| +} |
| + |
| class StreamMixerAlsaInstance : public StreamMixerAlsa { |
| public: |
| StreamMixerAlsaInstance() {} |
| @@ -245,11 +264,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)); |
|
gfhuang
2017/02/23 21:24:56
I don't think this is the right thing to do.
Thes
kmackay
2017/02/23 21:30:36
I think this is OK. It's only temporary (as I unde
bshaya
2017/02/24 00:17:08
The long term probably still has this. For instanc
kmackay
2017/02/24 17:24:57
We haven't planned any way to distinguish content
bshaya
2017/02/27 17:00:14
Done.
|
| DefineAlsaParameters(); |
| } |
| @@ -529,14 +559,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_); |
| @@ -762,6 +787,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 +796,21 @@ 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()); |
| + bool group_match = false; |
| + for (auto&& filter_group : filter_groups_) { |
| + if (filter_group->TryAddActiveInput(input.get())) { |
| + group_match = true; |
| + break; |
| + } |
| + } |
| + DCHECK(group_match) << "Could not find a filter group for " |
| + << input->name(); |
| 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) { |