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) { |