summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/filters/audio_renderer_algorithm_base.cc8
-rw-r--r--media/filters/audio_renderer_algorithm_base.h6
-rw-r--r--media/filters/audio_renderer_algorithm_ola.cc70
-rw-r--r--media/filters/audio_renderer_algorithm_ola.h8
-rw-r--r--media/filters/audio_renderer_algorithm_ola_unittest.cc163
-rw-r--r--media/media.gyp1
6 files changed, 230 insertions, 26 deletions
diff --git a/media/filters/audio_renderer_algorithm_base.cc b/media/filters/audio_renderer_algorithm_base.cc
index 2f3d4be..be28537 100644
--- a/media/filters/audio_renderer_algorithm_base.cc
+++ b/media/filters/audio_renderer_algorithm_base.cc
@@ -82,6 +82,10 @@ bool AudioRendererAlgorithmBase::IsQueueFull() {
return (queue_.SizeInBytes() >= kDefaultMinQueueSizeInBytes);
}
+size_t AudioRendererAlgorithmBase::QueueSize() {
+ return queue_.SizeInBytes();
+}
+
void AudioRendererAlgorithmBase::AdvanceInputPosition(size_t bytes) {
queue_.Consume(bytes);
@@ -93,10 +97,6 @@ size_t AudioRendererAlgorithmBase::CopyFromInput(uint8* dest, size_t bytes) {
return queue_.Copy(dest, bytes);
}
-size_t AudioRendererAlgorithmBase::QueueSize() {
- return queue_.SizeInBytes();
-}
-
int AudioRendererAlgorithmBase::channels() {
return channels_;
}
diff --git a/media/filters/audio_renderer_algorithm_base.h b/media/filters/audio_renderer_algorithm_base.h
index f3e4d78..8822de5 100644
--- a/media/filters/audio_renderer_algorithm_base.h
+++ b/media/filters/audio_renderer_algorithm_base.h
@@ -74,6 +74,9 @@ class AudioRendererAlgorithmBase {
// Returns true if we have enough data
virtual bool IsQueueFull();
+ // Returns the number of bytes left in |queue_|.
+ virtual size_t QueueSize();
+
protected:
// Advances |queue_|'s internal pointer by |bytes|.
void AdvanceInputPosition(size_t bytes);
@@ -82,9 +85,6 @@ class AudioRendererAlgorithmBase {
// bytes successfully copied.
size_t CopyFromInput(uint8* dest, size_t bytes);
- // Returns the number of bytes left in |queue_|.
- virtual size_t QueueSize();
-
// Number of audio channels.
virtual int channels();
diff --git a/media/filters/audio_renderer_algorithm_ola.cc b/media/filters/audio_renderer_algorithm_ola.cc
index 1d22d11..63c3868 100644
--- a/media/filters/audio_renderer_algorithm_ola.cc
+++ b/media/filters/audio_renderer_algorithm_ola.cc
@@ -17,7 +17,7 @@ const double kDefaultCrossfadeLength = 0.008;
// Default mute ranges for fast/slow audio. These rates would sound better
// under a frequency domain algorithm.
-const float kMinRate = 0.75f;
+const float kMinRate = 0.5f;
const float kMaxRate = 4.0f;
AudioRendererAlgorithmOLA::AudioRendererAlgorithmOLA()
@@ -47,27 +47,57 @@ size_t AudioRendererAlgorithmOLA::FillBuffer(uint8* dest, size_t length) {
return dest_written;
}
- // Mute when out of acceptable quality range. Note: This may not play at the
- // speed requested as we can only consume as much data as we have, and audio
- // timestamps drive the pipeline clock.
- if (playback_rate() < kMinRate || playback_rate() > kMaxRate) {
- size_t consume = static_cast<size_t>(length * playback_rate());
- size_t safe_to_consume = std::min(QueueSize(), consume);
- memset(dest, 0, length);
- AlignToSampleBoundary(&safe_to_consume);
- AdvanceInputPosition(safe_to_consume);
- return length;
- }
-
// For other playback rates, OLA with crossfade!
- while (length >= output_step_ + crossfade_size_) {
- // If we don't have enough data to completely finish this loop, quit.
- if (QueueSize() < window_size_)
+ while (true) {
+ // Mute when out of acceptable quality range or when we don't have enough
+ // data to completely finish this loop.
+ //
+ // Note: This may not play at the speed requested as we can only consume as
+ // much data as we have, and audio timestamps drive the pipeline clock.
+ //
+ // Furthermore, we won't end up scaling the very last bit of audio, but
+ // we're talking about <8ms of audio data.
+ if (playback_rate() < kMinRate || playback_rate() > kMaxRate ||
+ QueueSize() < window_size_) {
+ // Calculate the ideal input/output steps based on the size of the
+ // destination buffer.
+ size_t input_step = static_cast<size_t>(ceil(
+ static_cast<float>(length * playback_rate())));
+ size_t output_step = length;
+
+ // If the ideal size is too big, recalculate based on how much is left in
+ // the queue.
+ if (input_step > QueueSize()) {
+ input_step = QueueSize();
+ output_step = static_cast<size_t>(ceil(
+ static_cast<float>(input_step / playback_rate())));
+ }
+
+ // Stay aligned and sanity check before writing out zeros.
+ AlignToSampleBoundary(&input_step);
+ AlignToSampleBoundary(&output_step);
+ DCHECK_LE(output_step, length);
+ if (output_step > length) {
+ LOG(ERROR) << "OLA: output_step (" << output_step << ") calculated to "
+ << "be larger than destination length (" << length << ")";
+ output_step = length;
+ }
+
+ memset(dest, 0, output_step);
+ AdvanceInputPosition(input_step);
+ dest_written += output_step;
break;
+ }
+
+ // Break if we don't have enough room left in our buffer to do a full
+ // OLA iteration.
+ if (length < (output_step_ + crossfade_size_)) {
+ break;
+ }
// 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.
+ // our tally of remaining requested.
size_t copied = CopyFromInput(dest, output_step_ + crossfade_size_);
dest_written += copied;
length -= copied;
@@ -120,7 +150,7 @@ void AudioRendererAlgorithmOLA::set_playback_rate(float new_rate) {
* channels()
* kDefaultWindowLength);
- // Adjusting step sizes to accomodate requested playback rate.
+ // Adjusting step sizes to accommodate requested playback rate.
if (playback_rate() > 1.0f) {
input_step_ = window_size_;
output_step_ = static_cast<size_t>(ceil(
@@ -139,6 +169,10 @@ void AudioRendererAlgorithmOLA::set_playback_rate(float new_rate) {
* channels()
* kDefaultCrossfadeLength);
AlignToSampleBoundary(&crossfade_size_);
+ if (crossfade_size_ > std::min(input_step_, output_step_)) {
+ crossfade_size_ = 0;
+ return;
+ }
// To keep true to playback rate, modify the steps.
input_step_ -= crossfade_size_;
diff --git a/media/filters/audio_renderer_algorithm_ola.h b/media/filters/audio_renderer_algorithm_ola.h
index eb23a9a..5987ce8 100644
--- a/media/filters/audio_renderer_algorithm_ola.h
+++ b/media/filters/audio_renderer_algorithm_ola.h
@@ -15,6 +15,7 @@
#define MEDIA_FILTERS_AUDIO_RENDERER_ALGORITHM_OLA_H_
#include "media/filters/audio_renderer_algorithm_base.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
namespace media {
@@ -29,6 +30,11 @@ class AudioRendererAlgorithmOLA : public AudioRendererAlgorithmBase {
virtual void set_playback_rate(float new_rate);
private:
+ FRIEND_TEST(AudioRendererAlgorithmOLATest, FillBuffer_NormalRate);
+ FRIEND_TEST(AudioRendererAlgorithmOLATest, FillBuffer_DoubleRate);
+ FRIEND_TEST(AudioRendererAlgorithmOLATest, FillBuffer_HalfRate);
+ FRIEND_TEST(AudioRendererAlgorithmOLATest, FillBuffer_QuarterRate);
+
// Aligns |value| to a channel and sample boundary.
void AlignToSampleBoundary(size_t* value);
@@ -40,7 +46,7 @@ class AudioRendererAlgorithmOLA : public AudioRendererAlgorithmBase {
void Crossfade(int samples, const Type* src, Type* dest);
// 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
+ // on |playback_rate_|, but are stored separately so they don't have to be
// recalculated on every call to FillBuffer().
size_t input_step_;
size_t output_step_;
diff --git a/media/filters/audio_renderer_algorithm_ola_unittest.cc b/media/filters/audio_renderer_algorithm_ola_unittest.cc
new file mode 100644
index 0000000..f4d3504
--- /dev/null
+++ b/media/filters/audio_renderer_algorithm_ola_unittest.cc
@@ -0,0 +1,163 @@
+// 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.
+//
+// The format of these tests are to enqueue a known amount of data and then
+// request the exact amount we expect in order to dequeue the known amount of
+// data. This ensures that for any rate we are consuming input data at the
+// correct rate. We always pass in a very large destination buffer with the
+// expectation that FillBuffer() will fill as much as it can but no more.
+
+#include "media/base/data_buffer.h"
+#include "media/filters/audio_renderer_algorithm_ola.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AnyNumber;
+
+namespace media {
+
+class MockDataProvider {
+ public:
+ MockDataProvider() {}
+
+ MOCK_METHOD0(Read, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockDataProvider);
+};
+
+static const int kChannels = 1;
+static const int kSampleRate = 1000;
+static const int kSampleBits = 8;
+
+TEST(AudioRendererAlgorithmOLATest, FillBuffer_NormalRate) {
+ // When playback rate == 1.0f: straight copy of whatever is in |queue_|.
+ MockDataProvider mock;
+ AudioRendererAlgorithmOLA algorithm;
+ algorithm.Initialize(kChannels, kSampleRate, kSampleBits, 1.0f,
+ NewCallback(&mock, &MockDataProvider::Read));
+
+ // We won't reply to any read requests.
+ EXPECT_CALL(mock, Read()).Times(AnyNumber());
+
+ // Enqueue a buffer of any size since it doesn't matter.
+ const size_t kDataSize = 1024;
+ algorithm.EnqueueBuffer(new DataBuffer(new uint8[kDataSize], kDataSize));
+ EXPECT_EQ(kDataSize, algorithm.QueueSize());
+
+ // Read the same sized amount.
+ scoped_array<uint8> data(new uint8[kDataSize]);
+ EXPECT_EQ(kDataSize, algorithm.FillBuffer(data.get(), kDataSize));
+ EXPECT_EQ(0u, algorithm.QueueSize());
+}
+
+TEST(AudioRendererAlgorithmOLATest, FillBuffer_DoubleRate) {
+ // When playback rate > 1.0f: input is read faster than output is written.
+ MockDataProvider mock;
+ AudioRendererAlgorithmOLA algorithm;
+ algorithm.Initialize(kChannels, kSampleRate, kSampleBits, 2.0f,
+ NewCallback(&mock, &MockDataProvider::Read));
+
+ // We won't reply to any read requests.
+ EXPECT_CALL(mock, Read()).Times(AnyNumber());
+
+ // First parameter is the input buffer size, second parameter is how much data
+ // we expect to consume in order to have no data left in the |algorithm|.
+ //
+ // For rate == 0.5f, reading half the input size should consume all enqueued
+ // data.
+ const size_t kBufferSize = 16 * 1024;
+ scoped_array<uint8> data(new uint8[kBufferSize]);
+ const size_t kTestData[][2] = {
+ { algorithm.window_size_, algorithm.window_size_ / 2},
+ { algorithm.window_size_ / 2, algorithm.window_size_ / 4},
+ { 4u, 2u },
+ { 0u, 0u },
+ };
+
+ for (size_t i = 0u; i < arraysize(kTestData); ++i) {
+ const size_t kDataSize = kTestData[i][0];
+ algorithm.EnqueueBuffer(new DataBuffer(new uint8[kDataSize], kDataSize));
+ EXPECT_EQ(kDataSize, algorithm.QueueSize());
+
+ const size_t kExpectedSize = kTestData[i][1];
+ ASSERT_LE(kExpectedSize, kBufferSize);
+ EXPECT_EQ(kExpectedSize, algorithm.FillBuffer(data.get(), kBufferSize));
+ EXPECT_EQ(0u, algorithm.QueueSize());
+ }
+}
+
+TEST(AudioRendererAlgorithmOLATest, FillBuffer_HalfRate) {
+ // When playback rate < 1.0f: input is read slower than output is written.
+ MockDataProvider mock;
+ AudioRendererAlgorithmOLA algorithm;
+ algorithm.Initialize(kChannels, kSampleRate, kSampleBits, 0.5f,
+ NewCallback(&mock, &MockDataProvider::Read));
+
+ // We won't reply to any read requests.
+ EXPECT_CALL(mock, Read()).Times(AnyNumber());
+
+ // First parameter is the input buffer size, second parameter is how much data
+ // we expect to consume in order to have no data left in the |algorithm|.
+ //
+ // For rate == 0.5f, reading double the input size should consume all enqueued
+ // data.
+ const size_t kBufferSize = 16 * 1024;
+ scoped_array<uint8> data(new uint8[kBufferSize]);
+ const size_t kTestData[][2] = {
+ { algorithm.window_size_, algorithm.window_size_ * 2 },
+ { algorithm.window_size_ / 2, algorithm.window_size_ },
+ { 2u, 4u },
+ { 0u, 0u },
+ };
+
+ for (size_t i = 0u; i < arraysize(kTestData); ++i) {
+ const size_t kDataSize = kTestData[i][0];
+ algorithm.EnqueueBuffer(new DataBuffer(new uint8[kDataSize], kDataSize));
+ EXPECT_EQ(kDataSize, algorithm.QueueSize());
+
+ const size_t kExpectedSize = kTestData[i][1];
+ ASSERT_LE(kExpectedSize, kBufferSize);
+ EXPECT_EQ(kExpectedSize, algorithm.FillBuffer(data.get(), kBufferSize));
+ EXPECT_EQ(0u, algorithm.QueueSize());
+ }
+}
+
+TEST(AudioRendererAlgorithmOLATest, FillBuffer_QuarterRate) {
+ // When playback rate is very low the audio is simply muted.
+ MockDataProvider mock;
+ AudioRendererAlgorithmOLA algorithm;
+ algorithm.Initialize(kChannels, kSampleRate, kSampleBits, 0.25f,
+ NewCallback(&mock, &MockDataProvider::Read));
+
+ // We won't reply to any read requests.
+ EXPECT_CALL(mock, Read()).Times(AnyNumber());
+
+ // First parameter is the input buffer size, second parameter is how much data
+ // we expect to consume in order to have no data left in the |algorithm|.
+ //
+ // For rate == 0.25f, reading four times the input size should consume all
+ // enqueued data but without executing OLA.
+ const size_t kBufferSize = 16 * 1024;
+ scoped_array<uint8> data(new uint8[kBufferSize]);
+ const size_t kTestData[][2] = {
+ { algorithm.window_size_, algorithm.window_size_ * 4},
+ { algorithm.window_size_ / 2, algorithm.window_size_ * 2},
+ { 1u, 4u },
+ { 0u, 0u },
+ };
+
+ for (size_t i = 0u; i < arraysize(kTestData); ++i) {
+ const size_t kDataSize = kTestData[i][0];
+ algorithm.EnqueueBuffer(new DataBuffer(new uint8[kDataSize], kDataSize));
+ EXPECT_EQ(kDataSize, algorithm.QueueSize());
+
+ const size_t kExpectedSize = kTestData[i][1];
+ ASSERT_LE(kExpectedSize, kBufferSize);
+ EXPECT_EQ(kExpectedSize, algorithm.FillBuffer(data.get(), kBufferSize));
+ EXPECT_EQ(0u, algorithm.QueueSize());
+ }
+}
+
+} // namespace media
diff --git a/media/media.gyp b/media/media.gyp
index ece65c7..23140b0 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -174,6 +174,7 @@
'base/seekable_buffer_unittest.cc',
'base/video_frame_impl_unittest.cc',
'base/yuv_convert_unittest.cc',
+ 'filters/audio_renderer_algorithm_ola_unittest.cc',
'filters/audio_renderer_base_unittest.cc',
'filters/ffmpeg_demuxer_unittest.cc',
'filters/ffmpeg_glue_unittest.cc',