summaryrefslogtreecommitdiffstats
path: root/media/audio/simple_sources.cc
blob: 6aee89fabefafcac2037de9febb4c82254e507f1 (plain)
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
// Copyright (c) 2012 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.
// MSVC++ requires this to be set before any other includes to get M_PI.
#define _USE_MATH_DEFINES

#include "media/audio/simple_sources.h"

#include <stddef.h>

#include <algorithm>
#include <cmath>

#include "base/files/file.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "media/audio/sounds/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 a null pointer if we can't read the file or if it's malformed. The
// caller takes ownership of the returned data. The size of the data is stored
// in |read_length|.
scoped_ptr<char[]> ReadWavFile(const base::FilePath& wav_filename,
                               size_t* read_length) {
  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.";
    return nullptr;
  }

  size_t wav_file_length = wav_file.GetLength();
  if (wav_file_length == 0u) {
    LOG(ERROR) << "Input file to fake device is empty: "
               << wav_filename.value();
    return nullptr;
  }

  scoped_ptr<char[]> data(new char[wav_file_length]);
  size_t read_bytes = wav_file.Read(0, data.get(), wav_file_length);
  if (read_bytes != wav_file_length) {
    LOG(ERROR) << "Failed to read all bytes of " << wav_filename.value();
    return nullptr;
  }
  *read_length = wav_file_length;
  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_;
  bool automatic_beep_;
};

static base::LazyInstance<BeepContext>::Leaky g_beep_context =
    LAZY_INSTANCE_INITIALIZER;
}  // namespace

//////////////////////////////////////////////////////////////////////////////
// SineWaveAudioSource implementation.

SineWaveAudioSource::SineWaveAudioSource(int channels,
                                         double freq, double sample_freq)
    : channels_(channels),
      f_(freq / sample_freq),
      time_state_(0),
      cap_(0),
      callbacks_(0),
      errors_(0) {
}

SineWaveAudioSource::~SineWaveAudioSource() {
}

// The implementation could be more efficient if a lookup table is constructed
// but it is efficient enough for our simple needs.
int SineWaveAudioSource::OnMoreData(AudioBus* audio_bus,
                                    uint32_t total_bytes_delay,
                                    uint32_t frames_skipped) {
  base::AutoLock auto_lock(time_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.
  int max_frames = cap_ > 0 ?
      std::min(audio_bus->frames(), cap_ - time_state_) : audio_bus->frames();
  for (int i = 0; i < max_frames; ++i)
    audio_bus->channel(0)[i] = sin(2.0 * M_PI * f_ * time_state_++);
  for (int i = 1; i < audio_bus->channels(); ++i) {
    memcpy(audio_bus->channel(i), audio_bus->channel(0),
           max_frames * sizeof(*audio_bus->channel(i)));
  }
  return max_frames;
}

void SineWaveAudioSource::OnError(AudioOutputStream* stream) {
  errors_++;
}

void SineWaveAudioSource::CapSamples(int cap) {
  base::AutoLock auto_lock(time_lock_);
  DCHECK_GT(cap, 0);
  cap_ = cap;
}

void SineWaveAudioSource::Reset() {
  base::AutoLock auto_lock(time_lock_);
  time_state_ = 0;
}

FileSource::FileSource(const AudioParameters& params,
                       const base::FilePath& path_to_wav_file)
    : params_(params),
      path_to_wav_file_(path_to_wav_file),
      wav_file_read_pos_(0),
      load_failed_(false) {
}

FileSource::~FileSource() {
}

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 scoped_ptr so it gets deleted when
  // this class destructs. This data must be valid for the lifetime of
  // |wav_audio_handler_|.
  size_t length = 0u;
  raw_wav_data_ = ReadWavFile(path_to_wav_file, &length);
  if (!raw_wav_data_) {
    load_failed_ = true;
    return;
  }

  // Attempt to create a handler with this data. If the data is invalid, return.
  wav_audio_handler_ =
      WavAudioHandler::Create(base::StringPiece(raw_wav_data_.get(), length));
  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,
      GuessChannelLayout(wav_audio_handler_->num_channels()),
      wav_audio_handler_->sample_rate(), wav_audio_handler_->bits_per_sample(),
      params_.frames_per_buffer());

  file_audio_converter_.reset(
      new AudioConverter(file_audio_slice, params_, false));
  file_audio_converter_->AddInput(this);
}

int FileSource::OnMoreData(AudioBus* audio_bus,
                           uint32_t total_bytes_delay,
                           uint32_t frames_skipped) {
  // 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());

  // Stop playing if we've played out the whole file.
  if (wav_audio_handler_->AtEnd(wav_file_read_pos_))
    return 0;

  // This pulls data from ProvideInput.
  file_audio_converter_->Convert(audio_bus);
  return audio_bus->frames();
}

double FileSource::ProvideInput(AudioBus* audio_bus_into_converter,
                                base::TimeDelta buffer_delay) {
  // Unfilled frames will be zeroed by CopyTo.
  size_t bytes_written;
  wav_audio_handler_->CopyTo(audio_bus_into_converter, wav_file_read_pos_,
                             &bytes_written);
  wav_file_read_pos_ += bytes_written;
  return 1.0;
}

void FileSource::OnError(AudioOutputStream* stream) {
}

BeepingSource::BeepingSource(const AudioParameters& params)
    : buffer_size_(params.GetBytesPerBuffer()),
      buffer_(new uint8_t[buffer_size_]),
      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() {
}

int BeepingSource::OnMoreData(AudioBus* audio_bus,
                              uint32_t total_bytes_delay,
                              uint32_t frames_skipped) {
  // Accumulate the time from the last beep.
  interval_from_last_beep_ += base::TimeTicks::Now() - last_callback_time_;

  memset(buffer_.get(), 0, buffer_size_);
  bool should_beep = false;
  BeepContext* beep_context = g_beep_context.Pointer();
  if (beep_context->automatic_beep()) {
    base::TimeDelta delta = interval_from_last_beep_ -
        base::TimeDelta::FromMilliseconds(kAutomaticBeepIntervalInMs);
    if (delta > base::TimeDelta()) {
      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 and bits per channel.
    int high_frames = beep_period_in_frames_ / 2;
    int high_bytes = high_frames * params_.bits_per_sample() *
        params_.channels() / 8;

    // Separate high and low with the same number of bytes to generate a
    // square wave.
    int position = 0;
    while (position + high_bytes <= buffer_size_) {
      // Write high values first.
      memset(buffer_.get() + position, 128, 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();
  audio_bus->FromInterleaved(
      buffer_.get(), audio_bus->frames(), params_.bits_per_sample() / 8);
  return audio_bus->frames();
}

void BeepingSource::OnError(AudioOutputStream* stream) {
}

void BeepingSource::BeepOnce() {
  g_beep_context.Pointer()->SetBeepOnce(true);
}

}  // namespace media