1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "media/audio/simple_sources.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <numbers>
#include <string_view>
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/logging.h"
#include "base/numerics/checked_math.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/audio/wav_audio_handler.h"
#include "media/base/audio_bus.h"
namespace media {
namespace {
// Opens |wav_filename|, reads it and loads it as a wav file. This function will
// return an empty HeapArray if we can't read the file or if it's malformed. The
// caller takes ownership of the returned data.
base::HeapArray<uint8_t> ReadWavFile(const base::FilePath& wav_filename) {
base::File wav_file(wav_filename,
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!wav_file.IsValid()) {
LOG(ERROR) << "Failed to read " << wav_filename.value()
<< " as input to the fake device."
" Try disabling the sandbox with --no-sandbox.";
return {};
}
int64_t wav_file_length = wav_file.GetLength();
if (wav_file_length < 0) {
LOG(ERROR) << "Failed to get size of " << wav_filename.value();
return {};
}
if (wav_file_length == 0) {
LOG(ERROR) << "Input file to fake device is empty: "
<< wav_filename.value();
return {};
}
auto data = base::HeapArray<uint8_t>::Uninit(wav_file_length);
std::optional<size_t> read_bytes = wav_file.Read(0, data.as_span());
if (read_bytes != wav_file_length) {
LOG(ERROR) << "Failed to read all bytes of " << wav_filename.value();
return {};
}
return data;
}
// These values are based on experiments for local-to-local
// PeerConnection to demonstrate audio/video synchronization.
static const int kBeepDurationMilliseconds = 20;
static const int kBeepFrequency = 400;
// Intervals between two automatic beeps.
static const int kAutomaticBeepIntervalInMs = 500;
// Automatic beep will be triggered every |kAutomaticBeepIntervalInMs| unless
// users explicitly call BeepOnce(), which will disable the automatic beep.
class BeepContext {
public:
BeepContext() : beep_once_(false), automatic_beep_(true) {}
void SetBeepOnce(bool enable) {
base::AutoLock auto_lock(lock_);
beep_once_ = enable;
// Disable the automatic beep if users explicit set |beep_once_| to true.
if (enable)
automatic_beep_ = false;
}
bool beep_once() const {
base::AutoLock auto_lock(lock_);
return beep_once_;
}
bool automatic_beep() const {
base::AutoLock auto_lock(lock_);
return automatic_beep_;
}
private:
mutable base::Lock lock_;
bool beep_once_ GUARDED_BY(lock_);
bool automatic_beep_ GUARDED_BY(lock_);
};
BeepContext* GetBeepContext() {
static BeepContext* context = new BeepContext();
return context;
}
} // namespace
//////////////////////////////////////////////////////////////////////////////
// SineWaveAudioSource implementation.
SineWaveAudioSource::SineWaveAudioSource(int channels,
double freq,
double sample_freq)
: channels_(channels), f_(freq / sample_freq) {}
SineWaveAudioSource::~SineWaveAudioSource() = default;
// The implementation could be more efficient if a lookup table is constructed
// but it is efficient enough for our simple needs.
int SineWaveAudioSource::OnMoreData(base::TimeDelta /* delay */,
base::TimeTicks /* delay_timestamp */,
const AudioGlitchInfo& /* glitch_info */,
AudioBus* dest) {
size_t max_frames;
{
base::AutoLock auto_lock(lock_);
callbacks_++;
// The table is filled with s(t) = kint16max*sin(Theta*t),
// where Theta = 2*PI*fs.
// We store the discrete time value |t| in a member to ensure that the
// next pass starts at a correct state.
max_frames = static_cast<size_t>(dest->frames());
if (cap_ > 0) {
max_frames =
base::CheckMin(max_frames, base::CheckSub(cap_, pos_samples_))
.ValueOrDie();
}
if (max_frames > 0) {
auto first_channel_frames = dest->channel_span(0).first(max_frames);
for (float& sample : first_channel_frames) {
sample = sin(2.0 * std::numbers::pi * f_ * pos_samples_++);
}
for (int ch = 1; ch < dest->channels(); ++ch) {
dest->channel_span(ch)
.first(max_frames)
.copy_from_nonoverlapping(first_channel_frames);
}
}
}
if (on_more_data_callback_)
on_more_data_callback_.Run();
return max_frames;
}
void SineWaveAudioSource::OnError(ErrorType type) {
errors_++;
}
void SineWaveAudioSource::CapSamples(int cap) {
base::AutoLock auto_lock(lock_);
DCHECK_GT(cap, 0);
cap_ = cap;
}
void SineWaveAudioSource::Reset() {
base::AutoLock auto_lock(lock_);
pos_samples_ = 0;
}
FileSource::FileSource(const AudioParameters& params,
const base::FilePath& path_to_wav_file,
bool loop)
: params_(params),
path_to_wav_file_(path_to_wav_file),
load_failed_(false),
looping_(loop) {}
FileSource::~FileSource() = default;
void FileSource::LoadWavFile(const base::FilePath& path_to_wav_file) {
// Don't try again if we already failed.
if (load_failed_)
return;
// Read the file, and put its data in a HeapArray so it gets deleted when this
// class destructs. This data must be valid for the lifetime of
// |wav_audio_handler_|.
raw_wav_data_ = ReadWavFile(path_to_wav_file);
if (raw_wav_data_.empty()) {
load_failed_ = true;
return;
}
// Attempt to create a handler with this data. If the data is invalid, return.
wav_audio_handler_ =
WavAudioHandler::Create(base::as_byte_span(raw_wav_data_));
if (!wav_audio_handler_) {
LOG(ERROR) << "WAV data could be read but is not valid";
load_failed_ = true;
return;
}
// Hook us up so we pull in data from the file into the converter. We need to
// modify the wav file's audio parameters since we'll be reading small slices
// of it at a time and not the whole thing (like 10 ms at a time).
AudioParameters file_audio_slice(
AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Guess(wav_audio_handler_->GetNumChannels()),
wav_audio_handler_->GetSampleRate(), params_.frames_per_buffer());
file_audio_converter_ =
std::make_unique<AudioConverter>(file_audio_slice, params_, false);
file_audio_converter_->AddInput(this);
}
int FileSource::OnMoreData(base::TimeDelta /* delay */,
base::TimeTicks /* delay_timestamp */,
const AudioGlitchInfo& /* glitch_info */,
AudioBus* dest) {
// Load the file if we haven't already. This load needs to happen on the
// audio thread, otherwise we'll run on the UI thread on Mac for instance.
// This will massively delay the first OnMoreData, but we'll catch up.
if (!wav_audio_handler_)
LoadWavFile(path_to_wav_file_);
if (load_failed_)
return 0;
DCHECK(wav_audio_handler_.get());
if (wav_audio_handler_->AtEnd()) {
if (looping_)
Rewind();
else
return 0;
}
// This pulls data from ProvideInput.
file_audio_converter_->Convert(dest);
return dest->frames();
}
void FileSource::Rewind() {
wav_audio_handler_->Reset();
}
double FileSource::ProvideInput(AudioBus* audio_bus_into_converter,
uint32_t frames_delayed,
const AudioGlitchInfo&) {
// Unfilled frames will be zeroed by CopyTo.
size_t frames_written;
wav_audio_handler_->CopyTo(audio_bus_into_converter, &frames_written);
return 1.0;
}
void FileSource::OnError(ErrorType type) {}
BeepingSource::BeepingSource(const AudioParameters& params)
: buffer_(base::HeapArray<uint8_t>::Uninit(
params.GetBytesPerBuffer(kSampleFormatU8))),
params_(params),
last_callback_time_(base::TimeTicks::Now()),
beep_duration_in_buffers_(kBeepDurationMilliseconds *
params.sample_rate() /
params.frames_per_buffer() / 1000),
beep_generated_in_buffers_(0),
beep_period_in_frames_(params.sample_rate() / kBeepFrequency) {}
BeepingSource::~BeepingSource() = default;
int BeepingSource::OnMoreData(base::TimeDelta /* delay */,
base::TimeTicks /* delay_timestamp */,
const AudioGlitchInfo& /* glitch_info */,
AudioBus* dest) {
// Accumulate the time from the last beep.
interval_from_last_beep_ += base::TimeTicks::Now() - last_callback_time_;
memset(buffer_.data(), 128, buffer_.size());
bool should_beep = false;
BeepContext* beep_context = GetBeepContext();
if (beep_context->automatic_beep()) {
base::TimeDelta delta = interval_from_last_beep_ -
base::Milliseconds(kAutomaticBeepIntervalInMs);
if (delta.is_positive()) {
should_beep = true;
interval_from_last_beep_ = delta;
}
} else {
should_beep = beep_context->beep_once();
beep_context->SetBeepOnce(false);
}
// If this object was instructed to generate a beep or has started to
// generate a beep sound.
if (should_beep || beep_generated_in_buffers_) {
// Compute the number of frames to output high value. Then compute the
// number of bytes based on channels.
int high_frames = beep_period_in_frames_ / 2;
int high_bytes = high_frames * params_.channels();
// Separate high and low with the same number of bytes to generate a
// square wave.
size_t position = 0;
while (position + high_bytes <= buffer_.size()) {
// Write high values first.
memset(buffer_.data() + position, 255, high_bytes);
// Then leave low values in the buffer with |high_bytes|.
position += high_bytes * 2;
}
++beep_generated_in_buffers_;
if (beep_generated_in_buffers_ >= beep_duration_in_buffers_)
beep_generated_in_buffers_ = 0;
}
last_callback_time_ = base::TimeTicks::Now();
dest->FromInterleaved<UnsignedInt8SampleTypeTraits>(buffer_.data(),
dest->frames());
return dest->frames();
}
void BeepingSource::OnError(ErrorType type) {}
void BeepingSource::BeepOnce() {
GetBeepContext()->SetBeepOnce(true);
}
} // namespace media
|