summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorvrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-06 00:48:37 +0000
committervrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-06 00:48:37 +0000
commite4eccfdae7c8b34056e7b61450b0be30825fc5d2 (patch)
treef5090ba1d0d83f9df413ddf370eb12acbec8e7d2 /media
parent8e248501d06d6536bdc8f37d20d8c600f848485b (diff)
downloadchromium_src-e4eccfdae7c8b34056e7b61450b0be30825fc5d2.zip
chromium_src-e4eccfdae7c8b34056e7b61450b0be30825fc5d2.tar.gz
chromium_src-e4eccfdae7c8b34056e7b61450b0be30825fc5d2.tar.bz2
Fix muted audio when playback rate != 1.0 or 0.0
Rewrites the logic in AudioRendererAlgorithmBase to be able to output audio at any point of a sped-up/slowed down window, instead of only outputting audio in full multiples of windows. BUG=108239 TEST=media_unittests, manual testing on video test matrix Review URL: http://codereview.chromium.org/9395057 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@125052 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/audio/audio_util.cc72
-rw-r--r--media/audio/audio_util.h5
-rw-r--r--media/base/seekable_buffer.cc53
-rw-r--r--media/base/seekable_buffer.h9
-rw-r--r--media/filters/audio_renderer_algorithm_base.cc391
-rw-r--r--media/filters/audio_renderer_algorithm_base.h110
-rw-r--r--media/filters/audio_renderer_algorithm_base_unittest.cc374
-rw-r--r--media/filters/audio_renderer_base.cc44
-rw-r--r--media/filters/audio_renderer_base.h11
-rw-r--r--media/filters/audio_renderer_base_unittest.cc12
-rw-r--r--media/filters/null_audio_renderer.cc20
-rw-r--r--media/filters/null_audio_renderer.h2
12 files changed, 722 insertions, 381 deletions
diff --git a/media/audio/audio_util.cc b/media/audio/audio_util.cc
index 8caced5..0ce05fa 100644
--- a/media/audio/audio_util.cc
+++ b/media/audio/audio_util.cc
@@ -51,30 +51,6 @@ static void AdjustVolume(Format* buf_out,
}
}
-// Type is the datatype of a data point in the waveform (i.e. uint8, int16,
-// int32, etc).
-template <class Type>
-static void DoCrossfade(int bytes_to_crossfade, int number_of_channels,
- int bytes_per_channel, const Type* src, Type* dest) {
- DCHECK_EQ(sizeof(Type), static_cast<size_t>(bytes_per_channel));
- int number_of_samples =
- bytes_to_crossfade / (bytes_per_channel * number_of_channels);
-
- const Type* dest_end = dest + number_of_samples * number_of_channels;
- const Type* src_end = src + number_of_samples * number_of_channels;
-
- for (int i = 0; i < number_of_samples; ++i) {
- double crossfade_ratio = static_cast<double>(i) / number_of_samples;
- for (int j = 0; j < number_of_channels; ++j) {
- DCHECK_LT(dest, dest_end);
- DCHECK_LT(src, src_end);
- *dest = (*dest) * (1.0 - crossfade_ratio) + (*src) * crossfade_ratio;
- ++src;
- ++dest;
- }
- }
-}
-
static const int kChannel_L = 0;
static const int kChannel_R = 1;
static const int kChannel_C = 2;
@@ -415,52 +391,4 @@ bool IsWASAPISupported() {
#endif
-void Crossfade(int bytes_to_crossfade, int number_of_channels,
- int bytes_per_channel, const uint8* src, uint8* dest) {
- // TODO(vrk): The type punning below is no good!
- switch (bytes_per_channel) {
- case 4:
- DoCrossfade(bytes_to_crossfade, number_of_channels, bytes_per_channel,
- reinterpret_cast<const int32*>(src),
- reinterpret_cast<int32*>(dest));
- break;
- case 2:
- DoCrossfade(bytes_to_crossfade, number_of_channels, bytes_per_channel,
- reinterpret_cast<const int16*>(src),
- reinterpret_cast<int16*>(dest));
- break;
- case 1:
- DoCrossfade(bytes_to_crossfade, number_of_channels, bytes_per_channel,
- src, dest);
- break;
- default:
- NOTREACHED() << "Unsupported audio bit depth in crossfade.";
- }
-}
-
-// The minimum number of samples in a hardware packet.
-// This value is selected so that we can handle down to 5khz sample rate.
-static const int kMinSamplesPerHardwarePacket = 1024;
-
-// The maximum number of samples in a hardware packet.
-// This value is selected so that we can handle up to 192khz sample rate.
-static const int kMaxSamplesPerHardwarePacket = 64 * 1024;
-
-// This constant governs the hardware audio buffer size, this value should be
-// chosen carefully.
-// This value is selected so that we have 8192 samples for 48khz streams.
-static const int kMillisecondsPerHardwarePacket = 170;
-
-uint32 SelectSamplesPerPacket(int sample_rate) {
- // Select the number of samples that can provide at least
- // |kMillisecondsPerHardwarePacket| worth of audio data.
- int samples = kMinSamplesPerHardwarePacket;
- while (samples <= kMaxSamplesPerHardwarePacket &&
- samples * base::Time::kMillisecondsPerSecond <
- sample_rate * kMillisecondsPerHardwarePacket) {
- samples *= 2;
- }
- return samples;
-}
-
} // namespace media
diff --git a/media/audio/audio_util.h b/media/audio/audio_util.h
index 51d707a..e2f57fa 100644
--- a/media/audio/audio_util.h
+++ b/media/audio/audio_util.h
@@ -118,11 +118,6 @@ MEDIA_EXPORT bool IsWASAPISupported();
#endif // defined(OS_WIN)
-// Crossfades |bytes_to_crossfade| bytes of data in |dest| with the
-// data in |src|. Assumes there is room in |dest| and enough data in |src|.
-MEDIA_EXPORT void Crossfade(int bytes_to_crossfade, int number_of_channels,
- int bytes_per_channel, const uint8* src,
- uint8* dest);
} // namespace media
#endif // MEDIA_AUDIO_AUDIO_UTIL_H_
diff --git a/media/base/seekable_buffer.cc b/media/base/seekable_buffer.cc
index b9ad7a4..49df43f 100644
--- a/media/base/seekable_buffer.cc
+++ b/media/base/seekable_buffer.cc
@@ -36,12 +36,12 @@ void SeekableBuffer::Clear() {
size_t SeekableBuffer::Read(uint8* data, size_t size) {
DCHECK(data);
- return InternalRead(data, size, true);
+ return InternalRead(data, size, true, 0);
}
-size_t SeekableBuffer::Peek(uint8* data, size_t size) {
+size_t SeekableBuffer::Peek(uint8* data, size_t size, size_t forward_offset) {
DCHECK(data);
- return InternalRead(data, size, false);
+ return InternalRead(data, size, false, forward_offset);
}
bool SeekableBuffer::GetCurrentChunk(const uint8** data, size_t* size) const {
@@ -113,7 +113,7 @@ bool SeekableBuffer::SeekForward(size_t size) {
return false;
// Do a read of |size| bytes.
- size_t taken = InternalRead(NULL, size, true);
+ size_t taken = InternalRead(NULL, size, true, 0);
DCHECK_EQ(taken, size);
return true;
}
@@ -183,13 +183,15 @@ void SeekableBuffer::EvictBackwardBuffers() {
}
size_t SeekableBuffer::InternalRead(uint8* data, size_t size,
- bool advance_position) {
+ bool advance_position,
+ size_t forward_offset) {
// Counts how many bytes are actually read from the buffer queue.
size_t taken = 0;
BufferQueue::iterator current_buffer = current_buffer_;
size_t current_buffer_offset = current_buffer_offset_;
+ size_t bytes_to_skip = forward_offset;
while (taken < size) {
// |current_buffer| is valid since the first time this buffer is appended
// with data.
@@ -198,22 +200,31 @@ size_t SeekableBuffer::InternalRead(uint8* data, size_t size,
scoped_refptr<Buffer> buffer = *current_buffer;
- // Find the right amount to copy from the current buffer referenced by
- // |buffer|. We shall copy no more than |size| bytes in total and each
- // single step copied no more than the current buffer size.
- size_t copied = std::min(size - taken,
- buffer->GetDataSize() - current_buffer_offset);
-
- // |data| is NULL if we are seeking forward, so there's no need to copy.
- if (data)
- memcpy(data + taken, buffer->GetData() + current_buffer_offset, copied);
-
- // Increase total number of bytes copied, which regulates when to end this
- // loop.
- taken += copied;
-
- // We have read |copied| bytes from the current buffer. Advances the offset.
- current_buffer_offset += copied;
+ size_t remaining_bytes_in_buffer =
+ buffer->GetDataSize() - current_buffer_offset;
+
+ if (bytes_to_skip == 0) {
+ // Find the right amount to copy from the current buffer referenced by
+ // |buffer|. We shall copy no more than |size| bytes in total and each
+ // single step copied no more than the current buffer size.
+ size_t copied = std::min(size - taken, remaining_bytes_in_buffer);
+
+ // |data| is NULL if we are seeking forward, so there's no need to copy.
+ if (data)
+ memcpy(data + taken, buffer->GetData() + current_buffer_offset, copied);
+
+ // Increase total number of bytes copied, which regulates when to end this
+ // loop.
+ taken += copied;
+
+ // We have read |copied| bytes from the current buffer. Advances the
+ // offset.
+ current_buffer_offset += copied;
+ } else {
+ size_t skipped = std::min(remaining_bytes_in_buffer, bytes_to_skip);
+ current_buffer_offset += skipped;
+ bytes_to_skip -= skipped;
+ }
// The buffer has been consumed.
if (current_buffer_offset == buffer->GetDataSize()) {
diff --git a/media/base/seekable_buffer.h b/media/base/seekable_buffer.h
index 56a7ceb..eb7ba84 100644
--- a/media/base/seekable_buffer.h
+++ b/media/base/seekable_buffer.h
@@ -60,8 +60,10 @@ class MEDIA_EXPORT SeekableBuffer {
size_t Read(uint8* data, size_t size);
// Copies up to |size| bytes from current position to |data|. Returns
- // number of bytes copied. Doesn't advance current position.
- size_t Peek(uint8* data, size_t size);
+ // number of bytes copied. Doesn't advance current position. Optionally
+ // starts at a |forward_offset| from current position.
+ size_t Peek(uint8* data, size_t size) { return Peek(data, size, 0); }
+ size_t Peek(uint8* data, size_t size, size_t forward_offset);
// Returns pointer to the current chunk of data that is being consumed.
// If there is no data left in the buffer false is returned, otherwise
@@ -137,7 +139,8 @@ class MEDIA_EXPORT SeekableBuffer {
// of bytes read. The current read position will be moved forward by the
// number of bytes read. If |data| is NULL, only the current read position
// will advance but no data will be copied.
- size_t InternalRead(uint8* data, size_t size, bool advance_position);
+ size_t InternalRead(
+ uint8* data, size_t size, bool advance_position, size_t forward_offset);
// A helper method that moves the current read position forward by |size|
// bytes.
diff --git a/media/filters/audio_renderer_algorithm_base.cc b/media/filters/audio_renderer_algorithm_base.cc
index 29bafed..7495a33 100644
--- a/media/filters/audio_renderer_algorithm_base.cc
+++ b/media/filters/audio_renderer_algorithm_base.cc
@@ -41,7 +41,12 @@ AudioRendererAlgorithmBase::AudioRendererAlgorithmBase()
bytes_per_channel_(0),
playback_rate_(0.0f),
audio_buffer_(0, kStartingBufferSizeInBytes),
- crossfade_size_(0),
+ bytes_in_crossfade_(0),
+ bytes_per_frame_(0),
+ index_into_window_(0),
+ crossfade_frame_number_(0),
+ muted_(false),
+ needs_more_data_(false),
window_size_(0) {
}
@@ -84,134 +89,318 @@ void AudioRendererAlgorithmBase::Initialize(
channels_ = channels;
samples_per_second_ = samples_per_second;
bytes_per_channel_ = bits_per_channel / 8;
+ bytes_per_frame_ = bytes_per_channel_ * channels_;
request_read_cb_ = callback;
SetPlaybackRate(initial_playback_rate);
window_size_ =
samples_per_second_ * bytes_per_channel_ * channels_ * kWindowDuration;
- AlignToSampleBoundary(&window_size_);
+ AlignToFrameBoundary(&window_size_);
- crossfade_size_ =
+ bytes_in_crossfade_ =
samples_per_second_ * bytes_per_channel_ * channels_ * kCrossfadeDuration;
- AlignToSampleBoundary(&crossfade_size_);
+ AlignToFrameBoundary(&bytes_in_crossfade_);
+
+ crossfade_buffer_.reset(new uint8[bytes_in_crossfade_]);
}
-uint32 AudioRendererAlgorithmBase::FillBuffer(uint8* dest, uint32 length) {
- if (IsQueueEmpty() || playback_rate_ == 0.0f)
+uint32 AudioRendererAlgorithmBase::FillBuffer(
+ uint8* dest, uint32 requested_frames) {
+ DCHECK_NE(bytes_per_frame_, 0u);
+
+ if (playback_rate_ == 0.0f)
return 0;
- // Handle the simple case of normal playback.
- if (playback_rate_ == 1.0f) {
- uint32 bytes_written =
- CopyFromAudioBuffer(dest, std::min(length, bytes_buffered()));
- AdvanceBufferPosition(bytes_written);
- return bytes_written;
+ uint32 total_frames_rendered = 0;
+ uint8* output_ptr = dest;
+ while (total_frames_rendered < requested_frames) {
+ if (index_into_window_ == window_size_)
+ ResetWindow();
+
+ bool rendered_frame = true;
+ if (playback_rate_ > 1.0)
+ rendered_frame = OutputFasterPlayback(output_ptr);
+ else if (playback_rate_ < 1.0)
+ rendered_frame = OutputSlowerPlayback(output_ptr);
+ else
+ rendered_frame = OutputNormalPlayback(output_ptr);
+
+ if (!rendered_frame) {
+ needs_more_data_ = true;
+ break;
+ }
+
+ output_ptr += bytes_per_frame_;
+ total_frames_rendered++;
}
+ return total_frames_rendered;
+}
+
+void AudioRendererAlgorithmBase::ResetWindow() {
+ DCHECK_LE(index_into_window_, window_size_);
+ index_into_window_ = 0;
+ crossfade_frame_number_ = 0;
+}
- // Output muted data when out of acceptable quality range.
- if (playback_rate_ < kMinPlaybackRate || playback_rate_ > kMaxPlaybackRate)
- return MuteBuffer(dest, length);
+bool AudioRendererAlgorithmBase::OutputFasterPlayback(uint8* dest) {
+ DCHECK_LT(index_into_window_, window_size_);
+ DCHECK_GT(playback_rate_, 1.0);
+ if (audio_buffer_.forward_bytes() < bytes_per_frame_)
+ return false;
+
+ // The audio data is output in a series of windows. For sped-up playback,
+ // the window is comprised of the following phases:
+ //
+ // a) Output raw data.
+ // b) Save bytes for crossfade in |crossfade_buffer_|.
+ // c) Drop data.
+ // d) Output crossfaded audio leading up to the next window.
+ //
+ // The duration of each phase is computed below based on the |window_size_|
+ // and |playback_rate_|.
uint32 input_step = window_size_;
- uint32 output_step = window_size_;
+ uint32 output_step = ceil(window_size_ / playback_rate_);
+ AlignToFrameBoundary(&output_step);
+ DCHECK_GT(input_step, output_step);
+
+ uint32 bytes_to_crossfade = bytes_in_crossfade_;
+ if (muted_ || bytes_to_crossfade > output_step)
+ bytes_to_crossfade = 0;
+
+ // This is the index of the end of phase a, beginning of phase b.
+ uint32 outtro_crossfade_begin = output_step - bytes_to_crossfade;
+
+ // This is the index of the end of phase b, beginning of phase c.
+ uint32 outtro_crossfade_end = output_step;
+
+ // This is the index of the end of phase c, beginning of phase d.
+ // This phase continues until |index_into_window_| reaches |window_size_|, at
+ // which point the window restarts.
+ uint32 intro_crossfade_begin = input_step - bytes_to_crossfade;
+
+ // a) Output a raw frame if we haven't reached the crossfade section.
+ if (index_into_window_ < outtro_crossfade_begin) {
+ CopyWithAdvance(dest);
+ index_into_window_ += bytes_per_frame_;
+ return true;
+ }
- if (playback_rate_ > 1.0f) {
- // Playback is faster than normal; need to squish output!
- output_step = ceil(window_size_ / playback_rate_);
- } else {
- // Playback is slower than normal; need to stretch input!
- input_step = ceil(window_size_ * playback_rate_);
+ // b) Save outtro crossfade frames into intermediate buffer, but do not output
+ // anything to |dest|.
+ while (index_into_window_ < outtro_crossfade_end) {
+ if (audio_buffer_.forward_bytes() < bytes_per_frame_)
+ return false;
+
+ // This phase only applies if there are bytes to crossfade.
+ DCHECK_GT(bytes_to_crossfade, 0u);
+ uint8* place_to_copy = crossfade_buffer_.get() +
+ (index_into_window_ - outtro_crossfade_begin);
+ CopyWithAdvance(place_to_copy);
+ index_into_window_ += bytes_per_frame_;
}
- AlignToSampleBoundary(&input_step);
- AlignToSampleBoundary(&output_step);
- DCHECK_LE(crossfade_size_, input_step);
- DCHECK_LE(crossfade_size_, output_step);
+ // c) Drop frames until we reach the intro crossfade section.
+ while (index_into_window_ < intro_crossfade_begin) {
+ if (audio_buffer_.forward_bytes() < bytes_per_frame_)
+ return false;
- uint32 bytes_written = 0;
- uint32 bytes_left_to_output = length;
- uint8* output_ptr = dest;
+ DropFrame();
+ index_into_window_ += bytes_per_frame_;
+ }
- // TODO(vrk): The while loop and if test below are lame! We are requiring the
- // client to provide us with enough data to output only complete crossfaded
- // windows. Instead, we should output as much data as we can, and add state to
- // keep track of what point in the crossfade we are at.
- // This is also the cause of crbug.com/108239.
- while (bytes_left_to_output >= output_step) {
- // If there is not enough data buffered to complete an iteration of the
- // loop, mute the remaining and break.
- if (bytes_buffered() < window_size_) {
- bytes_written += MuteBuffer(output_ptr, bytes_left_to_output);
- break;
- }
+ // Return if we have run out of data after Phase c).
+ if (audio_buffer_.forward_bytes() < bytes_per_frame_)
+ return false;
- // Copy |output_step| bytes into destination buffer.
- uint32 copied = CopyFromAudioBuffer(output_ptr, output_step);
- DCHECK_EQ(copied, output_step);
- output_ptr += output_step;
- bytes_written += copied;
- bytes_left_to_output -= copied;
-
- // Copy the |crossfade_size_| bytes leading up to the next window that will
- // be played into an intermediate buffer. This will be used to crossfade
- // from the current window to the next.
- AdvanceBufferPosition(input_step - crossfade_size_);
- scoped_array<uint8> next_window_intro(new uint8[crossfade_size_]);
- uint32 bytes_copied =
- CopyFromAudioBuffer(next_window_intro.get(), crossfade_size_);
- DCHECK_EQ(bytes_copied, crossfade_size_);
- AdvanceBufferPosition(crossfade_size_);
-
- // Prepare pointers to end of the current window and the start of the next
- // window.
- uint8* start_of_outro = output_ptr - crossfade_size_;
- const uint8* start_of_intro = next_window_intro.get();
-
- // Do crossfade!
- Crossfade(crossfade_size_, channels_, bytes_per_channel_,
- start_of_intro, start_of_outro);
+ // Phase d) doesn't apply if there are no bytes to crossfade.
+ if (bytes_to_crossfade == 0) {
+ DCHECK_EQ(index_into_window_, window_size_);
+ return false;
}
- return bytes_written;
+ // d) Crossfade and output a frame.
+ DCHECK_LT(index_into_window_, window_size_);
+ uint32 offset_into_buffer = index_into_window_ - intro_crossfade_begin;
+ memcpy(dest, crossfade_buffer_.get() + offset_into_buffer,
+ bytes_per_frame_);
+ scoped_array<uint8> intro_frame_ptr(new uint8[bytes_per_frame_]);
+ audio_buffer_.Read(intro_frame_ptr.get(), bytes_per_frame_);
+ OutputCrossfadedFrame(dest, intro_frame_ptr.get());
+ index_into_window_ += bytes_per_frame_;
+ return true;
}
-uint32 AudioRendererAlgorithmBase::MuteBuffer(uint8* dest, uint32 length) {
+bool AudioRendererAlgorithmBase::OutputSlowerPlayback(uint8* dest) {
+ DCHECK_LT(index_into_window_, window_size_);
+ DCHECK_LT(playback_rate_, 1.0);
DCHECK_NE(playback_rate_, 0.0);
- // 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 (audio_buffer_.forward_bytes() < bytes_per_frame_)
+ return false;
+
+ // The audio data is output in a series of windows. For slowed down playback,
+ // the window is comprised of the following phases:
+ //
+ // a) Output raw data.
+ // b) Output and save bytes for crossfade in |crossfade_buffer_|.
+ // c) Output* raw data.
+ // d) Output* crossfaded audio leading up to the next window.
//
- // Furthermore, we won't end up scaling the very last bit of audio, but
- // we're talking about <8ms of audio data.
-
- // Cap the |input_step| by the amount of bytes buffered.
- uint32 input_step =
- std::min(static_cast<uint32>(length * playback_rate_), bytes_buffered());
- uint32 output_step = input_step / playback_rate_;
- 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;
+ // * Phases c) and d) do not progress |audio_buffer_|'s cursor so that the
+ // |audio_buffer_|'s cursor is in the correct place for the next window.
+ //
+ // The duration of each phase is computed below based on the |window_size_|
+ // and |playback_rate_|.
+ uint32 input_step = ceil(window_size_ * playback_rate_);
+ AlignToFrameBoundary(&input_step);
+ uint32 output_step = window_size_;
+ DCHECK_LT(input_step, output_step);
+
+ uint32 bytes_to_crossfade = bytes_in_crossfade_;
+ if (muted_ || bytes_to_crossfade > input_step)
+ bytes_to_crossfade = 0;
+
+ // This is the index of the end of phase a, beginning of phase b.
+ uint32 intro_crossfade_begin = input_step - bytes_to_crossfade;
+
+ // This is the index of the end of phase b, beginning of phase c.
+ uint32 intro_crossfade_end = input_step;
+
+ // This is the index of the end of phase c, beginning of phase d.
+ // This phase continues until |index_into_window_| reaches |window_size_|, at
+ // which point the window restarts.
+ uint32 outtro_crossfade_begin = output_step - bytes_to_crossfade;
+
+ // a) Output a raw frame.
+ if (index_into_window_ < intro_crossfade_begin) {
+ CopyWithAdvance(dest);
+ index_into_window_ += bytes_per_frame_;
+ return true;
+ }
+
+ // b) Save the raw frame for the intro crossfade section, then output the
+ // frame to |dest|.
+ if (index_into_window_ < intro_crossfade_end) {
+ uint32 offset = index_into_window_ - intro_crossfade_begin;
+ uint8* place_to_copy = crossfade_buffer_.get() + offset;
+ CopyWithoutAdvance(place_to_copy);
+ CopyWithAdvance(dest);
+ index_into_window_ += bytes_per_frame_;
+ return true;
}
- memset(dest, 0, output_step);
- AdvanceBufferPosition(input_step);
- return output_step;
+ uint32 audio_buffer_offset = index_into_window_ - intro_crossfade_end;
+
+ if (audio_buffer_.forward_bytes() < audio_buffer_offset + bytes_per_frame_)
+ return false;
+
+ // c) Output a raw frame into |dest| without advancing the |audio_buffer_|
+ // cursor. See function-level comment.
+ DCHECK_GE(index_into_window_, intro_crossfade_end);
+ CopyWithoutAdvance(dest, audio_buffer_offset);
+
+ // d) Crossfade the next frame of |crossfade_buffer_| into |dest| if we've
+ // reached the outtro crossfade section of the window.
+ if (index_into_window_ >= outtro_crossfade_begin) {
+ uint32 offset_into_crossfade_buffer =
+ index_into_window_ - outtro_crossfade_begin;
+ uint8* intro_frame_ptr =
+ crossfade_buffer_.get() + offset_into_crossfade_buffer;
+ OutputCrossfadedFrame(dest, intro_frame_ptr);
+ }
+
+ index_into_window_ += bytes_per_frame_;
+ return true;
+}
+
+bool AudioRendererAlgorithmBase::OutputNormalPlayback(uint8* dest) {
+ if (audio_buffer_.forward_bytes() >= bytes_per_frame_) {
+ CopyWithAdvance(dest);
+ index_into_window_ += bytes_per_frame_;
+ return true;
+ }
+ return false;
+}
+
+void AudioRendererAlgorithmBase::CopyWithAdvance(uint8* dest) {
+ CopyWithoutAdvance(dest);
+ DropFrame();
+}
+
+void AudioRendererAlgorithmBase::CopyWithoutAdvance(uint8* dest) {
+ CopyWithoutAdvance(dest, 0);
+}
+
+void AudioRendererAlgorithmBase::CopyWithoutAdvance(
+ uint8* dest, uint32 offset) {
+ if (muted_) {
+ memset(dest, 0, bytes_per_frame_);
+ return;
+ }
+ uint32 copied = audio_buffer_.Peek(dest, bytes_per_frame_, offset);
+ DCHECK_EQ(bytes_per_frame_, copied);
+}
+
+void AudioRendererAlgorithmBase::DropFrame() {
+ audio_buffer_.Seek(bytes_per_frame_);
+
+ if (!IsQueueFull())
+ request_read_cb_.Run();
+}
+
+void AudioRendererAlgorithmBase::OutputCrossfadedFrame(
+ uint8* outtro, const uint8* intro) {
+ DCHECK_LE(index_into_window_, window_size_);
+ DCHECK(!muted_);
+
+ switch (bytes_per_channel_) {
+ case 4:
+ CrossfadeFrame<int32>(outtro, intro);
+ break;
+ case 2:
+ CrossfadeFrame<int16>(outtro, intro);
+ break;
+ case 1:
+ CrossfadeFrame<uint8>(outtro, intro);
+ break;
+ default:
+ NOTREACHED() << "Unsupported audio bit depth in crossfade.";
+ }
+}
+
+template <class Type>
+void AudioRendererAlgorithmBase::CrossfadeFrame(
+ uint8* outtro_bytes, const uint8* intro_bytes) {
+ Type* outtro = reinterpret_cast<Type*>(outtro_bytes);
+ const Type* intro = reinterpret_cast<const Type*>(intro_bytes);
+
+ uint32 frames_in_crossfade = bytes_in_crossfade_ / bytes_per_frame_;
+ float crossfade_ratio =
+ static_cast<float>(crossfade_frame_number_) / frames_in_crossfade;
+ for (int channel = 0; channel < channels_; ++channel) {
+ *outtro *= 1.0 - crossfade_ratio;
+ *outtro++ += (*intro++) * crossfade_ratio;
+ }
+ crossfade_frame_number_++;
}
void AudioRendererAlgorithmBase::SetPlaybackRate(float new_rate) {
DCHECK_GE(new_rate, 0.0);
playback_rate_ = new_rate;
+ muted_ =
+ playback_rate_ < kMinPlaybackRate || playback_rate_ > kMaxPlaybackRate;
+
+ ResetWindow();
}
-void AudioRendererAlgorithmBase::AlignToSampleBoundary(uint32* value) {
- (*value) -= ((*value) % (channels_ * bytes_per_channel_));
+void AudioRendererAlgorithmBase::AlignToFrameBoundary(uint32* value) {
+ (*value) -= ((*value) % bytes_per_frame_);
}
void AudioRendererAlgorithmBase::FlushBuffers() {
+ ResetWindow();
+
// Clear the queue of decoded packets (releasing the buffers).
audio_buffer_.Clear();
request_read_cb_.Run();
@@ -222,15 +411,19 @@ base::TimeDelta AudioRendererAlgorithmBase::GetTime() {
}
void AudioRendererAlgorithmBase::EnqueueBuffer(Buffer* buffer_in) {
- // If we're at end of stream, |buffer_in| contains no data.
- if (!buffer_in->IsEndOfStream())
- audio_buffer_.Append(buffer_in);
+ DCHECK(!buffer_in->IsEndOfStream());
+ audio_buffer_.Append(buffer_in);
+ needs_more_data_ = false;
// If we still don't have enough data, request more.
if (!IsQueueFull())
request_read_cb_.Run();
}
+bool AudioRendererAlgorithmBase::NeedsMoreData() {
+ return needs_more_data_ || IsQueueEmpty();
+}
+
bool AudioRendererAlgorithmBase::IsQueueEmpty() {
return audio_buffer_.forward_bytes() == 0;
}
@@ -248,16 +441,4 @@ void AudioRendererAlgorithmBase::IncreaseQueueCapacity() {
std::min(2 * audio_buffer_.forward_capacity(), kMaxBufferSizeInBytes));
}
-void AudioRendererAlgorithmBase::AdvanceBufferPosition(uint32 bytes) {
- audio_buffer_.Seek(bytes);
-
- if (!IsQueueFull())
- request_read_cb_.Run();
-}
-
-uint32 AudioRendererAlgorithmBase::CopyFromAudioBuffer(
- uint8* dest, uint32 bytes) {
- return audio_buffer_.Peek(dest, bytes);
-}
-
} // namespace media
diff --git a/media/filters/audio_renderer_algorithm_base.h b/media/filters/audio_renderer_algorithm_base.h
index a7325da..9e7d8a8 100644
--- a/media/filters/audio_renderer_algorithm_base.h
+++ b/media/filters/audio_renderer_algorithm_base.h
@@ -30,6 +30,8 @@ namespace media {
class Buffer;
+// TODO(vrk): Remove all the uint32s from AudioRendererAlgorithmBase and
+// replace them with ints.
class MEDIA_EXPORT AudioRendererAlgorithmBase {
public:
AudioRendererAlgorithmBase();
@@ -51,22 +53,15 @@ class MEDIA_EXPORT AudioRendererAlgorithmBase {
float initial_playback_rate,
const base::Closure& request_read_cb);
- // Tries to fill |length| bytes of |dest| with possibly scaled data from our
- // |audio_buffer_|. Data is scaled based on the playback rate, using a
- // variation of the Overlap-Add method to combine sample windows.
+ // Tries to fill |requested_frames| frames into |dest| with possibly scaled
+ // data from our |audio_buffer_|. Data is scaled based on the playback rate,
+ // using a variation of the Overlap-Add method to combine sample windows.
//
// Data from |audio_buffer_| is consumed in proportion to the playback rate.
- // FillBuffer() will fit |playback_rate_| * |length| bytes of raw data from
- // |audio_buffer| into |length| bytes of output data in |dest| by chopping up
- // the buffered data into windows and crossfading from one window to the next.
- // For speeds greater than 1.0f, FillBuffer() "squish" the windows, dropping
- // some data in between windows to meet the sped-up playback. For speeds less
- // than 1.0f, FillBuffer() will "stretch" the window by copying and
- // overlapping data at the window boundaries, crossfading in between.
//
- // Returns the number of bytes copied into |dest|.
+ // Returns the number of frames copied into |dest|.
// May request more reads via |request_read_cb_| before returning.
- uint32 FillBuffer(uint8* dest, uint32 length);
+ uint32 FillBuffer(uint8* dest, uint32 requested_frames);
// Clears |audio_buffer_|.
void FlushBuffers();
@@ -82,8 +77,8 @@ class MEDIA_EXPORT AudioRendererAlgorithmBase {
float playback_rate() const { return playback_rate_; }
void SetPlaybackRate(float new_rate);
- // Returns whether |audio_buffer_| is empty.
- bool IsQueueEmpty();
+ // Returns whether the algorithm needs more data to continue filling buffers.
+ bool NeedsMoreData();
// Returns true if |audio_buffer_| is at or exceeds capacity.
bool IsQueueFull();
@@ -99,21 +94,65 @@ class MEDIA_EXPORT AudioRendererAlgorithmBase {
// than |audio_buffer_| was intending to hold.
uint32 bytes_buffered() { return audio_buffer_.forward_bytes(); }
- uint32 window_size() { return window_size_; }
+ uint32 bytes_per_frame() { return bytes_per_frame_; }
- private:
- // Advances |audio_buffer_|'s internal pointer by |bytes|.
- void AdvanceBufferPosition(uint32 bytes);
+ uint32 bytes_per_channel() { return bytes_per_channel_; }
- // Tries to copy |bytes| bytes from |audio_buffer_| to |dest|.
- // Returns the number of bytes successfully copied.
- uint32 CopyFromAudioBuffer(uint8* dest, uint32 bytes);
+ bool is_muted() { return muted_; }
- // Aligns |value| to a channel and sample boundary.
- void AlignToSampleBoundary(uint32* value);
+ private:
+ // Returns true if |audio_buffer_| is empty.
+ bool IsQueueEmpty();
- // Attempts to write |length| bytes of muted audio into |dest|.
- uint32 MuteBuffer(uint8* dest, uint32 length);
+ // Fills |dest| with one frame of audio data at normal speed. Returns true if
+ // a frame was rendered, false otherwise.
+ bool OutputNormalPlayback(uint8* dest);
+
+ // Fills |dest| with one frame of audio data at faster than normal speed.
+ // Returns true if a frame was rendered, false otherwise.
+ //
+ // When the audio playback is > 1.0, we use a variant of Overlap-Add to squish
+ // audio output while preserving pitch. Essentially, we play a bit of audio
+ // data at normal speed, then we "fast forward" by dropping the next bit of
+ // audio data, and then we stich the pieces together by crossfading from one
+ // audio chunk to the next.
+ bool OutputFasterPlayback(uint8* dest);
+
+ // Fills |dest| with one frame of audio data at slower than normal speed.
+ // Returns true if a frame was rendered, false otherwise.
+ //
+ // When the audio playback is < 1.0, we use a variant of Overlap-Add to
+ // stretch audio output while preserving pitch. This works by outputting a
+ // segment of audio data at normal speed. The next audio segment then starts
+ // by repeating some of the audio data from the previous audio segment.
+ // Segments are stiched together by crossfading from one audio chunk to the
+ // next.
+ bool OutputSlowerPlayback(uint8* dest);
+
+ // Resets the window state to the start of a new window.
+ void ResetWindow();
+
+ // Copies a raw frame from |audio_buffer_| into |dest| without progressing
+ // |audio_buffer_|'s internal "current" cursor. Optionally peeks at a forward
+ // byte |offset|.
+ void CopyWithoutAdvance(uint8* dest);
+ void CopyWithoutAdvance(uint8* dest, uint32 offset);
+
+ // Copies a raw frame from |audio_buffer_| into |dest| and progresses the
+ // |audio_buffer_| forward.
+ void CopyWithAdvance(uint8* dest);
+
+ // Moves the |audio_buffer_| forward by one frame.
+ void DropFrame();
+
+ // Does a linear crossfade from |intro| into |outtro| for one frame.
+ // Assumes pointers are valid and are at least size of |bytes_per_frame_|.
+ void OutputCrossfadedFrame(uint8* outtro, const uint8* intro);
+ template <class Type>
+ void CrossfadeFrame(uint8* outtro, const uint8* intro);
+
+ // Rounds |*value| down to the nearest frame boundary.
+ void AlignToFrameBoundary(uint32* value);
// Number of channels in audio stream.
int channels_;
@@ -134,7 +173,26 @@ class MEDIA_EXPORT AudioRendererAlgorithmBase {
SeekableBuffer audio_buffer_;
// Length for crossfade in bytes.
- uint32 crossfade_size_;
+ uint32 bytes_in_crossfade_;
+
+ // Length of frame in bytes.
+ uint32 bytes_per_frame_;
+
+ // The current location in the audio window, between 0 and |window_size_|.
+ // When |index_into_window_| reaches |window_size_|, the window resets.
+ // Indexed by byte.
+ uint32 index_into_window_;
+
+ // The frame number in the crossfade.
+ uint32 crossfade_frame_number_;
+
+ // True if the audio should be muted.
+ bool muted_;
+
+ bool needs_more_data_;
+
+ // Temporary buffer to hold crossfade data.
+ scoped_array<uint8> crossfade_buffer_;
// Window size, in bytes (calculated from audio properties).
uint32 window_size_;
diff --git a/media/filters/audio_renderer_algorithm_base_unittest.cc b/media/filters/audio_renderer_algorithm_base_unittest.cc
index 75d8c52..77c7761 100644
--- a/media/filters/audio_renderer_algorithm_base_unittest.cc
+++ b/media/filters/audio_renderer_algorithm_base_unittest.cc
@@ -8,136 +8,280 @@
// 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 <cmath>
+
#include "base/bind.h"
#include "base/callback.h"
#include "media/base/data_buffer.h"
#include "media/filters/audio_renderer_algorithm_base.h"
-#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
-using ::testing::AnyNumber;
+static const size_t kRawDataSize = 10 * 1024;
+static const int kSamplesPerSecond = 44100;
+static const int kDefaultChannels = 2;
+static const int kDefaultSampleBits = 16;
namespace media {
-static const int kChannels = 1;
-static const int kSampleRate = 1000;
-static const int kSampleBits = 8;
-
-TEST(AudioRendererAlgorithmBaseTest, FillBuffer_NormalRate) {
- // When playback rate == 1.0f: straight copy of whatever is in |queue_|.
- AudioRendererAlgorithmBase algorithm;
- algorithm.Initialize(kChannels, kSampleRate, kSampleBits, 1.0f,
- base::Bind(&base::DoNothing));
-
- // Enqueue a buffer of any size since it doesn't matter.
- const size_t kDataSize = 1024;
- algorithm.EnqueueBuffer(new DataBuffer(
- scoped_array<uint8>(new uint8[kDataSize]), kDataSize));
- EXPECT_EQ(kDataSize, algorithm.bytes_buffered());
-
- // Read the same sized amount.
- scoped_array<uint8> data(new uint8[kDataSize]);
- EXPECT_EQ(kDataSize, algorithm.FillBuffer(data.get(), kDataSize));
- EXPECT_EQ(0u, algorithm.bytes_buffered());
-}
+class AudioRendererAlgorithmBaseTest : public testing::Test {
+ public:
+ AudioRendererAlgorithmBaseTest()
+ : bytes_enqueued_(0) {
+ }
+
+ ~AudioRendererAlgorithmBaseTest() {}
-TEST(AudioRendererAlgorithmBaseTest, FillBuffer_DoubleRate) {
- // When playback rate > 1.0f: input is read faster than output is written.
- AudioRendererAlgorithmBase algorithm;
- algorithm.Initialize(kChannels, kSampleRate, kSampleBits, 2.0f,
- base::Bind(&base::DoNothing));
-
- // 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(
- scoped_array<uint8>(new uint8[kDataSize]), kDataSize));
- EXPECT_EQ(kDataSize, algorithm.bytes_buffered());
-
- const size_t kExpectedSize = kTestData[i][1];
- ASSERT_LE(kExpectedSize, kBufferSize);
- EXPECT_EQ(kExpectedSize, algorithm.FillBuffer(data.get(), kBufferSize));
- EXPECT_EQ(0u, algorithm.bytes_buffered());
+ void Initialize() {
+ Initialize(kDefaultChannels, kDefaultSampleBits);
}
-}
-TEST(AudioRendererAlgorithmBaseTest, FillBuffer_HalfRate) {
- // When playback rate < 1.0f: input is read slower than output is written.
- AudioRendererAlgorithmBase algorithm;
- algorithm.Initialize(kChannels, kSampleRate, kSampleBits, 0.5f,
- base::Bind(&base::DoNothing));
-
- // 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(
- scoped_array<uint8>(new uint8[kDataSize]), kDataSize));
- EXPECT_EQ(kDataSize, algorithm.bytes_buffered());
-
- const size_t kExpectedSize = kTestData[i][1];
- ASSERT_LE(kExpectedSize, kBufferSize);
- EXPECT_EQ(kExpectedSize, algorithm.FillBuffer(data.get(), kBufferSize));
- EXPECT_EQ(0u, algorithm.bytes_buffered());
+ void Initialize(int channels, int bits_per_channel) {
+ algorithm_.Initialize(
+ channels, kSamplesPerSecond, bits_per_channel, 1.0f,
+ base::Bind(&AudioRendererAlgorithmBaseTest::EnqueueData,
+ base::Unretained(this)));
+ EnqueueData();
+ }
+
+ void EnqueueData() {
+ scoped_array<uint8> audio_data(new uint8[kRawDataSize]);
+ CHECK_EQ(kRawDataSize % algorithm_.bytes_per_channel(), 0u);
+ CHECK_EQ(kRawDataSize % algorithm_.bytes_per_frame(), 0u);
+ size_t length = kRawDataSize / algorithm_.bytes_per_channel();
+ switch (algorithm_.bytes_per_channel()) {
+ case 4:
+ WriteFakeData<int32>(audio_data.get(), length);
+ break;
+ case 2:
+ WriteFakeData<int16>(audio_data.get(), length);
+ break;
+ case 1:
+ WriteFakeData<uint8>(audio_data.get(), length);
+ break;
+ default:
+ NOTREACHED() << "Unsupported audio bit depth in crossfade.";
+ }
+ algorithm_.EnqueueBuffer(new DataBuffer(audio_data.Pass(), kRawDataSize));
+ bytes_enqueued_ += kRawDataSize;
+ }
+
+ template <class Type>
+ void WriteFakeData(uint8* audio_data, size_t length) {
+ Type* output = reinterpret_cast<Type*>(audio_data);
+ for (size_t i = 0; i < length; i++) {
+ // The value of the data is meaningless; we just want non-zero data to
+ // differentiate it from muted data.
+ output[i] = i % 5 + 10;
+ }
+ }
+
+ void CheckFakeData(uint8* audio_data, int frames_written,
+ double playback_rate) {
+ size_t length =
+ (frames_written * algorithm_.bytes_per_frame())
+ / algorithm_.bytes_per_channel();
+
+ switch (algorithm_.bytes_per_channel()) {
+ case 4:
+ DoCheckFakeData<int32>(audio_data, length);
+ break;
+ case 2:
+ DoCheckFakeData<int16>(audio_data, length);
+ break;
+ case 1:
+ DoCheckFakeData<uint8>(audio_data, length);
+ break;
+ default:
+ NOTREACHED() << "Unsupported audio bit depth in crossfade.";
+ }
+ }
+
+ template <class Type>
+ void DoCheckFakeData(uint8* audio_data, size_t length) {
+ Type* output = reinterpret_cast<Type*>(audio_data);
+ for (size_t i = 0; i < length; i++) {
+ EXPECT_TRUE(algorithm_.is_muted() || output[i] != 0);
+ }
}
-}
-TEST(AudioRendererAlgorithmBaseTest, FillBuffer_QuarterRate) {
- // When playback rate is very low the audio is simply muted.
- AudioRendererAlgorithmBase algorithm;
- algorithm.Initialize(kChannels, kSampleRate, kSampleBits, 0.25f,
- base::Bind(&base::DoNothing));
-
- // 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(scoped_array<uint8>(
- new uint8[kDataSize]), kDataSize));
- EXPECT_EQ(kDataSize, algorithm.bytes_buffered());
-
- const size_t kExpectedSize = kTestData[i][1];
- ASSERT_LE(kExpectedSize, kBufferSize);
- EXPECT_EQ(kExpectedSize, algorithm.FillBuffer(data.get(), kBufferSize));
- EXPECT_EQ(0u, algorithm.bytes_buffered());
+ int ComputeConsumedBytes(int initial_bytes_enqueued,
+ int initial_bytes_buffered) {
+ int byte_delta = bytes_enqueued_ - initial_bytes_enqueued;
+ int buffered_delta = algorithm_.bytes_buffered() - initial_bytes_buffered;
+ int consumed = byte_delta - buffered_delta;
+ CHECK_GE(consumed, 0);
+ return consumed;
}
+
+ void TestPlaybackRate(double playback_rate) {
+ static const int kDefaultBufferSize = kSamplesPerSecond / 10;
+ static const int kDefaultFramesRequested = 5 * kSamplesPerSecond;
+
+ TestPlaybackRate(playback_rate, kDefaultBufferSize,
+ kDefaultFramesRequested);
+ }
+
+ void TestPlaybackRate(double playback_rate,
+ int buffer_size_in_frames,
+ int total_frames_requested) {
+ int initial_bytes_enqueued = bytes_enqueued_;
+ int initial_bytes_buffered = algorithm_.bytes_buffered();
+
+ algorithm_.SetPlaybackRate(static_cast<float>(playback_rate));
+
+ scoped_array<uint8> buffer(
+ new uint8[buffer_size_in_frames * algorithm_.bytes_per_frame()]);
+
+ if (playback_rate == 0.0) {
+ int frames_written =
+ algorithm_.FillBuffer(buffer.get(), buffer_size_in_frames);
+ EXPECT_EQ(0, frames_written);
+ return;
+ }
+
+ int frames_remaining = total_frames_requested;
+ while (frames_remaining > 0) {
+ int frames_requested = std::min(buffer_size_in_frames, frames_remaining);
+ int frames_written =
+ algorithm_.FillBuffer(buffer.get(), frames_requested);
+ CHECK_GT(frames_written, 0);
+ CheckFakeData(buffer.get(), frames_written, playback_rate);
+ frames_remaining -= frames_written;
+ }
+
+ int bytes_requested = total_frames_requested * algorithm_.bytes_per_frame();
+ int bytes_consumed = ComputeConsumedBytes(initial_bytes_enqueued,
+ initial_bytes_buffered);
+
+ // If playing back at normal speed, we should always get back the same
+ // number of bytes requested.
+ if (playback_rate == 1.0) {
+ EXPECT_EQ(bytes_requested, bytes_consumed);
+ return;
+ }
+
+ // Otherwise, allow |kMaxAcceptableDelta| difference between the target and
+ // actual playback rate.
+ // When |kSamplesPerSecond| and |total_frames_requested| are reasonably
+ // large, one can expect less than a 1% difference in most cases. In our
+ // current implementation, sped up playback is less accurate than slowed
+ // down playback, and for playback_rate > 1, playback rate generally gets
+ // less and less accurate the farther it drifts from 1 (though this is
+ // nonlinear).
+ static const double kMaxAcceptableDelta = 0.01;
+ double actual_playback_rate = 1.0 * bytes_consumed / bytes_requested;
+
+ // Calculate the percentage difference from the target |playback_rate| as a
+ // fraction from 0.0 to 1.0.
+ double delta = std::abs(1.0 - (actual_playback_rate / playback_rate));
+
+ EXPECT_LE(delta, kMaxAcceptableDelta);
+ }
+
+ protected:
+ AudioRendererAlgorithmBase algorithm_;
+ int bytes_enqueued_;
+};
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_NormalRate) {
+ Initialize();
+ TestPlaybackRate(1.0);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_OneAndAQuarterRate) {
+ Initialize();
+ TestPlaybackRate(1.25);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_OneAndAHalfRate) {
+ Initialize();
+ TestPlaybackRate(1.5);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_DoubleRate) {
+ Initialize();
+ TestPlaybackRate(2.0);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_EightTimesRate) {
+ Initialize();
+ TestPlaybackRate(8.0);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_ThreeQuartersRate) {
+ Initialize();
+ TestPlaybackRate(0.75);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_HalfRate) {
+ Initialize();
+ TestPlaybackRate(0.5);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_QuarterRate) {
+ Initialize();
+ TestPlaybackRate(0.25);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_Pause) {
+ Initialize();
+ TestPlaybackRate(0.0);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_SlowDown) {
+ Initialize();
+ TestPlaybackRate(4.5);
+ TestPlaybackRate(3.0);
+ TestPlaybackRate(2.0);
+ TestPlaybackRate(1.0);
+ TestPlaybackRate(0.5);
+ TestPlaybackRate(0.25);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_SpeedUp) {
+ Initialize();
+ TestPlaybackRate(0.25);
+ TestPlaybackRate(0.5);
+ TestPlaybackRate(1.0);
+ TestPlaybackRate(2.0);
+ TestPlaybackRate(3.0);
+ TestPlaybackRate(4.5);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_JumpAroundSpeeds) {
+ Initialize();
+ TestPlaybackRate(2.1);
+ TestPlaybackRate(0.9);
+ TestPlaybackRate(0.6);
+ TestPlaybackRate(1.4);
+ TestPlaybackRate(0.3);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_SmallBufferSize) {
+ Initialize();
+ static const int kBufferSizeInFrames = 1;
+ static const int kFramesRequested = 2 * kSamplesPerSecond;
+ TestPlaybackRate(1.0, kBufferSizeInFrames, kFramesRequested);
+ TestPlaybackRate(0.5, kBufferSizeInFrames, kFramesRequested);
+ TestPlaybackRate(1.5, kBufferSizeInFrames, kFramesRequested);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_LowerQualityAudio) {
+ static const int kChannels = 1;
+ static const int kSampleBits = 8;
+ Initialize(kChannels, kSampleBits);
+ TestPlaybackRate(1.0);
+ TestPlaybackRate(0.5);
+ TestPlaybackRate(1.5);
+}
+
+TEST_F(AudioRendererAlgorithmBaseTest, FillBuffer_HigherQualityAudio) {
+ static const int kChannels = 2;
+ static const int kSampleBits = 32;
+ Initialize(kChannels, kSampleBits);
+ TestPlaybackRate(1.0);
+ TestPlaybackRate(0.5);
+ TestPlaybackRate(1.5);
}
} // namespace media
diff --git a/media/filters/audio_renderer_base.cc b/media/filters/audio_renderer_base.cc
index bbf485a..0162f66 100644
--- a/media/filters/audio_renderer_base.cc
+++ b/media/filters/audio_renderer_base.cc
@@ -17,8 +17,9 @@ namespace media {
AudioRendererBase::AudioRendererBase()
: state_(kUninitialized),
pending_read_(false),
- recieved_end_of_stream_(false),
+ received_end_of_stream_(false),
rendered_end_of_stream_(false),
+ bytes_per_frame_(0),
read_cb_(base::Bind(&AudioRendererBase::DecodedAudioReady,
base::Unretained(this))) {
}
@@ -81,7 +82,7 @@ void AudioRendererBase::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
// Throw away everything and schedule our reads.
last_fill_buffer_time_ = base::TimeDelta();
- recieved_end_of_stream_ = false;
+ received_end_of_stream_ = false;
rendered_end_of_stream_ = false;
// |algorithm_| will request more reads.
@@ -113,6 +114,8 @@ void AudioRendererBase::Initialize(const scoped_refptr<AudioDecoder>& decoder,
int channels = ChannelLayoutToChannelCount(channel_layout);
int bits_per_channel = decoder_->bits_per_channel();
int sample_rate = decoder_->samples_per_second();
+ // TODO(vrk): Add method to AudioDecoder to compute bytes per frame.
+ bytes_per_frame_ = channels * bits_per_channel / 8;
bool config_ok = algorithm_->ValidateConfig(channels, sample_rate,
bits_per_channel);
@@ -133,11 +136,9 @@ void AudioRendererBase::Initialize(const scoped_refptr<AudioDecoder>& decoder,
bool AudioRendererBase::HasEnded() {
base::AutoLock auto_lock(lock_);
- if (rendered_end_of_stream_) {
- DCHECK(algorithm_->IsQueueEmpty())
- << "Audio queue should be empty if we have rendered end of stream";
- }
- return recieved_end_of_stream_ && rendered_end_of_stream_;
+ DCHECK(!rendered_end_of_stream_ || algorithm_->NeedsMoreData());
+
+ return received_end_of_stream_ && rendered_end_of_stream_;
}
void AudioRendererBase::ResumeAfterUnderflow(bool buffer_more_audio) {
@@ -159,7 +160,7 @@ void AudioRendererBase::DecodedAudioReady(scoped_refptr<Buffer> buffer) {
pending_read_ = false;
if (buffer && buffer->IsEndOfStream()) {
- recieved_end_of_stream_ = true;
+ received_end_of_stream_ = true;
// Transition to kPlaying if we are currently handling an underflow since
// no more data will be arriving.
@@ -202,12 +203,12 @@ void AudioRendererBase::DecodedAudioReady(scoped_refptr<Buffer> buffer) {
}
uint32 AudioRendererBase::FillBuffer(uint8* dest,
- uint32 dest_len,
+ uint32 requested_frames,
const base::TimeDelta& playback_delay) {
// The timestamp of the last buffer written during the last call to
// FillBuffer().
base::TimeDelta last_fill_buffer_time;
- size_t dest_written = 0;
+ size_t frames_written = 0;
base::Closure underflow_cb;
{
base::AutoLock auto_lock(lock_);
@@ -224,9 +225,10 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest,
//
// This should get handled by the subclass http://crbug.com/106600
const uint32 kZeroLength = 8192;
- dest_written = std::min(kZeroLength, dest_len);
- memset(dest, 0, dest_written);
- return dest_written;
+ size_t zeros_to_write =
+ std::min(kZeroLength, requested_frames * bytes_per_frame_);
+ memset(dest, 0, zeros_to_write);
+ return zeros_to_write / bytes_per_frame_;
}
// Save a local copy of last fill buffer time and reset the member.
@@ -234,9 +236,9 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest,
last_fill_buffer_time_ = base::TimeDelta();
// Use three conditions to determine the end of playback:
- // 1. Algorithm has no audio data. (algorithm_->IsQueueEmpty() == true)
- // 2. We've recieved an end of stream buffer.
- // (recieved_end_of_stream_ == true)
+ // 1. Algorithm needs more audio data.
+ // 2. We've received an end of stream buffer.
+ // (received_end_of_stream_ == true)
// 3. Browser process has no audio data being played.
// There is no way to check that condition that would work for all
// derived classes, so call virtual method that would either render
@@ -246,8 +248,8 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest,
// 1. Algorithm has no audio data.
// 2. Currently in the kPlaying state.
// 3. Have not received an end of stream buffer.
- if (algorithm_->IsQueueEmpty()) {
- if (recieved_end_of_stream_) {
+ if (algorithm_->NeedsMoreData()) {
+ if (received_end_of_stream_) {
OnRenderEndOfStream();
} else if (state_ == kPlaying) {
state_ = kUnderflow;
@@ -255,7 +257,7 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest,
}
} else {
// Otherwise fill the buffer.
- dest_written = algorithm_->FillBuffer(dest, dest_len);
+ frames_written = algorithm_->FillBuffer(dest, requested_frames);
}
// Get the current time.
@@ -273,11 +275,11 @@ uint32 AudioRendererBase::FillBuffer(uint8* dest,
if (!underflow_cb.is_null())
underflow_cb.Run();
- return dest_written;
+ return frames_written;
}
void AudioRendererBase::SignalEndOfStream() {
- DCHECK(recieved_end_of_stream_);
+ DCHECK(received_end_of_stream_);
if (!rendered_end_of_stream_) {
rendered_end_of_stream_ = true;
host()->NotifyEnded();
diff --git a/media/filters/audio_renderer_base.h b/media/filters/audio_renderer_base.h
index 3e41c43..20997ae 100644
--- a/media/filters/audio_renderer_base.h
+++ b/media/filters/audio_renderer_base.h
@@ -73,9 +73,10 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
// Fills the given buffer with audio data by delegating to its |algorithm_|.
// FillBuffer() also takes care of updating the clock. Returns the number of
- // bytes copied into |dest|, which may be less than or equal to |len|.
+ // frames copied into |dest|, which may be less than or equal to
+ // |requested_frames|.
//
- // If this method is returns less bytes than |len| (including zero), it could
+ // If this method returns fewer frames than |requested_frames|, it could
// be a sign that the pipeline is stalled or unable to stream the data fast
// enough. In such scenarios, the callee should zero out unused portions
// of their buffer to playback silence.
@@ -95,7 +96,7 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
//
// Safe to call on any thread.
uint32 FillBuffer(uint8* dest,
- uint32 len,
+ uint32 requested_frames,
const base::TimeDelta& playback_delay);
// Called by OnRenderEndOfStream() or some callback scheduled by derived class
@@ -144,7 +145,7 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
bool pending_read_;
// Keeps track of whether we received and rendered the end of stream buffer.
- bool recieved_end_of_stream_;
+ bool received_end_of_stream_;
bool rendered_end_of_stream_;
// Audio time at end of last call to FillBuffer().
@@ -161,6 +162,8 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
base::TimeDelta seek_timestamp_;
+ uint32 bytes_per_frame_;
+
AudioDecoder::ReadCB read_cb_;
DISALLOW_COPY_AND_ASSIGN(AudioRendererBase);
diff --git a/media/filters/audio_renderer_base_unittest.cc b/media/filters/audio_renderer_base_unittest.cc
index 4ada3e4..58bc59f 100644
--- a/media/filters/audio_renderer_base_unittest.cc
+++ b/media/filters/audio_renderer_base_unittest.cc
@@ -184,12 +184,16 @@ class AudioRendererBaseTest : public ::testing::Test {
// the consumed data is muted audio.
bool ConsumeBufferedData(uint32 size, bool* muted) {
scoped_array<uint8> buffer(new uint8[size]);
- uint32 bytes_read = renderer_->FillBuffer(buffer.get(), size,
- base::TimeDelta());
- if (bytes_read > 0 && muted) {
+ uint32 bytes_per_frame = (decoder_->bits_per_channel() / 8) *
+ ChannelLayoutToChannelCount(decoder_->channel_layout());
+ uint32 requested_frames = size / bytes_per_frame;
+ uint32 frames_read = renderer_->FillBuffer(
+ buffer.get(), requested_frames, base::TimeDelta());
+
+ if (frames_read > 0 && muted) {
*muted = (buffer[0] == kMutedAudio);
}
- return (bytes_read == size);
+ return (frames_read == requested_frames);
}
uint32 bytes_buffered() {
diff --git a/media/filters/null_audio_renderer.cc b/media/filters/null_audio_renderer.cc
index 6168bd9..f7ee27c 100644
--- a/media/filters/null_audio_renderer.cc
+++ b/media/filters/null_audio_renderer.cc
@@ -22,6 +22,7 @@ NullAudioRenderer::NullAudioRenderer()
: AudioRendererBase(),
bytes_per_millisecond_(0),
buffer_size_(0),
+ bytes_per_frame_(0),
thread_("AudioThread") {
}
@@ -37,10 +38,16 @@ bool NullAudioRenderer::OnInitialize(int bits_per_channel,
ChannelLayout channel_layout,
int sample_rate) {
// Calculate our bytes per millisecond value and allocate our buffer.
- bytes_per_millisecond_ =
- (ChannelLayoutToChannelCount(channel_layout) * sample_rate *
- bits_per_channel / 8) / base::Time::kMillisecondsPerSecond;
- buffer_size_ = bytes_per_millisecond_ * kBufferSizeInMilliseconds;
+ int channels = ChannelLayoutToChannelCount(channel_layout);
+ int bytes_per_channel = bits_per_channel / 8;
+ bytes_per_frame_ = channels * bytes_per_channel;
+
+ bytes_per_millisecond_ = (bytes_per_frame_ * sample_rate) /
+ base::Time::kMillisecondsPerSecond;
+
+ buffer_size_ =
+ bytes_per_millisecond_ * kBufferSizeInMilliseconds;
+
buffer_.reset(new uint8[buffer_size_]);
DCHECK(buffer_.get());
@@ -61,7 +68,10 @@ void NullAudioRenderer::FillBufferTask() {
// Only consume buffers when actually playing.
if (GetPlaybackRate() > 0.0f) {
- size_t bytes = FillBuffer(buffer_.get(), buffer_size_, base::TimeDelta());
+ size_t requested_frames = buffer_size_ / bytes_per_frame_;
+ size_t frames = FillBuffer(
+ buffer_.get(), requested_frames, base::TimeDelta());
+ size_t bytes = frames * bytes_per_frame_;
// Calculate our sleep duration, taking playback rate into consideration.
delay = base::TimeDelta::FromMilliseconds(
diff --git a/media/filters/null_audio_renderer.h b/media/filters/null_audio_renderer.h
index 45c8af6..03500ca 100644
--- a/media/filters/null_audio_renderer.h
+++ b/media/filters/null_audio_renderer.h
@@ -52,6 +52,8 @@ class MEDIA_EXPORT NullAudioRenderer : public AudioRendererBase {
scoped_array<uint8> buffer_;
size_t buffer_size_;
+ size_t bytes_per_frame_;
+
// Separate thread used to throw away data.
base::Thread thread_;