blob: dc7d58e405a1f8be3195f5f4376e96b900db14df [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/webrtc/audio_processor.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <array>
#include <limits>
#include <memory>
#include <optional>
#include <utility>
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "build/chromecast_buildflags.h"
#include "build/chromeos_buildflags.h"
#include "media/base/audio_fifo.h"
#include "media/base/audio_parameters.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/channel_layout.h"
#include "media/base/limits.h"
#include "media/webrtc/constants.h"
#include "media/webrtc/helpers.h"
#include "media/webrtc/webrtc_features.h"
#include "third_party/webrtc/modules/audio_processing/include/audio_processing.h"
#include "third_party/webrtc_overrides/task_queue_factory.h"
namespace media {
namespace {
constexpr int kBuffersPerSecond = 100; // 10 ms per buffer.
int GetCaptureBufferSize(bool need_webrtc_processing,
const AudioParameters device_format) {
#if BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CAST_ANDROID)
// TODO(henrika): Re-evaluate whether to use same logic as other platforms.
// https://crbug.com/638081
// Note: This computation does not match 2x10 ms as defined for audio
// processing when rates are 50 modulo 100. 22050 Hz here gives buffer size
// (2*22050)/100 = 441 samples, while WebRTC processes in chunks of 22050/100
// = 220 samples. This leads to unnecessary rebuffering.
return 2 * device_format.sample_rate() / 100;
#else
const int buffer_size_10_ms = device_format.sample_rate() / 100;
// If audio processing is turned on, require 10ms buffers to avoid
// rebuffering.
if (need_webrtc_processing) {
DCHECK_EQ(buffer_size_10_ms, webrtc::AudioProcessing::GetFrameSize(
device_format.sample_rate()));
return buffer_size_10_ms;
}
// If WebRTC audio processing is not required and the native hardware buffer
// size was provided, use it. It can be harmful, in terms of CPU/power
// consumption, to use smaller buffer sizes than the native size.
// (https://crbug.com/362261).
if (int hardware_buffer_size = device_format.frames_per_buffer())
return hardware_buffer_size;
// If the buffer size is missing from the device parameters, provide 10ms as
// a fall-back.
return buffer_size_10_ms;
#endif
}
bool ApmNeedsPlayoutReference(const webrtc::AudioProcessing* apm,
const AudioProcessingSettings& settings) {
if (!base::FeatureList::IsEnabled(
features::kWebRtcApmTellsIfPlayoutReferenceIsNeeded)) {
return settings.NeedPlayoutReference();
}
if (!apm) {
// APM is not available; hence, observing the playout reference is not
// needed.
return false;
}
// TODO(crbug.com/1410129): Move the logic below into WebRTC APM since APM may
// use injected sub-modules the usage of which is not reflected in the APM
// config (e.g., render side processing).
const webrtc::AudioProcessing::Config config = apm->GetConfig();
const bool aec = config.echo_canceller.enabled;
const bool legacy_agc =
config.gain_controller1.enabled &&
!config.gain_controller1.analog_gain_controller.enabled;
return aec || legacy_agc;
}
} // namespace
// Wraps AudioBus to provide access to the array of channel pointers, since this
// is the type webrtc::AudioProcessing deals in. The array is refreshed on every
// channel_ptrs() call, and will be valid until the underlying AudioBus pointers
// are changed, e.g. through calls to SetChannelData() or SwapChannels().
class AudioProcessorCaptureBus {
public:
AudioProcessorCaptureBus(int channels, int frames)
: bus_(media::AudioBus::Create(channels, frames)),
channel_ptrs_(new float*[channels]) {
bus_->Zero();
}
media::AudioBus* bus() { return bus_.get(); }
float* const* channel_ptrs() {
for (int i = 0; i < bus_->channels(); ++i) {
channel_ptrs_[i] = bus_->channel(i);
}
return channel_ptrs_.get();
}
private:
std::unique_ptr<media::AudioBus> bus_;
std::unique_ptr<float*[]> channel_ptrs_;
};
// Wraps AudioFifo to provide a cleaner interface to AudioProcessor.
// It avoids the FIFO when the source and destination frames match. If
// |source_channels| is larger than |destination_channels|, only the first
// |destination_channels| are kept from the source.
// Does not support concurrent access.
class AudioProcessorCaptureFifo {
public:
AudioProcessorCaptureFifo(int source_channels,
int destination_channels,
int source_frames,
int destination_frames,
int sample_rate)
:
#if DCHECK_IS_ON()
source_channels_(source_channels),
source_frames_(source_frames),
#endif
sample_rate_(sample_rate),
destination_(
std::make_unique<AudioProcessorCaptureBus>(destination_channels,
destination_frames)),
data_available_(false) {
DCHECK_GE(source_channels, destination_channels);
if (source_channels > destination_channels) {
audio_source_intermediate_ =
media::AudioBus::CreateWrapper(destination_channels);
}
if (source_frames != destination_frames) {
// Since we require every Push to be followed by as many Consumes as
// possible, twice the larger of the two is a (probably) loose upper bound
// on the FIFO size.
const int fifo_frames = 2 * std::max(source_frames, destination_frames);
fifo_ =
std::make_unique<media::AudioFifo>(destination_channels, fifo_frames);
}
}
void Push(const media::AudioBus& source, base::TimeDelta audio_delay) {
#if DCHECK_IS_ON()
DCHECK_EQ(source.channels(), source_channels_);
DCHECK_EQ(source.frames(), source_frames_);
#endif
const media::AudioBus* source_to_push = &source;
if (audio_source_intermediate_) {
for (int i = 0; i < destination_->bus()->channels(); ++i) {
audio_source_intermediate_->SetChannelData(
i, const_cast<float*>(source.channel(i)));
}
audio_source_intermediate_->set_frames(source.frames());
source_to_push = audio_source_intermediate_.get();
}
if (fifo_) {
CHECK_LT(fifo_->frames(), destination_->bus()->frames());
next_audio_delay_ =
audio_delay + fifo_->frames() * base::Seconds(1) / sample_rate_;
fifo_->Push(source_to_push);
} else {
CHECK(!data_available_);
source_to_push->CopyTo(destination_->bus());
next_audio_delay_ = audio_delay;
data_available_ = true;
}
}
// Returns true if there are destination_frames() of data available to be
// consumed, and otherwise false.
bool Consume(AudioProcessorCaptureBus** destination,
base::TimeDelta* audio_delay) {
if (fifo_) {
if (fifo_->frames() < destination_->bus()->frames())
return false;
fifo_->Consume(destination_->bus(), 0, destination_->bus()->frames());
*audio_delay = next_audio_delay_;
next_audio_delay_ -=
destination_->bus()->frames() * base::Seconds(1) / sample_rate_;
} else {
if (!data_available_)
return false;
*audio_delay = next_audio_delay_;
// The data was already copied to |destination_| in this case.
data_available_ = false;
}
*destination = destination_.get();
return true;
}
private:
#if DCHECK_IS_ON()
const int source_channels_;
const int source_frames_;
#endif
const int sample_rate_;
std::unique_ptr<media::AudioBus> audio_source_intermediate_;
std::unique_ptr<AudioProcessorCaptureBus> destination_;
std::unique_ptr<media::AudioFifo> fifo_;
// When using |fifo_|, this is the audio delay of the first sample to be
// consumed next from the FIFO. When not using |fifo_|, this is the audio
// delay of the first sample in |destination_|.
base::TimeDelta next_audio_delay_;
// True when |destination_| contains the data to be returned by the next call
// to Consume(). Only used when the FIFO is disabled.
bool data_available_;
};
// static
std::unique_ptr<AudioProcessor> AudioProcessor::Create(
DeliverProcessedAudioCallback deliver_processed_audio_callback,
LogCallback log_callback,
const AudioProcessingSettings& settings,
const media::AudioParameters& input_format,
const media::AudioParameters& output_format) {
log_callback.Run(base::StringPrintf(
"AudioProcessor::Create({multi_channel_capture_processing=%s})",
settings.multi_channel_capture_processing ? "true" : "false"));
rtc::scoped_refptr<webrtc::AudioProcessing> webrtc_audio_processing =
media::CreateWebRtcAudioProcessingModule(settings);
return std::make_unique<AudioProcessor>(
std::move(deliver_processed_audio_callback), std::move(log_callback),
input_format, output_format, std::move(webrtc_audio_processing),
settings.stereo_mirroring,
ApmNeedsPlayoutReference(webrtc_audio_processing.get(), settings));
}
AudioProcessor::AudioProcessor(
DeliverProcessedAudioCallback deliver_processed_audio_callback,
LogCallback log_callback,
const media::AudioParameters& input_format,
const media::AudioParameters& output_format,
rtc::scoped_refptr<webrtc::AudioProcessing> webrtc_audio_processing,
bool stereo_mirroring,
bool needs_playout_reference)
: webrtc_audio_processing_(webrtc_audio_processing),
stereo_mirroring_(stereo_mirroring),
needs_playout_reference_(needs_playout_reference),
log_callback_(std::move(log_callback)),
input_format_(input_format),
output_format_(output_format),
deliver_processed_audio_callback_(
std::move(deliver_processed_audio_callback)),
audio_delay_stats_reporter_(kBuffersPerSecond),
playout_fifo_(
// Unretained is safe, since the callback is always called
// synchronously within the AudioProcessor.
base::BindRepeating(&AudioProcessor::AnalyzePlayoutData,
base::Unretained(this))) {
DCHECK(deliver_processed_audio_callback_);
DCHECK(log_callback_);
CHECK(input_format_.IsValid());
CHECK(output_format_.IsValid());
if (webrtc_audio_processing_) {
DCHECK_EQ(
webrtc::AudioProcessing::GetFrameSize(output_format_.sample_rate()),
output_format_.frames_per_buffer());
}
if (input_format_.sample_rate() % 100 != 0 ||
output_format_.sample_rate() % 100 != 0) {
// The WebRTC audio processing module may simulate clock drift on
// non-divisible sample rates.
SendLogMessage(base::StringPrintf(
"%s: WARNING: Sample rate not divisible by 100, processing is provided "
"on a best-effort basis. input rate=[%d], output rate=[%d]",
__func__, input_format_.sample_rate(), output_format_.sample_rate()));
}
SendLogMessage(base::StringPrintf(
"%s({input_format_=[%s], output_format_=[%s]})", __func__,
input_format_.AsHumanReadableString().c_str(),
output_format_.AsHumanReadableString().c_str()));
// If audio processing is needed, rebuffer to APM frame size. If not, rebuffer
// to the requested output format.
const int fifo_output_frames_per_buffer =
webrtc_audio_processing_
? webrtc::AudioProcessing::GetFrameSize(input_format_.sample_rate())
: output_format_.frames_per_buffer();
SendLogMessage(base::StringPrintf(
"%s => (capture FIFO: fifo_output_frames_per_buffer=%d)", __func__,
fifo_output_frames_per_buffer));
capture_fifo_ = std::make_unique<AudioProcessorCaptureFifo>(
input_format.channels(), input_format_.channels(),
input_format.frames_per_buffer(), fifo_output_frames_per_buffer,
input_format.sample_rate());
if (webrtc_audio_processing_) {
output_bus_ = std::make_unique<AudioProcessorCaptureBus>(
output_format_.channels(), output_format.frames_per_buffer());
}
}
AudioProcessor::~AudioProcessor() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
OnStopDump();
}
void AudioProcessor::ProcessCapturedAudio(const media::AudioBus& audio_source,
base::TimeTicks audio_capture_time,
int num_preferred_channels,
double volume,
bool key_pressed) {
DCHECK(deliver_processed_audio_callback_);
// Sanity-check the input audio format in debug builds.
DCHECK(input_format_.IsValid());
DCHECK_EQ(audio_source.channels(), input_format_.channels());
DCHECK_EQ(audio_source.frames(), input_format_.frames_per_buffer());
base::TimeDelta capture_delay = base::TimeTicks::Now() - audio_capture_time;
TRACE_EVENT1("audio", "AudioProcessor::ProcessCapturedAudio", "delay (ms)",
capture_delay.InMillisecondsF());
capture_fifo_->Push(audio_source, capture_delay);
// Process and consume the data in the FIFO until there is not enough
// data to process.
AudioProcessorCaptureBus* process_bus;
while (capture_fifo_->Consume(&process_bus, &capture_delay)) {
// Use the process bus directly if audio processing is disabled.
AudioProcessorCaptureBus* output_bus = process_bus;
std::optional<double> new_volume;
if (webrtc_audio_processing_) {
output_bus = output_bus_.get();
new_volume =
ProcessData(process_bus->channel_ptrs(), process_bus->bus()->frames(),
capture_delay, volume, key_pressed,
num_preferred_channels, output_bus->channel_ptrs());
}
// Swap channels before interleaving the data.
if (stereo_mirroring_ &&
output_format_.channel_layout() == media::CHANNEL_LAYOUT_STEREO) {
// Swap the first and second channels.
output_bus->bus()->SwapChannels(0, 1);
}
deliver_processed_audio_callback_.Run(*output_bus->bus(),
audio_capture_time, new_volume);
}
}
void AudioProcessor::SetOutputWillBeMuted(bool muted) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
SendLogMessage(
base::StringPrintf("%s({muted=%s})", __func__, muted ? "true" : "false"));
if (webrtc_audio_processing_) {
webrtc_audio_processing_->set_output_will_be_muted(muted);
}
}
void AudioProcessor::OnStartDump(base::File dump_file) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
DCHECK(dump_file.IsValid());
if (webrtc_audio_processing_) {
if (!worker_queue_) {
worker_queue_ =
CreateWebRtcTaskQueue(webrtc::TaskQueueFactory::Priority::LOW);
}
// Here tasks will be posted on the |worker_queue_|. It must be
// kept alive until media::StopEchoCancellationDump is called or the
// webrtc::AudioProcessing instance is destroyed.
media::StartEchoCancellationDump(webrtc_audio_processing_.get(),
std::move(dump_file), worker_queue_.get());
} else {
// Post the file close to avoid blocking the control sequence.
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::LOWEST, base::MayBlock()},
base::DoNothingWithBoundArgs(std::move(dump_file)));
}
}
void AudioProcessor::OnStopDump() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (!worker_queue_)
return;
if (webrtc_audio_processing_)
media::StopEchoCancellationDump(webrtc_audio_processing_.get());
worker_queue_ = nullptr;
}
void AudioProcessor::OnPlayoutData(const AudioBus& audio_bus,
int sample_rate,
base::TimeDelta audio_delay) {
TRACE_EVENT1("audio", "AudioProcessor::OnPlayoutData", "delay (ms)",
audio_delay.InMillisecondsF());
if (!webrtc_audio_processing_) {
return;
}
unbuffered_playout_delay_ = audio_delay;
if (!playout_sample_rate_hz_ || sample_rate != *playout_sample_rate_hz_) {
// We reset the buffer on sample rate changes because the current buffer
// content is rendered obsolete (the audio processing module will reset
// internally) and the FIFO does not resample previous content to the new
// rate.
// Channel count changes are already handled within the AudioPushFifo.
playout_sample_rate_hz_ = sample_rate;
const int samples_per_channel =
webrtc::AudioProcessing::GetFrameSize(sample_rate);
playout_fifo_.Reset(samples_per_channel);
}
playout_fifo_.Push(audio_bus);
}
void AudioProcessor::AnalyzePlayoutData(const AudioBus& audio_bus,
int frame_delay) {
DCHECK(webrtc_audio_processing_);
DCHECK(playout_sample_rate_hz_.has_value());
const base::TimeDelta playout_delay =
unbuffered_playout_delay_ +
AudioTimestampHelper::FramesToTime(frame_delay, *playout_sample_rate_hz_);
playout_delay_ = playout_delay;
TRACE_EVENT1("audio", "AudioProcessor::AnalyzePlayoutData", "delay (ms)",
playout_delay.InMillisecondsF());
webrtc::StreamConfig input_stream_config(*playout_sample_rate_hz_,
audio_bus.channels());
std::array<const float*, media::limits::kMaxChannels> input_ptrs;
for (int i = 0; i < audio_bus.channels(); ++i)
input_ptrs[i] = audio_bus.channel(i);
const int apm_error = webrtc_audio_processing_->AnalyzeReverseStream(
input_ptrs.data(), input_stream_config);
if (apm_error != webrtc::AudioProcessing::kNoError &&
apm_playout_error_code_log_count_ < 10) {
LOG(ERROR) << "MSAP::OnPlayoutData: AnalyzeReverseStream error="
<< apm_error;
++apm_playout_error_code_log_count_;
}
}
webrtc::AudioProcessingStats AudioProcessor::GetStats() {
if (!webrtc_audio_processing_)
return {};
return webrtc_audio_processing_->GetStatistics();
}
std::optional<double> AudioProcessor::ProcessData(
const float* const* process_ptrs,
int process_frames,
base::TimeDelta capture_delay,
double volume,
bool key_pressed,
int num_preferred_channels,
float* const* output_ptrs) {
DCHECK(webrtc_audio_processing_);
const base::TimeDelta playout_delay = playout_delay_;
TRACE_EVENT2("audio", "AudioProcessor::ProcessData", "capture_delay (ms)",
capture_delay.InMillisecondsF(), "playout_delay (ms)",
playout_delay.InMillisecondsF());
const int64_t total_delay_ms =
(capture_delay + playout_delay).InMilliseconds();
if (total_delay_ms > 300 && large_delay_log_count_ < 10) {
LOG(WARNING) << "Large audio delay, capture delay: "
<< capture_delay.InMillisecondsF()
<< "ms; playout delay: " << playout_delay.InMillisecondsF()
<< "ms";
++large_delay_log_count_;
}
audio_delay_stats_reporter_.ReportDelay(capture_delay, playout_delay);
webrtc::AudioProcessing* ap = webrtc_audio_processing_.get();
DCHECK_LE(total_delay_ms, std::numeric_limits<int>::max());
ap->set_stream_delay_ms(base::saturated_cast<int>(total_delay_ms));
// Keep track of the maximum number of preferred channels. The number of
// output channels of APM can increase if preferred by the sinks, but
// never decrease.
max_num_preferred_output_channels_ =
std::max(max_num_preferred_output_channels_, num_preferred_channels);
// Upscale the volume to the range expected by the WebRTC automatic gain
// controller.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
DCHECK_LE(volume, 1.0);
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) || \
BUILDFLAG(IS_OPENBSD)
// We have a special situation on Linux where the microphone volume can be
// "higher than maximum". The input volume slider in the sound preference
// allows the user to set a scaling that is higher than 100%. It means that
// even if the reported maximum levels is N, the actual microphone level can
// go up to 1.5x*N and that corresponds to a normalized |volume| of 1.5x.
DCHECK_LE(volume, 1.6);
#endif
// Map incoming volume range of [0.0, 1.0] to [0, 255] used by AGC.
// The volume can be higher than 255 on Linux, and it will be cropped to
// 255 since AGC does not allow values out of range.
const int max_analog_gain_level = media::MaxWebRtcAnalogGainLevel();
int current_analog_gain_level =
static_cast<int>((volume * max_analog_gain_level) + 0.5);
current_analog_gain_level =
std::min(current_analog_gain_level, max_analog_gain_level);
DCHECK_LE(current_analog_gain_level, max_analog_gain_level);
ap->set_stream_analog_level(current_analog_gain_level);
ap->set_stream_key_pressed(key_pressed);
// Depending on how many channels the sinks prefer, the number of APM output
// channels is allowed to vary between 1 and the number of channels of the
// output format. The output format in turn depends on the input format.
// Example: With a stereo mic the output format will have 2 channels, and APM
// will produce 1 or 2 output channels depending on the sinks.
int num_apm_output_channels =
std::min(max_num_preferred_output_channels_, output_format_.channels());
// Limit number of apm output channels to 2 to avoid potential problems with
// discrete channel mapping.
num_apm_output_channels = std::min(num_apm_output_channels, 2);
CHECK_GE(num_apm_output_channels, 1);
const webrtc::StreamConfig apm_output_config = webrtc::StreamConfig(
output_format_.sample_rate(), num_apm_output_channels);
int err = ap->ProcessStream(process_ptrs, CreateStreamConfig(input_format_),
apm_output_config, output_ptrs);
DCHECK_EQ(err, 0) << "ProcessStream() error: " << err;
// Upmix if the number of channels processed by APM is less than the number
// specified in the output format. Channels above stereo will be set to zero.
if (num_apm_output_channels < output_format_.channels()) {
if (num_apm_output_channels == 1) {
// The right channel is a copy of the left channel. Remaining channels
// have already been set to zero at initialization.
memcpy(&output_ptrs[1][0], &output_ptrs[0][0],
output_format_.frames_per_buffer() * sizeof(output_ptrs[0][0]));
}
}
// Return a new mic volume, if the volume has been changed.
const int recommended_analog_gain_level =
ap->recommended_stream_analog_level();
if (recommended_analog_gain_level == current_analog_gain_level) {
return std::nullopt;
} else {
return static_cast<double>(recommended_analog_gain_level) /
media::MaxWebRtcAnalogGainLevel();
}
}
// Called on the owning sequence.
void AudioProcessor::SendLogMessage(const std::string& message) {
log_callback_.Run(base::StringPrintf("MSAP::%s [this=0x%" PRIXPTR "]",
message.c_str(),
reinterpret_cast<uintptr_t>(this)));
}
std::optional<AudioParameters> AudioProcessor::ComputeInputFormat(
const AudioParameters& device_format,
const AudioProcessingSettings& audio_processing_settings) {
const ChannelLayout channel_layout = device_format.channel_layout();
// The audio processor can only handle up to two channels.
if (channel_layout != CHANNEL_LAYOUT_MONO &&
channel_layout != CHANNEL_LAYOUT_STEREO &&
channel_layout != CHANNEL_LAYOUT_DISCRETE) {
return std::nullopt;
}
AudioParameters params(
device_format.format(), device_format.channel_layout_config(),
device_format.sample_rate(),
GetCaptureBufferSize(
audio_processing_settings.NeedWebrtcAudioProcessing(),
device_format));
params.set_effects(device_format.effects());
if (channel_layout == CHANNEL_LAYOUT_DISCRETE) {
DCHECK_LE(device_format.channels(), 2);
}
DVLOG(1) << params.AsHumanReadableString();
CHECK(params.IsValid());
return params;
}
// If WebRTC audio processing is used, the default output format is fixed to the
// native WebRTC processing format in order to avoid rebuffering and resampling.
// If not, then the input format is essentially preserved.
// static
AudioParameters AudioProcessor::GetDefaultOutputFormat(
const AudioParameters& input_format,
const AudioProcessingSettings& settings) {
const bool need_webrtc_audio_processing =
settings.NeedWebrtcAudioProcessing();
// TODO(crbug.com/1336055): Investigate why chromecast devices need special
// logic here.
const int output_sample_rate =
need_webrtc_audio_processing ?
#if BUILDFLAG(IS_CASTOS) || BUILDFLAG(IS_CAST_ANDROID)
std::min(media::kAudioProcessingSampleRateHz,
input_format.sample_rate())
#else
media::kAudioProcessingSampleRateHz
#endif
: input_format.sample_rate();
media::ChannelLayoutConfig output_channel_layout_config;
if (!need_webrtc_audio_processing) {
output_channel_layout_config = input_format.channel_layout_config();
} else if (settings.multi_channel_capture_processing) {
// The number of output channels is equal to the number of input channels.
// If the media stream audio processor receives stereo input it will
// output stereo. To reduce computational complexity, APM will not perform
// full multichannel processing unless any sink requests more than one
// channel. If the input is multichannel but the sinks are not interested
// in more than one channel, APM will internally downmix the signal to
// mono and process it. The processed mono signal will then be upmixed to
// same number of channels as the input before leaving the media stream
// audio processor. If a sink later requests stereo, APM will start
// performing true stereo processing. There will be no need to change the
// output format.
output_channel_layout_config = input_format.channel_layout_config();
} else {
output_channel_layout_config = ChannelLayoutConfig::Mono();
}
// When processing is enabled, the buffer size is dictated by
// webrtc::AudioProcessing (typically 10 ms). When processing is disabled, we
// use the same size as the source if it is less than that.
//
// TODO(ajm): This conditional buffer size appears to be assuming knowledge of
// the sink based on the source parameters. PeerConnection sinks seem to want
// 10 ms chunks regardless, while WebAudio sinks want less, and we're assuming
// we can identify WebAudio sinks by the input chunk size. Less fragile would
// be to have the sink actually tell us how much it wants (as in the above
// todo).
int output_frames = webrtc::AudioProcessing::GetFrameSize(output_sample_rate);
if (!need_webrtc_audio_processing &&
input_format.frames_per_buffer() < output_frames) {
output_frames = input_format.frames_per_buffer();
}
media::AudioParameters output_format = media::AudioParameters(
input_format.format(), output_channel_layout_config, output_sample_rate,
output_frames);
return output_format;
}
} // namespace media