| // 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. |
| |
| #include "media/audio/simple_sources.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/files/file.h" |
| #include "base/logging.h" |
| #include "base/numerics/math_constants.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 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|. |
| std::unique_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." |
| " Try disabling the sandbox with --no-sandbox."; |
| return nullptr; |
| } |
| |
| int64_t wav_file_length = wav_file.GetLength(); |
| if (wav_file_length < 0) { |
| LOG(ERROR) << "Failed to get size of " << wav_filename.value(); |
| return nullptr; |
| } |
| if (wav_file_length == 0) { |
| LOG(ERROR) << "Input file to fake device is empty: " |
| << wav_filename.value(); |
| return nullptr; |
| } |
| |
| std::unique_ptr<char[]> data(new char[wav_file_length]); |
| int 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_ 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 */, |
| int /* prior_frames_skipped */, |
| AudioBus* dest) { |
| int 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 = cap_ > 0 ? std::min(dest->frames(), cap_ - pos_samples_) |
| : dest->frames(); |
| for (int i = 0; i < max_frames; ++i) |
| dest->channel(0)[i] = sin(2.0 * base::kPiDouble * f_ * pos_samples_++); |
| for (int i = 1; i < dest->channels(); ++i) { |
| memcpy(dest->channel(i), dest->channel(0), |
| max_frames * sizeof(*dest->channel(i))); |
| } |
| } |
| |
| 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), |
| wav_file_read_pos_(0), |
| 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 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(), 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 */, |
| int /* prior_frames_skipped */, |
| 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(wav_file_read_pos_)) { |
| if (looping_) |
| Rewind(); |
| else |
| return 0; |
| } |
| |
| // This pulls data from ProvideInput. |
| file_audio_converter_->Convert(dest); |
| return dest->frames(); |
| } |
| |
| void FileSource::Rewind() { |
| wav_file_read_pos_ = 0; |
| } |
| |
| double FileSource::ProvideInput(AudioBus* audio_bus_into_converter, |
| uint32_t frames_delayed) { |
| // 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(ErrorType type) {} |
| |
| BeepingSource::BeepingSource(const AudioParameters& params) |
| : buffer_size_(params.GetBytesPerBuffer(kSampleFormatU8)), |
| 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() = default; |
| |
| int BeepingSource::OnMoreData(base::TimeDelta /* delay */, |
| base::TimeTicks /* delay_timestamp */, |
| int /* prior_frames_skipped */, |
| AudioBus* dest) { |
| // Accumulate the time from the last beep. |
| interval_from_last_beep_ += base::TimeTicks::Now() - last_callback_time_; |
| |
| memset(buffer_.get(), 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 > 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. |
| 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. |
| int position = 0; |
| while (position + high_bytes <= buffer_size_) { |
| // Write high values first. |
| memset(buffer_.get() + 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_.get(), |
| dest->frames()); |
| return dest->frames(); |
| } |
| |
| void BeepingSource::OnError(ErrorType type) {} |
| |
| void BeepingSource::BeepOnce() { |
| GetBeepContext()->SetBeepOnce(true); |
| } |
| |
| } // namespace media |