diff options
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/audio_renderer_algorithm_base.cc | 8 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_base.h | 6 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_ola.cc | 70 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_ola.h | 8 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_ola_unittest.cc | 163 | ||||
-rw-r--r-- | media/media.gyp | 1 |
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', |