diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-25 00:22:33 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-25 00:22:33 +0000 |
commit | b67954bed772b0d67ddcf2424ac5d47f43cbf3f8 (patch) | |
tree | 96aaed54a3eb06c256f3566d782e2bcc8b7e1064 /media | |
parent | 2976fe30ed9e0454b0995456e64a5ef7ae216df8 (diff) | |
download | chromium_src-b67954bed772b0d67ddcf2424ac5d47f43cbf3f8.zip chromium_src-b67954bed772b0d67ddcf2424ac5d47f43cbf3f8.tar.gz chromium_src-b67954bed772b0d67ddcf2424ac5d47f43cbf3f8.tar.bz2 |
Clean up AudioRendererAlgorithmOLA when dealing with very little remaining data.
Since the main OLA loop refuses to process data when there isn't enough to process, it was possible in some situations for OLA to hold on to a few remaining bytes and never be completely "finished". This resulted in the ended event not firing, which leads to looping not working.
The solution was to simply consume all remaining data and replace it with muted audio in the destination buffer. Not the best solution (we end up losing <8ms of audio at the end), but it works and is a safe fix.
I also lowered the OLA threshold to 0.5x since we sound the same as other implementations so why not :P
TEST=media_unittests, ended+looping when rate != 1.0f
BUG=19105,19856
Review URL: http://codereview.chromium.org/174270
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24189 0039d316-1c4b-4281-b951-d872f2087c98
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', |