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