diff options
author | kylep@chromium.org <kylep@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-07 00:22:57 +0000 |
---|---|---|
committer | kylep@chromium.org <kylep@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-07 00:22:57 +0000 |
commit | 67c68b544e429d34a445d20a83d5d3d6993aa7d0 (patch) | |
tree | 6143bd0dc0abf28b55445cce9964c9727af2535a /media | |
parent | 7b57c3af9a2762d57d4adcea1b5dd7cd5e9c970a (diff) | |
download | chromium_src-67c68b544e429d34a445d20a83d5d3d6993aa7d0.zip chromium_src-67c68b544e429d34a445d20a83d5d3d6993aa7d0.tar.gz chromium_src-67c68b544e429d34a445d20a83d5d3d6993aa7d0.tar.bz2 |
OLA Algorithm and test shell.
BUG=16011
TEST=none
Review URL: http://codereview.chromium.org/151120
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19994 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/audio_renderer_algorithm_ola.cc | 212 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_ola.h | 75 | ||||
-rw-r--r-- | media/media.gyp | 12 | ||||
-rw-r--r-- | media/tools/wav_ola_test.cc | 153 |
4 files changed, 452 insertions, 0 deletions
diff --git a/media/filters/audio_renderer_algorithm_ola.cc b/media/filters/audio_renderer_algorithm_ola.cc new file mode 100644 index 0000000..9a235de --- /dev/null +++ b/media/filters/audio_renderer_algorithm_ola.cc @@ -0,0 +1,212 @@ +// Copyright (c) 2009 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/filters/audio_renderer_algorithm_ola.h" + +#include <cmath> + +#include "media/base/buffers.h" +#include "media/base/data_buffer.h" + +namespace media { + +// Default window size in bytes. +// TODO(kylep): base the window size in seconds, not bytes. +const size_t kDefaultWindowSize = 4096; + +AudioRendererAlgorithmOLA::AudioRendererAlgorithmOLA() + : data_offset_(0), + input_step_(0), + output_step_(0) { +} + +AudioRendererAlgorithmOLA::~AudioRendererAlgorithmOLA() { +} + +size_t AudioRendererAlgorithmOLA::FillBuffer(DataBuffer* buffer_out) { + if (IsInputFinished()) + return 0; + if (playback_rate() == 0.0f) + return 0; + + // Grab info from |buffer_out| and handle the simple case of normal playback. + size_t dest_remaining = buffer_out->GetDataSize(); + uint8* dest = buffer_out->GetWritableData(dest_remaining); + size_t dest_written = 0; + if (playback_rate() == 1.0f) { + dest_written = CopyInput(dest, dest_remaining); + AdvanceInput(dest_written); + return dest_written; + } + + // For other playback rates, OLA with crossfade! + // TODO(kylep): Limit the rates to reasonable values. We may want to do this + // on the UI side or in set_playback_rate(). + while (dest_remaining >= output_step_ + crossfade_size_) { + // Copy bulk of data to output (including some to crossfade to the next + // copy), then add to our running sum of written data and subtract from + // our tally of remaing requested. + size_t copied = CopyInput(dest, output_step_ + crossfade_size_); + dest_written += copied; + dest_remaining -= copied; + + // Advance pointers for crossfade. + dest += output_step_; + AdvanceInput(input_step_); + + // Prepare intermediate buffer. + size_t crossfade_size; + scoped_array<uint8> src(new uint8[crossfade_size_]); + crossfade_size = CopyInput(src.get(), crossfade_size_); + + // Calculate number of samples to crossfade, then do so. + int samples = static_cast<int>(crossfade_size / sample_bytes() + / channels()); + switch (sample_bytes()) { + case 4: + Crossfade(samples, + reinterpret_cast<const int32*>(src.get()), + reinterpret_cast<int32*>(dest)); + break; + case 2: + Crossfade(samples, + reinterpret_cast<const int16*>(src.get()), + reinterpret_cast<int16*>(dest)); + break; + case 1: + Crossfade(samples, src.get(), dest); + break; + default: + NOTREACHED() << "Unsupported audio bit depth sent to OLA algorithm"; + } + + // Advance pointers again. + AdvanceInput(crossfade_size_); + dest += crossfade_size_; + } + return dest_written; +} + +void AudioRendererAlgorithmOLA::FlushBuffers() { + AudioRendererAlgorithmBase::FlushBuffers(); + saved_buf_ = NULL; +} + +void AudioRendererAlgorithmOLA::set_playback_rate(float new_rate) { + AudioRendererAlgorithmBase::set_playback_rate(new_rate); + + // Adjusting step sizes to accomodate requested playback rate. + if (playback_rate() > 1.0f) { + input_step_ = kDefaultWindowSize; + output_step_ = static_cast<size_t>(ceil( + static_cast<float>(kDefaultWindowSize / playback_rate()))); + } else { + input_step_ = static_cast<size_t>(ceil( + static_cast<float>(kDefaultWindowSize * playback_rate()))); + output_step_ = kDefaultWindowSize; + } + AlignToSampleBoundary(&input_step_); + AlignToSampleBoundary(&output_step_); + + // Calculate length for crossfading. + crossfade_size_ = kDefaultWindowSize / 10; + AlignToSampleBoundary(&crossfade_size_); + + // To keep true to playback rate, modify the steps. + input_step_ -= crossfade_size_; + output_step_ -= crossfade_size_; +} + +void AudioRendererAlgorithmOLA::AdvanceInput(size_t bytes) { + if (IsInputFinished()) + return; + + DCHECK(saved_buf_) << "Did you forget to call CopyInput()?"; + + // Calculate number of usable bytes in |saved_buf_|. + size_t saved_buf_remaining = saved_buf_->GetDataSize() - data_offset_; + + // If there is enough data in |saved_buf_| to advance into it, do so. + // Otherwise, advance into the queue. + if (saved_buf_remaining > bytes) { + data_offset_ += bytes; + } else { + if (!IsQueueEmpty()) { + saved_buf_ = FrontQueue(); + PopFrontQueue(); + } else { + saved_buf_ = NULL; + } + // TODO(kylep): Make this function loop to eliminate the DCHECK. + DCHECK_GE(bytes, saved_buf_remaining); + + data_offset_ = bytes - saved_buf_remaining; + } +} + +void AudioRendererAlgorithmOLA::AlignToSampleBoundary(size_t* value) { + (*value) -= ((*value) % (channels() * sample_bytes())); +} + +// TODO(kylep): Make this function loop to satisfy requests better. +size_t AudioRendererAlgorithmOLA::CopyInput(uint8* dest, size_t length) { + if (IsInputFinished()) + return 0; + + // Lazy initialization. + if (!saved_buf_) { + saved_buf_ = FrontQueue(); + PopFrontQueue(); + } + + size_t dest_written = 0; + size_t data_length = saved_buf_->GetDataSize() - data_offset_; + + // Prevent writing past end of the buffer. + if (data_length > length) + data_length = length; + memcpy(dest, saved_buf_->GetData() + data_offset_, data_length); + + dest += data_length; + length -= data_length; + dest_written += data_length; + + if (length > 0) { + // We should have enough data in the next buffer so long as the + // queue is not empty. + if (IsQueueEmpty()) + return dest_written; + DCHECK_LE(length, FrontQueue()->GetDataSize()); + + memcpy(dest, FrontQueue()->GetData(), length); + dest_written += length; + } + + return dest_written; +} + +template <class Type> +void AudioRendererAlgorithmOLA::Crossfade(int samples, + const Type* src, + Type* dest) { + Type* dest_end = dest + samples * channels(); + const Type* src_end = src + samples * channels(); + for (int i = 0; i < samples; ++i) { + double x_ratio = static_cast<double>(i) / static_cast<double>(samples); + for (int j = 0; j < channels(); ++j) { + DCHECK(dest < dest_end); + DCHECK(src < src_end); + (*dest) = static_cast<Type>((*dest) * (1.0 - x_ratio) + + (*src) * x_ratio); + ++src; + ++dest; + } + } +} + +bool AudioRendererAlgorithmOLA::IsInputFinished() { + return !saved_buf_ && IsQueueEmpty(); +} + +} // namespace media diff --git a/media/filters/audio_renderer_algorithm_ola.h b/media/filters/audio_renderer_algorithm_ola.h new file mode 100644 index 0000000..c46ba134 --- /dev/null +++ b/media/filters/audio_renderer_algorithm_ola.h @@ -0,0 +1,75 @@ +// Copyright (c) 2009 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. + +// AudioRendererAlgorithmOLA [ARAO] is the pitch-preservation implementation of +// AudioRendererAlgorithmBase [ARAB]. For speeds greater than 1.0f, FillBuffer() +// consumes more input data than output data requested and crossfades +// samples to fill |buffer_out|. For speeds less than 1.0f, FillBuffer() +// consumers less input data than output data requested and draws overlapping +// samples from the input data to fill |buffer_out|. As ARAB is thread-unsafe, +// so is ARAO. + +#ifndef MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_OLA_H_ +#define MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_OLA_H_ + +#include "media/filters/audio_renderer_algorithm_base.h" + +namespace media { + +class DataBuffer; + +class AudioRendererAlgorithmOLA : public AudioRendererAlgorithmBase { + public: + AudioRendererAlgorithmOLA(); + virtual ~AudioRendererAlgorithmOLA(); + + // AudioRendererAlgorithmBase implementation + virtual size_t FillBuffer(DataBuffer* buffer_out); + + virtual void FlushBuffers(); + + virtual void set_playback_rate(float new_rate); + + private: + // Advances our input position by |bytes| bytes. + void AdvanceInput(size_t bytes); + + // Aligns |value| to a channel and sample boundary. + void AlignToSampleBoundary(size_t* value); + + // Copies |length| bytes from our buffers to |dest|. Returns how many bytes + // of data were copied. + size_t CopyInput(uint8* dest, size_t length); + + // Crossfades |samples| samples of |dest| with the data in |src|. Assumes + // there is room in |dest| and enough data in |src|. Type is the datatype + // of a data point in the waveform (i.e. uint8, int16, int32, etc). Also, + // sizeof(one sample) == sizeof(Type) * channels. + template <class Type> + void Crossfade(int samples, const Type* src, Type* dest); + + // Returns true if |saved_buf_| is NULL and the queue is empty. + bool IsInputFinished(); + + // Stores the front element of the queue so we can peek at the second one. + scoped_refptr<Buffer> saved_buf_; + + // Remembers the amount of remaining audio data for our |saved_buf_|. + size_t data_offset_; + + // Members for ease of calculation in FillBuffer(). These members are + // based on |playback_rate_|, but are stored seperately so they don't + // have to be recalculated on every call to FillBuffer(). + size_t input_step_; + size_t output_step_; + + // Length for crossfade in bytes. + size_t crossfade_size_; + + DISALLOW_COPY_AND_ASSIGN(AudioRendererAlgorithmOLA); +}; + +} // namespace media + +#endif // MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_OLA_H_ diff --git a/media/media.gyp b/media/media.gyp index 39751e2..1b20982 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -76,6 +76,8 @@ 'filters/audio_renderer_algorithm_base.h', 'filters/audio_renderer_algorithm_default.cc', 'filters/audio_renderer_algorithm_default.h', + 'filters/audio_renderer_algorithm_ola.cc', + 'filters/audio_renderer_algorithm_ola.h', 'filters/audio_renderer_base.cc', 'filters/audio_renderer_base.h', 'filters/audio_renderer_impl.cc', @@ -192,6 +194,16 @@ 'bench/bench.cc', ], }, + { + 'target_name': 'wav_ola_test', + 'type': 'executable', + 'dependencies': [ + 'media', + ], + 'sources': [ + 'tools/wav_ola_test.cc' + ], + }, ], 'conditions': [ ['OS=="win"', { diff --git a/media/tools/wav_ola_test.cc b/media/tools/wav_ola_test.cc new file mode 100644 index 0000000..59c6970 --- /dev/null +++ b/media/tools/wav_ola_test.cc @@ -0,0 +1,153 @@ +// Copyright (c) 2009 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. + +// This application is a test for AudioRendererAlgorithmOLA. It reads in a +// specified wav file (so far only 8, 16 and 32 bit are supported) and uses +// ARAO to scale the playback by a specified rate. Then it outputs the result +// to the specified location. Command line calls should be as follows: +// +// wav_ola_test RATE INFILE OUTFILE + +#include <iostream> +#include <string> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/ref_counted.h" +#include "base/string_util.h" +#include "media/base/data_buffer.h" +#include "media/filters/audio_renderer_algorithm_ola.h" + +using file_util::ScopedFILE; +using media::AudioRendererAlgorithmOLA; +using media::DataBuffer; + +const size_t kDefaultWindowSize = 4096; + +struct WavHeader { + int32 riff; + int32 chunk_size; + char unused0[8]; + int32 subchunk1_size; + int16 audio_format; + int16 channels; + int32 sample_rate; + char unused1[6]; + int16 bit_rate; + char unused2[4]; + int32 subchunk2_size; +}; + +// Dummy class to feed data to OLA algorithm. Necessary to create callback. +class Dummy { + public: + Dummy(FILE* in, AudioRendererAlgorithmOLA* ola) + : input_(in), + ola_(ola) { + } + + void ReadDataForAlg() { + scoped_refptr<DataBuffer> b(new DataBuffer()); + uint8* buf = b->GetWritableData(kDefaultWindowSize); + if (fread(buf, 1, kDefaultWindowSize, input_) > 0) { + ola_->EnqueueBuffer(b.get()); + } + } + + private: + FILE* input_; + AudioRendererAlgorithmOLA* ola_; + + DISALLOW_COPY_AND_ASSIGN(Dummy); +}; + +int main(int argc, const char** argv) { + AudioRendererAlgorithmOLA ola; + CommandLine::Init(argc, argv); + const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); + + std::vector<std::wstring> filenames(cmd_line->GetLooseValues()); + if (filenames.empty()) { + std::cerr << "Usage: alg_test RATE INFILE OUTFILE\n" + << std::endl; + return 1; + } + + // Retrieve command line options. + std::string in_path(WideToUTF8(filenames[1])); + std::string out_path(WideToUTF8(filenames[2])); + double playback_rate = 0.0; + + // Determine speed of rerecord. + if (!StringToDouble(WideToUTF8(filenames[0]), &playback_rate)) + playback_rate = 0.0; + + // Open input file. + ScopedFILE input(file_util::OpenFile(in_path.c_str(), "rb")); + if (!(input.get())) { + LOG(ERROR) << "could not open input"; + return 1; + } + + // Open output file. + ScopedFILE output(file_util::OpenFile(out_path.c_str(), "wb")); + if (!(output.get())) { + LOG(ERROR) << "could not open output"; + return 1; + } + + // Read in header. + WavHeader wav; + if (fread(&wav, sizeof(wav), 1, input.get()) < 1) { + LOG(ERROR) << "could not read WAV header"; + return 1; + } + + // Instantiate dummy class and callback to feed data to |ola|. + Dummy guy(input.get(), &ola); + AudioRendererAlgorithmOLA::RequestReadCallback* cb = + NewCallback(&guy, &Dummy::ReadDataForAlg); + ola.Initialize(wav.channels, + wav.bit_rate, + static_cast<float>(playback_rate), + cb); + + // Print out input format. + std::cout << in_path << "\n" + << "Channels: " << wav.channels << "\n" + << "Sample Rate: " << wav.sample_rate << "\n" + << "Bit Rate: " << wav.bit_rate << "\n" + << "\n" + << "Scaling audio by " << playback_rate << "x..." << std::endl; + + // Write the header back out again. + if (fwrite(&wav, sizeof(wav), 1, output.get()) < 1) { + LOG(ERROR) << "could not write WAV header"; + return 1; + } + + // Create buffer to be filled by |ola|. + scoped_refptr<DataBuffer> buffer(new DataBuffer()); + uint8* buf = buffer->GetWritableData(kDefaultWindowSize); + + // Keep track of bytes written to disk and bytes copied to |b|. + size_t bytes_written = 0; + size_t bytes; + while ((bytes = ola.FillBuffer(buffer.get())) > 0) { + if (fwrite(buf, 1, bytes, output.get()) != bytes) { + LOG(ERROR) << "could not write data after " << bytes_written; + } else { + bytes_written += bytes; + } + } + + // Seek back to the beginning of our output file and update the header. + wav.chunk_size = 36 + bytes_written; + wav.subchunk1_size = 16; + wav.subchunk2_size = bytes_written; + fseek(output.get(), 0, SEEK_SET); + fwrite(&wav, sizeof(wav), 1, output.get()); + + return 0; +} |