diff options
author | vrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-06 00:48:37 +0000 |
---|---|---|
committer | vrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-06 00:48:37 +0000 |
commit | e4eccfdae7c8b34056e7b61450b0be30825fc5d2 (patch) | |
tree | f5090ba1d0d83f9df413ddf370eb12acbec8e7d2 /media | |
parent | 8e248501d06d6536bdc8f37d20d8c600f848485b (diff) | |
download | chromium_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.cc | 72 | ||||
-rw-r--r-- | media/audio/audio_util.h | 5 | ||||
-rw-r--r-- | media/base/seekable_buffer.cc | 53 | ||||
-rw-r--r-- | media/base/seekable_buffer.h | 9 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_base.cc | 391 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_base.h | 110 | ||||
-rw-r--r-- | media/filters/audio_renderer_algorithm_base_unittest.cc | 374 | ||||
-rw-r--r-- | media/filters/audio_renderer_base.cc | 44 | ||||
-rw-r--r-- | media/filters/audio_renderer_base.h | 11 | ||||
-rw-r--r-- | media/filters/audio_renderer_base_unittest.cc | 12 | ||||
-rw-r--r-- | media/filters/null_audio_renderer.cc | 20 | ||||
-rw-r--r-- | media/filters/null_audio_renderer.h | 2 |
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_; |