Commit 67c68b54 authored by kylep@chromium.org's avatar kylep@chromium.org

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
parent 7b57c3af
// 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
// 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_
......@@ -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"', {
......
// 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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment