summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorkylep@chromium.org <kylep@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-07 00:22:57 +0000
committerkylep@chromium.org <kylep@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-07 00:22:57 +0000
commit67c68b544e429d34a445d20a83d5d3d6993aa7d0 (patch)
tree6143bd0dc0abf28b55445cce9964c9727af2535a /media
parent7b57c3af9a2762d57d4adcea1b5dd7cd5e9c970a (diff)
downloadchromium_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.cc212
-rw-r--r--media/filters/audio_renderer_algorithm_ola.h75
-rw-r--r--media/media.gyp12
-rw-r--r--media/tools/wav_ola_test.cc153
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;
+}