Commit 6dc7843c authored by Yuri Wiitala's avatar Yuri Wiitala Committed by Commit Bot

Reland "The SnooperNode: Audio loopback for a single stream."

This is a reland of f1b4c43b

Original change's description:
> The SnooperNode: Audio loopback for a single stream.
>
> An audio::GroupMember::Snooper that records the audio from a GroupMember
> on one thread, and re-renders it to the desired output format on another
> thread. Since the data flow rates are known to be driven by different
> clocks (audio hardware clock versus system clock), the SnooperNode also
> uses its resampler to compensate for skew and re-synchronize the audio
> going into and out of it.
>
> Bug: 824019
> Change-Id: I87d410724fd00f9372232bfffdfbb89ada0b3de8
> Reviewed-on: https://chromium-review.googlesource.com/1041657
> Commit-Queue: Yuri Wiitala <miu@chromium.org>
> Reviewed-by: Xiangjun Zhang <xjz@chromium.org>
> Reviewed-by: Chrome Cunningham <chcunningham@chromium.org>
> Reviewed-by: Olga Sharonova <olka@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#556719}

TBR=chcunningham@chromium.org,olka@chromium.org

Bug: 824019
Change-Id: I579b99bda7bc53710004470c0cc07f8a06cdd8b5
Reviewed-on: https://chromium-review.googlesource.com/1050403Reviewed-by: default avatarYuri Wiitala <miu@chromium.org>
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557008}
parent 8052054a
...@@ -42,12 +42,20 @@ void ChannelMixer::Initialize( ...@@ -42,12 +42,20 @@ void ChannelMixer::Initialize(
ChannelMixer::~ChannelMixer() = default; ChannelMixer::~ChannelMixer() = default;
void ChannelMixer::Transform(const AudioBus* input, AudioBus* output) { void ChannelMixer::Transform(const AudioBus* input, AudioBus* output) {
CHECK_EQ(input->frames(), output->frames());
TransformPartial(input, input->frames(), output);
}
void ChannelMixer::TransformPartial(const AudioBus* input,
int frame_count,
AudioBus* output) {
CHECK_EQ(matrix_.size(), static_cast<size_t>(output->channels())); CHECK_EQ(matrix_.size(), static_cast<size_t>(output->channels()));
CHECK_EQ(matrix_[0].size(), static_cast<size_t>(input->channels())); CHECK_EQ(matrix_[0].size(), static_cast<size_t>(input->channels()));
CHECK_EQ(input->frames(), output->frames()); CHECK_LE(frame_count, input->frames());
CHECK_LE(frame_count, output->frames());
// Zero initialize |output| so we're accumulating from zero. // Zero initialize |output| so we're accumulating from zero.
output->Zero(); output->ZeroFrames(frame_count);
// If we're just remapping we can simply copy the correct input to output. // If we're just remapping we can simply copy the correct input to output.
if (remapping_) { if (remapping_) {
...@@ -57,7 +65,7 @@ void ChannelMixer::Transform(const AudioBus* input, AudioBus* output) { ...@@ -57,7 +65,7 @@ void ChannelMixer::Transform(const AudioBus* input, AudioBus* output) {
if (scale > 0) { if (scale > 0) {
DCHECK_EQ(scale, 1.0f); DCHECK_EQ(scale, 1.0f);
memcpy(output->channel(output_ch), input->channel(input_ch), memcpy(output->channel(output_ch), input->channel(input_ch),
sizeof(*output->channel(output_ch)) * output->frames()); sizeof(*output->channel(output_ch)) * frame_count);
break; break;
} }
} }
...@@ -71,7 +79,7 @@ void ChannelMixer::Transform(const AudioBus* input, AudioBus* output) { ...@@ -71,7 +79,7 @@ void ChannelMixer::Transform(const AudioBus* input, AudioBus* output) {
// Scale should always be positive. Don't bother scaling by zero. // Scale should always be positive. Don't bother scaling by zero.
DCHECK_GE(scale, 0); DCHECK_GE(scale, 0);
if (scale > 0) { if (scale > 0) {
vector_math::FMAC(input->channel(input_ch), scale, output->frames(), vector_math::FMAC(input->channel(input_ch), scale, frame_count,
output->channel(output_ch)); output->channel(output_ch));
} }
} }
......
...@@ -35,6 +35,14 @@ class MEDIA_EXPORT ChannelMixer { ...@@ -35,6 +35,14 @@ class MEDIA_EXPORT ChannelMixer {
// Transforms all channels from |input| into |output| channels. // Transforms all channels from |input| into |output| channels.
void Transform(const AudioBus* input, AudioBus* output); void Transform(const AudioBus* input, AudioBus* output);
// Transforms all channels from |input| into |output| channels, for just the
// initial part of the input. Callers can use this to avoid reallocating
// AudioBuses, if the length of the data changes frequently for their use
// case.
void TransformPartial(const AudioBus* input,
int frame_count,
AudioBus* output);
private: private:
void Initialize(ChannelLayout input_layout, int input_channels, void Initialize(ChannelLayout input_layout, int input_channels,
ChannelLayout output_layout, int output_channels); ChannelLayout output_layout, int output_channels);
......
...@@ -53,6 +53,8 @@ source_set("lib") { ...@@ -53,6 +53,8 @@ source_set("lib") {
"service.h", "service.h",
"service_factory.cc", "service_factory.cc",
"service_factory.h", "service_factory.h",
"snooper_node.cc",
"snooper_node.h",
"stream_factory.cc", "stream_factory.cc",
"stream_factory.h", "stream_factory.h",
"sync_reader.cc", "sync_reader.cc",
...@@ -82,6 +84,7 @@ source_set("tests") { ...@@ -82,6 +84,7 @@ source_set("tests") {
"local_muter_unittest.cc", "local_muter_unittest.cc",
"output_controller_unittest.cc", "output_controller_unittest.cc",
"output_stream_unittest.cc", "output_stream_unittest.cc",
"snooper_node_unittest.cc",
"stream_factory_unittest.cc", "stream_factory_unittest.cc",
"sync_reader_unittest.cc", "sync_reader_unittest.cc",
"test/audio_system_to_service_adapter_test.cc", "test/audio_system_to_service_adapter_test.cc",
......
This diff is collapsed.
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_AUDIO_SNOOPER_NODE_H_
#define SERVICES_AUDIO_SNOOPER_NODE_H_
#include <limits>
#include <memory>
#include "base/macros.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "media/base/audio_parameters.h"
#include "media/base/channel_mixer.h"
#include "media/base/multi_channel_resampler.h"
#include "services/audio/delay_buffer.h"
#include "services/audio/group_member.h"
namespace media {
class AudioBus;
} // namespace media
namespace audio {
// Thread-safe implementation of Snooper that records the audio from a
// GroupMember on one thread, and re-renders it to the desired output format on
// another thread. Since the data flow rates are known to be driven by different
// clocks (audio hardware clock versus system clock), the base::TimeTicks
// reference clock is used to detect drift and automatically correct for it to
// maintain proper synchronization.
//
// Throughout this class, there are sample counters (in terms of the input
// audio's sample rate) that are tracked/computed. They refer to the media
// timestamp of the audio flowing through specific parts of the processing
// pipeline: inbound from OnData() calls → through the delay buffer → through
// the resampler → and outbound via Render() calls:
//
// write position: The position of audio about to be written into the delay
// buffer. This is managed by OnData().
// read position: The position of audio about to be read from the delay
// buffer and pushed into the resampler. This is managed by
// ReadFromDelayBuffer().
// output position: The position of the audio about to come out of the
// resampler. This is computed within Render(). Note that
// this is a "virtual" position since it is in terms of the
// input audio's sample count, but refers to audio about to
// be generated in the output format (with a possibly
// different sample rate).
//
// Note that the media timestamps represented by the "positions," as well as the
// surrounding math operations, might seem backwards; but they are not. This is
// because the inbound audio is from a source that pre-renders audio for playout
// in the near future, while the outbound audio is audio that would have been
// played-out in the recent past.
class SnooperNode : public GroupMember::Snooper {
public:
// Use sample counts as a precise measure of audio signal position and time
// duration.
using FrameTicks = int64_t;
// Contruct a SnooperNode that buffers input of one format and renders output
// in [possibly] another format.
SnooperNode(const media::AudioParameters& input_params,
const media::AudioParameters& output_params);
~SnooperNode() final;
// GroupMember::Snooper implementation. Inserts more data into the delay
// buffer.
void OnData(const media::AudioBus& input_bus,
base::TimeTicks reference_time,
double volume) final;
// Renders more audio that was recorded from the GroupMember until
// |output_bus| is filled, resampling and remixing the channels if necessary.
// |reference_time| is used for detecting skip-ahead (i.e., a significant
// forward jump in the reference time) and also to maintain synchronization
// with the input.
void Render(base::TimeTicks reference_time, media::AudioBus* output_bus);
private:
// Helper to store the new |correction_fps|, recompute the resampling I/O
// ratio, and reconfigure the resampler with the new ratio.
void UpdateCorrectionRate(int correction_fps);
// Called by the MultiChannelResampler to acquire more data from the delay
// buffer. This is invoked in the same call stack (and thread) as Render(),
// zero or more times as data is needed by the resampler.
void ReadFromDelayBuffer(int ignored, media::AudioBus* resampler_bus);
// Input and output audio parameters.
const media::AudioParameters input_params_;
const media::AudioParameters output_params_;
// Input and output AudioBus time durations, pre-computed from the input and
// output AudioParameters.
const base::TimeDelta input_bus_duration_;
const base::TimeDelta output_bus_duration_;
// The ratio between the input sampling rate and the output sampling rate. It
// is "perfect" because it assumes no clock skew. Corrections are applied to
// this to determine the actual resampler I/O ratio.
const double perfect_io_ratio_;
// Protects concurrent access to |buffer_| and the |write_position_| and
// |write_reference_time_|. All other members are either read-only, or are not
// accessed by multiple threads.
base::Lock lock_;
// Allows input data to be recorded and then read-back from any position
// later (by the resampler).
DelayBuffer buffer_; // Guarded by |lock_|.
// The next frame position at which to write into the delay buffer, and the
// TimeTicks representing its corresponding system clock timestamp.
FrameTicks write_position_; // Guarded by |lock_|.
base::TimeTicks write_reference_time_; // Guarded by |lock_|.
// The next frame position from which to read from the delay buffer. This is
// the position of the frames about to be pushed into the resampler, not the
// position of frames about to be Render()'ed.
FrameTicks read_position_;
// The expected |reference_time| to be provided in the next call to Render().
// This is used to detect skip-ahead in the output, and compensate when
// necessary.
base::TimeTicks render_reference_time_;
// The additional number of frames currently being consumed by the resampler
// each second to correct for drift.
int correction_fps_;
// Resamples input audio that is read from the delay buffer. Even if the input
// and output have the same sampling rate, this is used to subtly stretch the
// audio signal to correct for drift.
media::MultiChannelResampler resampler_;
// Specifies whether channel mixing should occur before or after resampling,
// or is not needed. The strategy is chosen such that the minimal number of
// channels are resampled, as resampling is the more-expensive operation.
enum { kBefore, kAfter, kNone } const channel_mix_strategy_;
// Only used when the input channel layout differs from the output.
media::ChannelMixer channel_mixer_;
// Only allocated when using the channel mixer. When using the kAfter
// strategy, it is allocated just once, in the constructor, since its frame
// length is constant. When using the kBefore strategy, it is re-allocated
// whenever a larger one is needed and is reused thereafter.
std::unique_ptr<media::AudioBus> mix_bus_;
// An impossible value re-purposed to represent the "null" or "not set yet"
// condition for |read_position_| and |write_position_|.
static constexpr FrameTicks kNullPosition =
std::numeric_limits<FrameTicks>::min();
// The frame position where recording into the delay buffer always starts.
static constexpr FrameTicks kWriteStartPosition = 0;
DISALLOW_COPY_AND_ASSIGN(SnooperNode);
};
} // namespace audio
#endif // SERVICES_AUDIO_SNOOPER_NODE_H_
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment