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..c00f1073d9b7964fcd6a4542f919359f00adfe80 100644 |
| --- a/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc |
| +++ b/chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc |
| @@ -21,6 +21,7 @@ |
| #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/stream_mixer_alsa_input_impl.h" |
| +#include "chromecast/public/media/audio_device_ids.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/media_switches.h" |
| @@ -117,6 +118,16 @@ const float kSilenceSecondsToFilter = 1.0f; |
| const int64_t kNoTimestamp = std::numeric_limits<int64_t>::min(); |
| +const AudioFilterFactory::FilterType kFilterTypes[kNumFilterGroups] = { |
| + AudioFilterFactory::MEDIA_FILTER, AudioFilterFactory::SYSTEM_AUDIO_FILTER}; |
| + |
| +int GetFilterGroup(std::string stream_name) { |
| + if (stream_name == kSystemAudioDeviceId) { |
| + return 1; |
| + } |
| + return 0; |
| +} |
| + |
| int64_t TimespecToMicroseconds(struct timespec time) { |
| return static_cast<int64_t>(time.tv_sec) * |
| base::Time::kMicrosecondsPerSecond + |
| @@ -167,6 +178,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] += source[i]; |
| + } |
| +} |
| + |
| class StreamMixerAlsaInstance : public StreamMixerAlsa { |
| public: |
| StreamMixerAlsaInstance() {} |
| @@ -246,8 +263,10 @@ StreamMixerAlsa::StreamMixerAlsa() |
| : 0; |
| // Create filters |
| - pre_loopback_filter_ = AudioFilterFactory::MakeAudioFilter( |
| - AudioFilterFactory::PRE_LOOPBACK_FILTER); |
| + for (int filter = 0; filter < kNumFilterGroups; ++filter) { |
| + pre_loopback_filter_[filter] = |
| + AudioFilterFactory::MakeAudioFilter(kFilterTypes[filter]); |
| + } |
| post_loopback_filter_ = AudioFilterFactory::MakeAudioFilter( |
| AudioFilterFactory::POST_LOOPBACK_FILTER); |
| @@ -529,9 +548,11 @@ void StreamMixerAlsa::Start() { |
| } |
| // Initialize filters |
| - if (pre_loopback_filter_) { |
| - pre_loopback_filter_->SetSampleRateAndFormat( |
| - output_samples_per_second_, ::media::SampleFormat::kSampleFormatS32); |
| + for (int filter = 0; filter < kNumFilterGroups; ++filter) { |
| + if (pre_loopback_filter_[filter]) { |
| + pre_loopback_filter_[filter]->SetSampleRateAndFormat( |
| + output_samples_per_second_, ::media::SampleFormat::kSampleFormatS32); |
| + } |
| } |
| if (post_loopback_filter_) { |
| @@ -769,12 +790,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; |
| + std::vector<InputQueue*> active_inputs[kNumFilterGroups]; |
| + bool is_silence = true; |
| for (auto&& input : inputs_) { |
| int read_size = input->MaxReadSize(); |
| if (read_size > 0) { |
| - active_inputs.push_back(input.get()); |
| + active_inputs[GetFilterGroup(input->name())].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"; |
| @@ -801,58 +824,107 @@ 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); |
| - } |
| + ResizeBuffersIfNecessary(chunk_size); |
| + memset(interleaved_.data(), 0, |
| + static_cast<size_t>(chunk_size * kNumOutputChannels) * |
| + BytesPerOutputFormatSample()); |
| + } else { |
| + ResizeBuffersIfNecessary(chunk_size); |
| + } |
| - mixed_->Zero(); |
| - WriteMixedPcm(*mixed_, chunk_size, true /* is_silence */); |
| - return true; |
| + // If |mixed_|, |temp_|, |interleaved_|, or |interleaved_intermediate_| have |
| + // not been allocated, or are too small, allocate a buffer. |
| + |
| + // Mix each group, pass through corresponding audio filter, and |
| + // accumulate into |interleaved_|. |
| + bool filter_frames = false; |
| + for (int filter_group = 0; filter_group < kNumFilterGroups; ++filter_group) { |
| + filter_frames = |
| + filter_frames || |
| + MixAndFilterGroup(active_inputs[filter_group], filter_group, chunk_size, |
| + filter_frames /* accumulate */); |
| } |
| - // 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); |
| + WriteMixedPcm(chunk_size, filter_frames); |
| + return true; |
| +} |
| + |
| +bool StreamMixerAlsa::MixAndFilterGroup( |
| + const std::vector<InputQueue*>& active_inputs, |
| + int filter_group, |
| + int frames, |
| + bool accumulate) { |
| + // Mix into group buffer, |mixed_|. |
| + mixed_->ZeroFramesPartial(0, frames); |
| + bool is_silence = true; |
| + if (!active_inputs.empty()) { |
| + is_silence = false; |
| + // Loop through active inputs, polling them for data, and mixing them. |
| + for (InputQueue* input : active_inputs) { |
| + input->GetResampledData(temp_.get(), frames); |
| + for (int c = 0; c < kNumOutputChannels; ++c) { |
| + input->VolumeScaleAccumulate(c, temp_->channel(c), frames, |
| + mixed_->channel(c)); |
| + } |
| + } |
| } |
| - // 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); |
| + // If no data has been written to |interleaved_| thus far, |
| + // we can write to |interleaved_| directly, skipping a vector addition step. |
| + std::vector<uint8_t>* interleaved_group = &interleaved_intermediate_; |
| + if (!accumulate) { |
| + interleaved_group = &interleaved_; |
| } |
| - mixed_->ZeroFramesPartial(0, chunk_size); |
| + // Convert to interleaved for post-processing. |
| + mixed_->ToInterleaved(frames, BytesPerOutputFormatSample(), |
| + interleaved_group->data()); |
| - // 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)); |
| + // 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; |
|
kmackay
2017/02/17 06:11:25
The design for mixing seems a bit clunky. I'd rath
bshaya
2017/02/17 18:26:53
Hmm, how about that, and:
* Have an InputGroup cla
kmackay
2017/02/17 19:42:42
Your suggestions sound good. I meant for the per-s
bshaya
2017/02/21 23:30:13
Done.
|
| + if (is_silence) { |
| + int silence_frames_to_filter = |
| + output_samples_per_second_ * kSilenceSecondsToFilter; |
| + if (silence_frames_filtered_[filter_group] < silence_frames_to_filter) { |
| + silence_frames_filtered_[filter_group] += frames; |
| + } else { |
| + return false; // Output will be silence, no need to mix. |
| } |
| + } else { |
| + silence_frames_filtered_[filter_group] = 0; |
| } |
| - WriteMixedPcm(*mixed_, chunk_size, false /* is_silence */); |
| - return true; |
| + // Filter the mixed group. |
| + if (pre_loopback_filter_[filter_group] && filter_frames) { |
| + pre_loopback_filter_[filter_group]->ProcessInterleaved( |
| + interleaved_group->data(), frames); |
| + } |
| + |
| + // Exit if we already wrote to |interleaved_|. |
| + if (!accumulate) { |
| + return filter_frames; |
| + } |
| + |
| + // Mix into final output buffer, |interleaved_| |
| + DCHECK_EQ(4, BytesPerOutputFormatSample()); |
| + VectorAccumulate(reinterpret_cast<int32_t*>(interleaved_group->data()), |
| + frames * kNumOutputChannels, |
| + reinterpret_cast<int32_t*>(interleaved_.data())); |
| + return filter_frames; |
| } |
| 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(int frames, bool filter_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); |
| - } |
| - |
| int64_t expected_playback_time; |
| if (rendering_delay_.timestamp_microseconds == kNoTimestamp) { |
| expected_playback_time = kNoTimestamp; |
| @@ -861,29 +933,8 @@ 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); |
| - } |
| - |
| + size_t interleaved_size = static_cast<size_t>(frames * kNumOutputChannels) * |
|
kmackay
2017/02/17 06:11:25
use int instead of size_t
bshaya
2017/02/17 18:26:54
See the first point in "Types" in https://chromium
|
| + BytesPerOutputFormatSample(); |
| for (CastMediaShlib::LoopbackAudioObserver* observer : loopback_observers_) { |
| observer->OnLoopbackAudio(expected_playback_time, kSampleFormatS32, |
| output_samples_per_second_, kNumOutputChannels, |
| @@ -961,5 +1012,24 @@ void StreamMixerAlsa::RemoveLoopbackAudioObserver( |
| observer->OnRemoved(); |
| } |
| +void StreamMixerAlsa::ResizeBuffersIfNecessary(int chunk_size) { |
| + if (!mixed_ || mixed_->frames() < chunk_size) { |
| + mixed_ = ::media::AudioBus::Create(kNumOutputChannels, chunk_size); |
| + } |
| + if (!temp_ || temp_->frames() < chunk_size) { |
| + temp_ = ::media::AudioBus::Create(kNumOutputChannels, chunk_size); |
| + } |
| + |
| + size_t interleaved_size = |
| + static_cast<size_t>(chunk_size * kNumOutputChannels) * |
| + BytesPerOutputFormatSample(); |
| + if (interleaved_.size() < interleaved_size) { |
| + interleaved_.resize(interleaved_size); |
| + } |
| + if (interleaved_intermediate_.size() < interleaved_size) { |
| + interleaved_intermediate_.resize(interleaved_size); |
| + } |
| +} |
| + |
| } // namespace media |
| } // namespace chromecast |