diff --git a/media/base/audio_decoder_config.cc b/media/base/audio_decoder_config.cc index bd0e6a7e75f84e..ed2eeabeb07044 100644 --- a/media/base/audio_decoder_config.cc +++ b/media/base/audio_decoder_config.cc @@ -16,6 +16,7 @@ AudioDecoderConfig::AudioDecoderConfig() bits_per_channel_(0), channel_layout_(CHANNEL_LAYOUT_UNSUPPORTED), samples_per_second_(0), + bytes_per_frame_(0), extra_data_size_(0), is_encrypted_(false) { } @@ -73,6 +74,9 @@ void AudioDecoderConfig::Initialize(AudioCodec codec, } is_encrypted_ = is_encrypted; + + int channels = ChannelLayoutToChannelCount(channel_layout_); + bytes_per_frame_ = channels * bits_per_channel_ / 8; } AudioDecoderConfig::~AudioDecoderConfig() {} @@ -124,6 +128,10 @@ int AudioDecoderConfig::samples_per_second() const { return samples_per_second_; } +int AudioDecoderConfig::bytes_per_frame() const { + return bytes_per_frame_; +} + uint8* AudioDecoderConfig::extra_data() const { return extra_data_.get(); } diff --git a/media/base/audio_decoder_config.h b/media/base/audio_decoder_config.h index 2bc2487e9b7f53..ce62eb85a1f638 100644 --- a/media/base/audio_decoder_config.h +++ b/media/base/audio_decoder_config.h @@ -78,6 +78,7 @@ class MEDIA_EXPORT AudioDecoderConfig { int bits_per_channel() const; ChannelLayout channel_layout() const; int samples_per_second() const; + int bytes_per_frame() const; // Optional byte data required to initialize audio decoders such as Vorbis // codebooks. @@ -94,6 +95,7 @@ class MEDIA_EXPORT AudioDecoderConfig { int bits_per_channel_; ChannelLayout channel_layout_; int samples_per_second_; + int bytes_per_frame_; scoped_array extra_data_; size_t extra_data_size_; diff --git a/media/base/audio_splicer.cc b/media/base/audio_splicer.cc new file mode 100644 index 00000000000000..2efbba93264e23 --- /dev/null +++ b/media/base/audio_splicer.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/audio_splicer.h" + +#include + +#include "base/logging.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/audio_timestamp_helper.h" +#include "media/base/buffers.h" +#include "media/base/data_buffer.h" + +namespace media { + +// Largest gap or overlap allowed by this class. Anything +// larger than this will trigger an error. +// This is an arbitrary value, but the initial selection of 50ms +// roughly represents the duration of 2 compressed AAC or MP3 frames. +static const int kMaxTimeDeltaInMilliseconds = 50; + +AudioSplicer::AudioSplicer(int bytes_per_frame, int samples_per_second) + : output_timestamp_helper_(bytes_per_frame, samples_per_second), + min_gap_size_(2 * bytes_per_frame), + received_end_of_stream_(false) { +} + +AudioSplicer::~AudioSplicer() { +} + +void AudioSplicer::Reset() { + output_timestamp_helper_.SetBaseTimestamp(kNoTimestamp()); + output_buffers_.clear(); + received_end_of_stream_ = false; +} + +bool AudioSplicer::AddInput(const scoped_refptr& input){ + DCHECK(!received_end_of_stream_ || input->IsEndOfStream()); + + if (input->IsEndOfStream()) { + output_buffers_.push_back(input); + received_end_of_stream_ = true; + return true; + } + + DCHECK(input->GetTimestamp() != kNoTimestamp()); + DCHECK(input->GetDuration() > base::TimeDelta()); + DCHECK_GT(input->GetDataSize(), 0); + + if (output_timestamp_helper_.base_timestamp() == kNoTimestamp()) + output_timestamp_helper_.SetBaseTimestamp(input->GetTimestamp()); + + if (output_timestamp_helper_.base_timestamp() > input->GetTimestamp()) { + DVLOG(1) << "Input timestamp is before the base timestamp."; + return false; + } + + base::TimeDelta timestamp = input->GetTimestamp(); + base::TimeDelta expected_timestamp = output_timestamp_helper_.GetTimestamp(); + base::TimeDelta delta = timestamp - expected_timestamp; + + if (std::abs(delta.InMilliseconds()) > kMaxTimeDeltaInMilliseconds) { + DVLOG(1) << "Timestamp delta too large: " << delta.InMicroseconds() << "us"; + return false; + } + + int bytes_to_fill = 0; + if (delta != base::TimeDelta()) + bytes_to_fill = output_timestamp_helper_.GetBytesToTarget(timestamp); + + if (bytes_to_fill == 0 || std::abs(bytes_to_fill) < min_gap_size_) { + AddOutputBuffer(input); + return true; + } + + if (bytes_to_fill > 0) { + DVLOG(1) << "Gap detected @ " << expected_timestamp.InMicroseconds() + << " us: " << delta.InMicroseconds() << " us"; + + // Create a buffer with enough silence samples to fill the gap and + // add it to the output buffer. + scoped_refptr gap = new DataBuffer(bytes_to_fill); + gap->SetDataSize(bytes_to_fill); + memset(gap->GetWritableData(), 0, bytes_to_fill); + gap->SetTimestamp(expected_timestamp); + gap->SetDuration(output_timestamp_helper_.GetDuration(bytes_to_fill)); + AddOutputBuffer(gap); + + // Add the input buffer now that the gap has been filled. + AddOutputBuffer(input); + return true; + } + + int bytes_to_skip = -bytes_to_fill; + + DVLOG(1) << "Overlap detected @ " << expected_timestamp.InMicroseconds() + << " us: " << -delta.InMicroseconds() << " us"; + + if (input->GetDataSize() <= bytes_to_skip) { + DVLOG(1) << "Dropping whole buffer"; + return true; + } + + // Copy the trailing samples that do not overlap samples already output + // into a new buffer. Add this new buffer to the output queue. + // + // TODO(acolwell): Implement a cross-fade here so the transition is less + // jarring. + int new_buffer_size = input->GetDataSize() - bytes_to_skip; + + scoped_refptr new_buffer = new DataBuffer(new_buffer_size); + new_buffer->SetDataSize(new_buffer_size); + memcpy(new_buffer->GetWritableData(), + input->GetData() + bytes_to_skip, + new_buffer_size); + new_buffer->SetTimestamp(expected_timestamp); + new_buffer->SetDuration( + output_timestamp_helper_.GetDuration(new_buffer_size)); + AddOutputBuffer(new_buffer); + return true; +} + +bool AudioSplicer::HasNextBuffer() const { + return !output_buffers_.empty(); +} + +scoped_refptr AudioSplicer::GetNextBuffer() { + scoped_refptr ret = output_buffers_.front(); + output_buffers_.pop_front(); + return ret; +} + +void AudioSplicer::AddOutputBuffer(const scoped_refptr& buffer) { + output_timestamp_helper_.AddBytes(buffer->GetDataSize()); + output_buffers_.push_back(buffer); +} + +} // namespace media diff --git a/media/base/audio_splicer.h b/media/base/audio_splicer.h new file mode 100644 index 00000000000000..aa97faef469482 --- /dev/null +++ b/media/base/audio_splicer.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_AUDIO_SPLICER_H_ +#define MEDIA_BASE_AUDIO_SPLICER_H_ + +#include + +#include "base/memory/ref_counted.h" +#include "media/base/audio_timestamp_helper.h" +#include "media/base/media_export.h" + +namespace media { + +class AudioDecoderConfig; +class Buffer; + +// Helper class that handles filling gaps and resolving overlaps. +class MEDIA_EXPORT AudioSplicer { + public: + AudioSplicer(int bytes_per_frame, int samples_per_second); + ~AudioSplicer(); + + // Resets the splicer state by clearing the output buffers queue, + // and resetting the timestamp helper. + void Reset(); + + // Adds a new buffer full of samples or end of stream buffer to the splicer. + // Returns true if the buffer was accepted. False is returned if an error + // occurred. + bool AddInput(const scoped_refptr& input); + + // Returns true if the splicer has a buffer to return. + bool HasNextBuffer() const; + + // Removes the next buffer from the output buffer queue and returns it. + // This should only be called if HasNextBuffer() returns true. + scoped_refptr GetNextBuffer(); + + private: + void AddOutputBuffer(const scoped_refptr& buffer); + + AudioTimestampHelper output_timestamp_helper_; + + // Minimum gap size needed before the splicer will take action to + // fill a gap. This avoids periodically inserting and then dropping samples + // when the buffer timestamps are slightly off because of timestamp rounding + // in the source content. + int min_gap_size_; + + std::deque > output_buffers_; + bool received_end_of_stream_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(AudioSplicer); +}; + +} // namespace media + +#endif diff --git a/media/base/audio_splicer_unittest.cc b/media/base/audio_splicer_unittest.cc new file mode 100644 index 00000000000000..2096789f04bb84 --- /dev/null +++ b/media/base/audio_splicer_unittest.cc @@ -0,0 +1,366 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "media/base/audio_splicer.h" +#include "media/base/audio_timestamp_helper.h" +#include "media/base/buffers.h" +#include "media/base/data_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +static const int kBytesPerFrame = 4; +static const int kDefaultSampleRate = 44100; +static const int kDefaultBufferSize = 100 * kBytesPerFrame; + +class AudioSplicerTest : public ::testing::Test { + public: + AudioSplicerTest() + : splicer_(kBytesPerFrame, kDefaultSampleRate), + input_timestamp_helper_(kBytesPerFrame, kDefaultSampleRate) { + input_timestamp_helper_.SetBaseTimestamp(base::TimeDelta()); + } + + scoped_refptr GetNextInputBuffer(uint8 value) { + return GetNextInputBuffer(value, kDefaultBufferSize); + } + + scoped_refptr GetNextInputBuffer(uint8 value, int size) { + scoped_refptr buffer = new DataBuffer(size); + buffer->SetDataSize(size); + memset(buffer->GetWritableData(), value, buffer->GetDataSize()); + buffer->SetTimestamp(input_timestamp_helper_.GetTimestamp()); + buffer->SetDuration( + input_timestamp_helper_.GetDuration(buffer->GetDataSize())); + input_timestamp_helper_.AddBytes(buffer->GetDataSize()); + return buffer; + } + + bool VerifyData(const uint8* data, int size, int value) { + for (int i = 0; i < size; ++i) { + if (data[i] != value) + return false; + } + return true; + } + + protected: + AudioSplicer splicer_; + AudioTimestampHelper input_timestamp_helper_; + + DISALLOW_COPY_AND_ASSIGN(AudioSplicerTest); +}; + +TEST_F(AudioSplicerTest, PassThru) { + EXPECT_FALSE(splicer_.HasNextBuffer()); + + // Test single buffer pass-thru behavior. + scoped_refptr input_1 = GetNextInputBuffer(1); + EXPECT_TRUE(splicer_.AddInput(input_1)); + EXPECT_TRUE(splicer_.HasNextBuffer()); + + scoped_refptr output_1 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp()); + EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration()); + EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize()); + + // Test that multiple buffers can be queued in the splicer. + scoped_refptr input_2 = GetNextInputBuffer(2); + scoped_refptr input_3 = GetNextInputBuffer(3); + EXPECT_TRUE(splicer_.AddInput(input_2)); + EXPECT_TRUE(splicer_.AddInput(input_3)); + EXPECT_TRUE(splicer_.HasNextBuffer()); + + scoped_refptr output_2 = splicer_.GetNextBuffer(); + EXPECT_TRUE(splicer_.HasNextBuffer()); + EXPECT_EQ(input_2->GetTimestamp(), output_2->GetTimestamp()); + EXPECT_EQ(input_2->GetDuration(), output_2->GetDuration()); + EXPECT_EQ(input_2->GetDataSize(), output_2->GetDataSize()); + + scoped_refptr output_3 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + EXPECT_EQ(input_3->GetTimestamp(), output_3->GetTimestamp()); + EXPECT_EQ(input_3->GetDuration(), output_3->GetDuration()); + EXPECT_EQ(input_3->GetDataSize(), output_3->GetDataSize()); +} + +TEST_F(AudioSplicerTest, Reset) { + scoped_refptr input_1 = GetNextInputBuffer(1); + EXPECT_TRUE(splicer_.AddInput(input_1)); + EXPECT_TRUE(splicer_.HasNextBuffer()); + + splicer_.Reset(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + + // Add some bytes to the timestamp helper so that the + // next buffer starts many frames beyond the end of + // |input_1|. This is to make sure that Reset() actually + // clears its state and doesn't try to insert a gap. + input_timestamp_helper_.AddBytes(100 * kBytesPerFrame); + + // Verify that a new input buffer passes through as expected. + scoped_refptr input_2 = GetNextInputBuffer(2); + EXPECT_TRUE(splicer_.AddInput(input_2)); + EXPECT_TRUE(splicer_.HasNextBuffer()); + + scoped_refptr output_2 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + EXPECT_EQ(input_2->GetTimestamp(), output_2->GetTimestamp()); + EXPECT_EQ(input_2->GetDuration(), output_2->GetDuration()); + EXPECT_EQ(input_2->GetDataSize(), output_2->GetDataSize()); +} + +TEST_F(AudioSplicerTest, EndOfStream) { + scoped_refptr input_1 = GetNextInputBuffer(1); + scoped_refptr input_2 = new DataBuffer(0); // End of stream. + scoped_refptr input_3 = GetNextInputBuffer(2); + EXPECT_TRUE(input_2->IsEndOfStream()); + + EXPECT_TRUE(splicer_.AddInput(input_1)); + EXPECT_TRUE(splicer_.AddInput(input_2)); + EXPECT_TRUE(splicer_.HasNextBuffer()); + + scoped_refptr output_1 = splicer_.GetNextBuffer(); + scoped_refptr output_2 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp()); + EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration()); + EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize()); + + EXPECT_TRUE(output_2->IsEndOfStream()); + + // Verify that buffers can be added again after Reset(). + splicer_.Reset(); + EXPECT_TRUE(splicer_.AddInput(input_3)); + scoped_refptr output_3 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + EXPECT_EQ(input_3->GetTimestamp(), output_3->GetTimestamp()); + EXPECT_EQ(input_3->GetDuration(), output_3->GetDuration()); + EXPECT_EQ(input_3->GetDataSize(), output_3->GetDataSize()); +} + + +// Test the gap insertion code. +// +--------------+ +--------------+ +// |11111111111111| |22222222222222| +// +--------------+ +--------------+ +// Results in: +// +--------------+----+--------------+ +// |11111111111111|0000|22222222222222| +// +--------------+----+--------------+ +TEST_F(AudioSplicerTest, GapInsertion) { + scoped_refptr input_1 = GetNextInputBuffer(1); + + // Add bytes to the timestamp helper so that the next buffer + // will have a starting timestamp that indicates a gap is + // present. + const int kGapSize = 7 * kBytesPerFrame; + input_timestamp_helper_.AddBytes(kGapSize); + scoped_refptr input_2 = GetNextInputBuffer(2); + + EXPECT_TRUE(splicer_.AddInput(input_1)); + EXPECT_TRUE(splicer_.AddInput(input_2)); + + // Verify that a gap buffer is generated. + EXPECT_TRUE(splicer_.HasNextBuffer()); + scoped_refptr output_1 = splicer_.GetNextBuffer(); + scoped_refptr output_2 = splicer_.GetNextBuffer(); + scoped_refptr output_3 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + + // Verify that the first input buffer passed through unmodified. + EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp()); + EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration()); + EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize()); + EXPECT_TRUE(VerifyData(output_1->GetData(), output_1->GetDataSize(), 1)); + + // Verify the contents of the gap buffer. + base::TimeDelta gap_timestamp = + input_1->GetTimestamp() + input_1->GetDuration(); + base::TimeDelta gap_duration = input_2->GetTimestamp() - gap_timestamp; + EXPECT_GT(gap_duration, base::TimeDelta()); + EXPECT_EQ(gap_timestamp, output_2->GetTimestamp()); + EXPECT_EQ(gap_duration, output_2->GetDuration()); + EXPECT_EQ(kGapSize, output_2->GetDataSize()); + EXPECT_TRUE(VerifyData(output_2->GetData(), output_2->GetDataSize(), 0)); + + // Verify that the second input buffer passed through unmodified. + EXPECT_EQ(input_2->GetTimestamp(), output_3->GetTimestamp()); + EXPECT_EQ(input_2->GetDuration(), output_3->GetDuration()); + EXPECT_EQ(input_2->GetDataSize(), output_3->GetDataSize()); + EXPECT_TRUE(VerifyData(output_3->GetData(), output_3->GetDataSize(), 2)); +} + + +// Test that an error is signalled when the gap between input buffers is +// too large. +TEST_F(AudioSplicerTest, GapTooLarge) { + scoped_refptr input_1 = GetNextInputBuffer(1); + + // Add a seconds worth of bytes so that an unacceptably large + // gap exists between |input_1| and |input_2|. + const int kGapSize = kDefaultSampleRate * kBytesPerFrame; + input_timestamp_helper_.AddBytes(kGapSize); + scoped_refptr input_2 = GetNextInputBuffer(2); + + EXPECT_TRUE(splicer_.AddInput(input_1)); + EXPECT_FALSE(splicer_.AddInput(input_2)); + + EXPECT_TRUE(splicer_.HasNextBuffer()); + scoped_refptr output_1 = splicer_.GetNextBuffer(); + + // Verify that the first input buffer passed through unmodified. + EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp()); + EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration()); + EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize()); + EXPECT_TRUE(VerifyData(output_1->GetData(), output_1->GetDataSize(), 1)); + + // Verify that the second buffer is not available. + EXPECT_FALSE(splicer_.HasNextBuffer()); + + // Reset the timestamp helper so it can generate a buffer that is + // right after |input_1|. + input_timestamp_helper_.SetBaseTimestamp( + input_1->GetTimestamp() + input_1->GetDuration()); + + // Verify that valid buffers are still accepted. + scoped_refptr input_3 = GetNextInputBuffer(3); + EXPECT_TRUE(splicer_.AddInput(input_3)); + EXPECT_TRUE(splicer_.HasNextBuffer()); + scoped_refptr output_2 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + EXPECT_EQ(input_3->GetTimestamp(), output_2->GetTimestamp()); + EXPECT_EQ(input_3->GetDuration(), output_2->GetDuration()); + EXPECT_EQ(input_3->GetDataSize(), output_2->GetDataSize()); + EXPECT_TRUE(VerifyData(output_2->GetData(), output_2->GetDataSize(), 3)); +} + + +// Verifies that an error is signalled if AddInput() is called +// with a timestamp that is earlier than the first buffer added. +TEST_F(AudioSplicerTest, BufferAddedBeforeBase) { + input_timestamp_helper_.SetBaseTimestamp( + base::TimeDelta::FromMicroseconds(10)); + scoped_refptr input_1 = GetNextInputBuffer(1); + + // Reset the timestamp helper so the next buffer will have a timestamp earlier + // than |input_1|. + input_timestamp_helper_.SetBaseTimestamp(base::TimeDelta::FromSeconds(0)); + scoped_refptr input_2 = GetNextInputBuffer(1); + + EXPECT_GT(input_1->GetTimestamp(), input_2->GetTimestamp()); + EXPECT_TRUE(splicer_.AddInput(input_1)); + EXPECT_FALSE(splicer_.AddInput(input_2)); +} + + +// Test when one buffer partially overlaps another. +// +--------------+ +// |11111111111111| +// +--------------+ +// +--------------+ +// |22222222222222| +// +--------------+ +// Results in: +// +--------------+----------+ +// |11111111111111|2222222222| +// +--------------+----------+ +TEST_F(AudioSplicerTest, PartialOverlap) { + scoped_refptr input_1 = GetNextInputBuffer(1); + + // Reset timestamp helper so that the next buffer will have a + // timestamp that starts in the middle of |input_1|. + const int kOverlapSize = input_1->GetDataSize() / 4; + input_timestamp_helper_.SetBaseTimestamp(input_1->GetTimestamp()); + input_timestamp_helper_.AddBytes(input_1->GetDataSize() - kOverlapSize); + + scoped_refptr input_2 = GetNextInputBuffer(2); + + EXPECT_TRUE(splicer_.AddInput(input_1)); + EXPECT_TRUE(splicer_.AddInput(input_2)); + + EXPECT_TRUE(splicer_.HasNextBuffer()); + scoped_refptr output_1 = splicer_.GetNextBuffer(); + scoped_refptr output_2 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + + // Verify that the first input buffer passed through unmodified. + EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp()); + EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration()); + EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize()); + EXPECT_TRUE(VerifyData(output_1->GetData(), output_1->GetDataSize(), 1)); + + + // Verify that the second input buffer was truncated to only contain + // the samples that are after the end of |input_1|. + base::TimeDelta expected_timestamp = + input_1->GetTimestamp() + input_1->GetDuration(); + base::TimeDelta expected_duration = + (input_2->GetTimestamp() + input_2->GetDuration()) - expected_timestamp; + EXPECT_EQ(expected_timestamp, output_2->GetTimestamp()); + EXPECT_EQ(expected_duration, output_2->GetDuration()); + EXPECT_EQ(input_2->GetDataSize() - kOverlapSize, output_2->GetDataSize()); + EXPECT_TRUE(VerifyData(output_2->GetData(), output_2->GetDataSize(), 2)); +} + + +// Test that an input buffer that is completely overlapped by a buffer +// that was already added is dropped. +// +--------------+ +// |11111111111111| +// +--------------+ +// +-----+ +// |22222| +// +-----+ +// +-------------+ +// |3333333333333| +// +-------------+ +// Results in: +// +--------------+-------------+ +// |11111111111111|3333333333333| +// +--------------+-------------+ +TEST_F(AudioSplicerTest, DropBuffer) { + scoped_refptr input_1 = GetNextInputBuffer(1); + + // Reset timestamp helper so that the next buffer will have a + // timestamp that starts in the middle of |input_1|. + const int kOverlapOffset = input_1->GetDataSize() / 2; + const int kOverlapSize = input_1->GetDataSize() / 4; + input_timestamp_helper_.SetBaseTimestamp(input_1->GetTimestamp()); + input_timestamp_helper_.AddBytes(kOverlapOffset); + + scoped_refptr input_2 = GetNextInputBuffer(2, kOverlapSize); + + // Reset the timestamp helper so the next buffer will be right after + // |input_1|. + input_timestamp_helper_.SetBaseTimestamp(input_1->GetTimestamp()); + input_timestamp_helper_.AddBytes(input_1->GetDataSize()); + scoped_refptr input_3 = GetNextInputBuffer(3); + + EXPECT_TRUE(splicer_.AddInput(input_1)); + EXPECT_TRUE(splicer_.AddInput(input_2)); + EXPECT_TRUE(splicer_.AddInput(input_3)); + + EXPECT_TRUE(splicer_.HasNextBuffer()); + scoped_refptr output_1 = splicer_.GetNextBuffer(); + scoped_refptr output_2 = splicer_.GetNextBuffer(); + EXPECT_FALSE(splicer_.HasNextBuffer()); + + // Verify that the first input buffer passed through unmodified. + EXPECT_EQ(input_1->GetTimestamp(), output_1->GetTimestamp()); + EXPECT_EQ(input_1->GetDuration(), output_1->GetDuration()); + EXPECT_EQ(input_1->GetDataSize(), output_1->GetDataSize()); + EXPECT_TRUE(VerifyData(output_1->GetData(), output_1->GetDataSize(), 1)); + + // Verify that the second output buffer only contains + // the samples that are in |input_3|. + EXPECT_EQ(input_3->GetTimestamp(), output_2->GetTimestamp()); + EXPECT_EQ(input_3->GetDuration(), output_2->GetDuration()); + EXPECT_EQ(input_3->GetDataSize(), output_2->GetDataSize()); + EXPECT_TRUE(VerifyData(output_2->GetData(), output_2->GetDataSize(), 3)); +} + +} // namespace media diff --git a/media/base/audio_timestamp_helper.cc b/media/base/audio_timestamp_helper.cc new file mode 100644 index 00000000000000..a3f37c4b83c2fb --- /dev/null +++ b/media/base/audio_timestamp_helper.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/audio_timestamp_helper.h" + +#include "base/logging.h" +#include "media/base/buffers.h" + +namespace media { + +AudioTimestampHelper::AudioTimestampHelper(int bytes_per_frame, + int samples_per_second) + : bytes_per_frame_(bytes_per_frame), + base_timestamp_(kNoTimestamp()), + frame_count_(0) { + DCHECK_GT(bytes_per_frame, 0); + DCHECK_GT(samples_per_second, 0); + double fps = samples_per_second; + microseconds_per_frame_ = base::Time::kMicrosecondsPerSecond / fps; +} + +void AudioTimestampHelper::SetBaseTimestamp(base::TimeDelta base_timestamp) { + base_timestamp_ = base_timestamp; + frame_count_ = 0; +} + +base::TimeDelta AudioTimestampHelper::base_timestamp() const { + return base_timestamp_; +} + +void AudioTimestampHelper::AddBytes(int byte_count) { + DCHECK_GE(byte_count, 0); + DCHECK(base_timestamp_ != kNoTimestamp()); + DCHECK_EQ(byte_count % bytes_per_frame_, 0); + frame_count_ += byte_count / bytes_per_frame_; +} + +base::TimeDelta AudioTimestampHelper::GetTimestamp() const { + return ComputeTimestamp(frame_count_); +} + +base::TimeDelta AudioTimestampHelper::GetDuration(int byte_count) const { + DCHECK_GE(byte_count, 0); + DCHECK_EQ(byte_count % bytes_per_frame_, 0); + int frames = byte_count / bytes_per_frame_; + base::TimeDelta end_timestamp = ComputeTimestamp(frame_count_ + frames); + return end_timestamp - GetTimestamp(); +} + +int64 AudioTimestampHelper::GetBytesToTarget( + base::TimeDelta target) const { + DCHECK(base_timestamp_ != kNoTimestamp()); + DCHECK(target >= base_timestamp_); + + int64 delta_in_us = (target - GetTimestamp()).InMicroseconds(); + if (delta_in_us == 0) + return 0; + + // Compute a timestamp relative to |base_timestamp_| since timestamps + // created from |frame_count_| are computed relative to this base. + // This ensures that the time to frame computation here is the proper inverse + // of the frame to time computation in ComputeTimestamp(). + base::TimeDelta delta_from_base = target - base_timestamp_; + + // Compute frame count for the time delta. This computation rounds to + // the nearest whole number of frames. + double threshold = microseconds_per_frame_ / 2; + int64 target_frame_count = + (delta_from_base.InMicroseconds() + threshold) / microseconds_per_frame_; + return bytes_per_frame_ * (target_frame_count - frame_count_); +} + +base::TimeDelta AudioTimestampHelper::ComputeTimestamp( + int64 frame_count) const { + DCHECK_GE(frame_count, 0); + DCHECK(base_timestamp_ != kNoTimestamp()); + double frames_us = microseconds_per_frame_ * frame_count; + return base_timestamp_ + base::TimeDelta::FromMicroseconds(frames_us); +} + +} // namespace media diff --git a/media/base/audio_timestamp_helper.h b/media/base/audio_timestamp_helper.h new file mode 100644 index 00000000000000..4b38be7b96f679 --- /dev/null +++ b/media/base/audio_timestamp_helper.h @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_AUDIO_TIMESTAMP_HELPER_H_ +#define MEDIA_BASE_AUDIO_TIMESTAMP_HELPER_H_ + +#include "base/time.h" +#include "media/base/media_export.h" + +namespace media { + +// Generates timestamps for a sequence of audio sample bytes. This class should +// be used any place timestamps need to be calculated for a sequence of audio +// samples. It helps avoid timestamps inaccuracies caused by rounding/truncation +// in repeated sample count to timestamp conversions. +// +// The class is constructed with bytes per frame and samples_per_second +// information so that it can convert audio sample byte counts into timestamps. +// After the object is constructed, SetBaseTimestamp() must be called to specify +// the starting timestamp of the audio sequence. As audio samples are received, +// their byte counts are added to AddBytes(). These byte counts are +// accumulated by this class so GetTimestamp() can be used to determine the +// timestamp for the samples that have been added. GetDuration() calculates +// the proper duration values for samples added to the current timestamp. +// GetBytesToTarget() determines the number of bytes that need to be +// added/removed from the accumulated bytes to reach a target timestamp. +class MEDIA_EXPORT AudioTimestampHelper { + public: + AudioTimestampHelper(int bytes_per_frame, int samples_per_second); + + // Sets the base timestamp to |base_timestamp| and the sets count to 0. + void SetBaseTimestamp(base::TimeDelta base_timestamp); + + base::TimeDelta base_timestamp() const; + + // Adds sample bytes to the frame counter. + // + // Note: SetBaseTimestamp() must be called with a value other than + // kNoTimestamp() before this method can be called. + void AddBytes(int byte_count); + + // Get the current timestamp. This value is computed from the base_timestamp() + // and the number of sample bytes that have been added so far. + base::TimeDelta GetTimestamp() const; + + // Gets the duration if |byte_count| bytes were added to the current + // timestamp reported by GetTimestamp(). This method ensures that + // (GetTimestamp() + GetDuration(n)) will equal the timestamp that + // GetTimestamp() will return if AddBytes(n) is called. + base::TimeDelta GetDuration(int byte_count) const; + + // Returns the number of bytes needed to reach the target timestamp. + // + // Note: |target| must be >= |base_timestamp_|. + int64 GetBytesToTarget(base::TimeDelta target) const; + + private: + base::TimeDelta ComputeTimestamp(int64 frame_count) const; + + int bytes_per_frame_; + double microseconds_per_frame_; + + base::TimeDelta base_timestamp_; + + // Number of frames accumulated by byte counts passed to AddBytes() calls. + int64 frame_count_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(AudioTimestampHelper); +}; + +} // namespace media + +#endif diff --git a/media/base/audio_timestamp_helper_unittest.cc b/media/base/audio_timestamp_helper_unittest.cc new file mode 100644 index 00000000000000..5f5bb4e57074d6 --- /dev/null +++ b/media/base/audio_timestamp_helper_unittest.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/audio_timestamp_helper.h" +#include "media/base/buffers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +static const int kBytesPerFrame = 4; +static const int kDefaultSampleRate = 44100; + +class AudioTimestampHelperTest : public ::testing::Test { + public: + AudioTimestampHelperTest() + : helper_(kBytesPerFrame, kDefaultSampleRate) { + helper_.SetBaseTimestamp(base::TimeDelta()); + } + + // Adds bytes to the helper and returns the current timestamp in microseconds. + int64 AddBytes(int bytes) { + helper_.AddBytes(bytes); + return helper_.GetTimestamp().InMicroseconds(); + } + + int64 BytesToTarget(int target_in_microseconds) { + return helper_.GetBytesToTarget( + base::TimeDelta::FromMicroseconds(target_in_microseconds)); + } + + void TestGetBytesToTargetRange(int byte_count, int start, int end) { + for (int i = start; i <= end; ++i) + EXPECT_EQ(byte_count,BytesToTarget(i)) << " Failure for timestamp " + << i << " us."; + } + + protected: + AudioTimestampHelper helper_; + + DISALLOW_COPY_AND_ASSIGN(AudioTimestampHelperTest); +}; + +TEST_F(AudioTimestampHelperTest, Basic) { + EXPECT_EQ(0, helper_.GetTimestamp().InMicroseconds()); + + // Verify that the output timestamp is always rounded down to the + // nearest microsecond. 1 frame @ 44100 is ~22.67573 microseconds, + // which is why the timestamp sometimes increments by 23 microseconds + // and other times it increments by 22 microseconds. + EXPECT_EQ(0, AddBytes(0)); + EXPECT_EQ(22, AddBytes(kBytesPerFrame)); + EXPECT_EQ(45, AddBytes(kBytesPerFrame)); + EXPECT_EQ(68, AddBytes(kBytesPerFrame)); + EXPECT_EQ(90, AddBytes(kBytesPerFrame)); + EXPECT_EQ(113, AddBytes(kBytesPerFrame)); + + // Verify that adding bytes one frame at a time matches the timestamp returned + // if the same number of bytes are added all at once. + base::TimeDelta timestamp_1 = helper_.GetTimestamp(); + helper_.SetBaseTimestamp(kNoTimestamp()); + EXPECT_TRUE(kNoTimestamp() == helper_.base_timestamp()); + helper_.SetBaseTimestamp(base::TimeDelta()); + EXPECT_EQ(0, helper_.GetTimestamp().InMicroseconds()); + + helper_.AddBytes(5 * kBytesPerFrame); + EXPECT_EQ(113, helper_.GetTimestamp().InMicroseconds()); + EXPECT_TRUE(timestamp_1 == helper_.GetTimestamp()); +} + + +TEST_F(AudioTimestampHelperTest, GetDuration) { + helper_.SetBaseTimestamp(base::TimeDelta::FromMicroseconds(100)); + + int byte_count = 5 * kBytesPerFrame; + int64 expected_durations[] = { 113, 113, 114, 113, 113, 114 }; + for (size_t i = 0; i < arraysize(expected_durations); ++i) { + base::TimeDelta duration = helper_.GetDuration(byte_count); + EXPECT_EQ(expected_durations[i], duration.InMicroseconds()); + + base::TimeDelta timestamp_1 = helper_.GetTimestamp() + duration; + helper_.AddBytes(byte_count); + base::TimeDelta timestamp_2 = helper_.GetTimestamp(); + EXPECT_TRUE(timestamp_1 == timestamp_2); + } +} + +TEST_F(AudioTimestampHelperTest, GetBytesToTarget) { + // Verify GetBytesToTarget() rounding behavior. + // 1 frame @ 44100 is ~22.67573 microseconds, + + // Test values less than half of the frame duration. + TestGetBytesToTargetRange(0, 0, 11); + + // Test values between half the frame duration & the + // full frame duration. + TestGetBytesToTargetRange(kBytesPerFrame, 12, 22); + + // Verify that the same number of bytes is returned up + // to the next half a frame. + TestGetBytesToTargetRange(kBytesPerFrame, 23, 34); + + // Verify the next 3 ranges. + TestGetBytesToTargetRange(2 * kBytesPerFrame, 35, 56); + TestGetBytesToTargetRange(3 * kBytesPerFrame, 57, 79); + TestGetBytesToTargetRange(4 * kBytesPerFrame, 80, 102); + TestGetBytesToTargetRange(5 * kBytesPerFrame, 103, 124); + + + // Add bytes to the helper so negative byte counts can + // be tested. + helper_.AddBytes(5 * kBytesPerFrame); + + // Note: The timestamp ranges must match the positive values + // tested above to verify that the code is rounding properly. + TestGetBytesToTargetRange(0 * kBytesPerFrame, 103, 124); + TestGetBytesToTargetRange(-1 * kBytesPerFrame, 80, 102); + TestGetBytesToTargetRange(-2 * kBytesPerFrame, 57, 79); + TestGetBytesToTargetRange(-3 * kBytesPerFrame, 35, 56); + TestGetBytesToTargetRange(-4 * kBytesPerFrame, 12, 34); + TestGetBytesToTargetRange(-5 * kBytesPerFrame, 0, 11); +} + +} // namespace media diff --git a/media/filters/audio_renderer_impl.cc b/media/filters/audio_renderer_impl.cc index 6aa554c888acbd..c07048161d9967 100644 --- a/media/filters/audio_renderer_impl.cc +++ b/media/filters/audio_renderer_impl.cc @@ -15,6 +15,7 @@ #include "base/logging.h" #include "base/message_loop_proxy.h" #include "media/audio/audio_util.h" +#include "media/base/audio_splicer.h" #include "media/base/bind_to_loop.h" #include "media/base/demuxer_stream.h" #include "media/base/media_switches.h" @@ -137,6 +138,8 @@ void AudioRendererImpl::Preroll(base::TimeDelta time, rendered_end_of_stream_ = false; preroll_aborted_ = false; + splicer_->Reset(); + // |algorithm_| will request more reads. algorithm_->FlushBuffers(); earliest_end_time_ = base::Time::Now(); @@ -265,6 +268,10 @@ void AudioRendererImpl::OnDecoderInitDone( return; } + int channels = ChannelLayoutToChannelCount(decoder_->channel_layout()); + int bytes_per_frame = channels * decoder_->bits_per_channel() / 8; + splicer_.reset(new AudioSplicer(bytes_per_frame, sample_rate)); + // We're all good! Continue initializing the rest of the audio renderer based // on the decoder format. algorithm_.reset(new AudioRendererAlgorithm()); @@ -330,6 +337,28 @@ void AudioRendererImpl::DecodedAudioReady(AudioDecoder::Status status, DCHECK_EQ(status, AudioDecoder::kOk); DCHECK(buffer); + if (!splicer_->AddInput(buffer)) { + HandleAbortedReadOrDecodeError(true); + return; + } + + if (!splicer_->HasNextBuffer()) { + ScheduleRead_Locked(); + return; + } + + bool need_another_buffer = false; + while (splicer_->HasNextBuffer()) + need_another_buffer = HandleSplicerBuffer(splicer_->GetNextBuffer()); + + if (!need_another_buffer) + return; + + ScheduleRead_Locked(); +} + +bool AudioRendererImpl::HandleSplicerBuffer( + const scoped_refptr& buffer) { if (buffer->IsEndOfStream()) { received_end_of_stream_ = true; @@ -342,35 +371,35 @@ void AudioRendererImpl::DecodedAudioReady(AudioDecoder::Status status, switch (state_) { case kUninitialized: NOTREACHED(); - return; + return false; case kPaused: if (!buffer->IsEndOfStream()) algorithm_->EnqueueBuffer(buffer); DCHECK(!pending_read_); base::ResetAndReturn(&pause_cb_).Run(); - return; + return false; case kPrerolling: - if (IsBeforePrerollTime(buffer)) { - ScheduleRead_Locked(); - return; - } + if (IsBeforePrerollTime(buffer)) + return true; + if (!buffer->IsEndOfStream()) { algorithm_->EnqueueBuffer(buffer); if (!algorithm_->IsQueueFull()) - return; + return false; } state_ = kPaused; base::ResetAndReturn(&preroll_cb_).Run(PIPELINE_OK); - return; + return false; case kPlaying: case kUnderflow: case kRebuffering: if (!buffer->IsEndOfStream()) algorithm_->EnqueueBuffer(buffer); - return; + return false; case kStopped: - return; + return false; } + return false; } void AudioRendererImpl::ScheduleRead_Locked() { diff --git a/media/filters/audio_renderer_impl.h b/media/filters/audio_renderer_impl.h index 11498fd51071e7..1d716be84d36f0 100644 --- a/media/filters/audio_renderer_impl.h +++ b/media/filters/audio_renderer_impl.h @@ -33,6 +33,8 @@ namespace media { +class AudioSplicer; + class MEDIA_EXPORT AudioRendererImpl : public AudioRenderer, NON_EXPORTED_BASE(public media::AudioRendererSink::RenderCallback) { @@ -80,6 +82,10 @@ class MEDIA_EXPORT AudioRendererImpl void DecodedAudioReady(AudioDecoder::Status status, const scoped_refptr& buffer); + // Handles buffers that come out of |splicer_|. + // Returns true if more buffers are needed. + bool HandleSplicerBuffer(const scoped_refptr& buffer); + // Helper functions for AudioDecoder::Status values passed to // DecodedAudioReady(). void HandleAbortedReadOrDecodeError(bool is_decode_error); @@ -147,6 +153,8 @@ class MEDIA_EXPORT AudioRendererImpl // Audio decoder. scoped_refptr decoder_; + scoped_ptr splicer_; + // The sink (destination) for rendered audio. |sink_| must only be accessed // on the pipeline thread (verify with |pipeline_thread_checker_|). |sink_| // must never be called under |lock_| or the 3-way thread bridge between the diff --git a/media/filters/audio_renderer_impl_unittest.cc b/media/filters/audio_renderer_impl_unittest.cc index cfc31449fb44dd..4f741d90b2e97d 100644 --- a/media/filters/audio_renderer_impl_unittest.cc +++ b/media/filters/audio_renderer_impl_unittest.cc @@ -7,6 +7,7 @@ #include "base/gtest_prod_util.h" #include "base/message_loop.h" #include "base/stl_util.h" +#include "media/base/audio_timestamp_helper.h" #include "media/base/data_buffer.h" #include "media/base/gmock_callback_support.h" #include "media/base/mock_audio_renderer_sink.h" @@ -101,6 +102,10 @@ class AudioRendererImplTest : public ::testing::Test { InitializeWithStatus(PIPELINE_OK); message_loop_.RunUntilIdle(); + int channels = ChannelLayoutToChannelCount(decoder_->channel_layout()); + int bytes_per_frame = decoder_->bits_per_channel() * channels / 8; + next_timestamp_.reset(new AudioTimestampHelper( + bytes_per_frame, decoder_->samples_per_second())); } void InitializeWithStatus(PipelineStatus expected) { @@ -123,6 +128,8 @@ class AudioRendererImplTest : public ::testing::Test { } void Preroll() { + next_timestamp_->SetBaseTimestamp(base::TimeDelta()); + // Fill entire buffer to complete prerolling. EXPECT_CALL(*decoder_, Read(_)); renderer_->Preroll(base::TimeDelta(), NewPrerollCB()); @@ -137,7 +144,7 @@ class AudioRendererImplTest : public ::testing::Test { } void Preroll(base::TimeDelta preroll_time) { - next_timestamp_ = preroll_time; + next_timestamp_->SetBaseTimestamp(preroll_time); // Fill entire buffer to complete prerolling. EXPECT_CALL(*decoder_, Read(_)); @@ -155,10 +162,9 @@ class AudioRendererImplTest : public ::testing::Test { buffer->SetDataSize(size); memset(buffer->GetWritableData(), kPlayingAudio, buffer->GetDataSize()); - buffer->SetTimestamp(next_timestamp_); - int64 bps = decoder_->bits_per_channel() * decoder_->samples_per_second(); - buffer->SetDuration(base::TimeDelta::FromMilliseconds(8000 * size / bps)); - next_timestamp_ += buffer->GetDuration(); + buffer->SetTimestamp(next_timestamp_->GetTimestamp()); + buffer->SetDuration(next_timestamp_->GetDuration(buffer->GetDataSize())); + next_timestamp_->AddBytes(buffer->GetDataSize()); base::ResetAndReturn(&read_cb_).Run(AudioDecoder::kOk, buffer); } @@ -229,7 +235,7 @@ class AudioRendererImplTest : public ::testing::Test { scoped_refptr decoder_; AudioRendererImpl::AudioDecoderList decoders_; AudioDecoder::ReadCB read_cb_; - base::TimeDelta next_timestamp_; + scoped_ptr next_timestamp_; MessageLoop message_loop_; private: diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc index a3e0a61c6dbfd9..5384d63c48874a 100644 --- a/media/filters/ffmpeg_audio_decoder.cc +++ b/media/filters/ffmpeg_audio_decoder.cc @@ -9,6 +9,7 @@ #include "base/location.h" #include "base/message_loop_proxy.h" #include "media/base/audio_decoder_config.h" +#include "media/base/audio_timestamp_helper.h" #include "media/base/data_buffer.h" #include "media/base/decoder_buffer.h" #include "media/base/demuxer.h" @@ -41,8 +42,6 @@ FFmpegAudioDecoder::FFmpegAudioDecoder( channel_layout_(CHANNEL_LAYOUT_NONE), samples_per_second_(0), bytes_per_frame_(0), - output_timestamp_base_(kNoTimestamp()), - total_frames_decoded_(0), last_input_timestamp_(kNoTimestamp()), output_bytes_to_drop_(0), av_frame_(NULL) { @@ -88,16 +87,7 @@ void FFmpegAudioDecoder::Reset(const base::Closure& closure) { FFmpegAudioDecoder::~FFmpegAudioDecoder() { // TODO(scherkus): should we require Stop() to be called? this might end up // getting called on a random thread due to refcounting. - if (codec_context_) { - av_free(codec_context_->extradata); - avcodec_close(codec_context_); - av_free(codec_context_); - } - - if (av_frame_) { - av_free(av_frame_); - av_frame_ = NULL; - } + ReleaseFFmpegResources(); } void FFmpegAudioDecoder::DoInitialize( @@ -114,56 +104,19 @@ void FFmpegAudioDecoder::DoInitialize( } demuxer_stream_ = stream; - const AudioDecoderConfig& config = stream->audio_decoder_config(); - statistics_cb_ = statistics_cb; - - // TODO(scherkus): this check should go in Pipeline prior to creating - // decoder objects. - if (!config.IsValidConfig()) { - DLOG(ERROR) << "Invalid audio stream -" - << " codec: " << config.codec() - << " channel layout: " << config.channel_layout() - << " bits per channel: " << config.bits_per_channel() - << " samples per second: " << config.samples_per_second(); - - status_cb.Run(DECODER_ERROR_NOT_SUPPORTED); - return; - } - if (config.is_encrypted()) { - DLOG(ERROR) << "Encrypted audio stream not supported"; + if (!ConfigureDecoder()) { status_cb.Run(DECODER_ERROR_NOT_SUPPORTED); return; } - // Initialize AVCodecContext structure. - codec_context_ = avcodec_alloc_context3(NULL); - AudioDecoderConfigToAVCodecContext(config, codec_context_); - - AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); - if (!codec || avcodec_open2(codec_context_, codec, NULL) < 0) { - DLOG(ERROR) << "Could not initialize audio decoder: " - << codec_context_->codec_id; - - status_cb.Run(DECODER_ERROR_NOT_SUPPORTED); - return; - } - - // Success! - av_frame_ = avcodec_alloc_frame(); - bits_per_channel_ = config.bits_per_channel(); - channel_layout_ = config.channel_layout(); - samples_per_second_ = config.samples_per_second(); - bytes_per_frame_ = codec_context_->channels * bits_per_channel_ / 8; + statistics_cb_ = statistics_cb; status_cb.Run(PIPELINE_OK); } void FFmpegAudioDecoder::DoReset(const base::Closure& closure) { avcodec_flush_buffers(codec_context_); - output_timestamp_base_ = kNoTimestamp(); - total_frames_decoded_ = 0; - last_input_timestamp_ = kNoTimestamp(); - output_bytes_to_drop_ = 0; + ResetTimestampState(); queued_audio_.clear(); closure.Run(); } @@ -200,19 +153,46 @@ void FFmpegAudioDecoder::DoDecodeBuffer( DCHECK(queued_audio_.empty()); DCHECK_EQ(status != DemuxerStream::kOk, !input) << status; - if (status != DemuxerStream::kOk) { - // TODO(acolwell): Add support for reinitializing the decoder when - // |status| == kConfigChanged. For now we just trigger a decode error. - AudioDecoder::Status decoder_status = - (status == DemuxerStream::kAborted) ? kAborted : kDecodeError; - base::ResetAndReturn(&read_cb_).Run(decoder_status, NULL); + if (status == DemuxerStream::kAborted) { + DCHECK(!input); + base::ResetAndReturn(&read_cb_).Run(kAborted, NULL); return; } + if (status == DemuxerStream::kConfigChanged) { + DCHECK(!input); + + // Send a "end of stream" buffer to the decode loop + // to output any remaining data still in the decoder. + RunDecodeLoop(DecoderBuffer::CreateEOSBuffer(), true); + + DVLOG(1) << "Config changed."; + + if (!ConfigureDecoder()) { + base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); + return; + } + + ResetTimestampState(); + + if (queued_audio_.empty()) { + ReadFromDemuxerStream(); + return; + } + + base::ResetAndReturn(&read_cb_).Run( + queued_audio_.front().status, queued_audio_.front().buffer); + queued_audio_.pop_front(); + return; + } + + DCHECK_EQ(status, DemuxerStream::kOk); + DCHECK(input); + // Make sure we are notified if http://crbug.com/49709 returns. Issue also // occurs with some damaged files. if (!input->IsEndOfStream() && input->GetTimestamp() == kNoTimestamp() && - output_timestamp_base_ == kNoTimestamp()) { + output_timestamp_helper_->base_timestamp() == kNoTimestamp()) { DVLOG(1) << "Received a buffer without timestamps!"; base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); return; @@ -244,6 +224,104 @@ void FFmpegAudioDecoder::DoDecodeBuffer( } } + RunDecodeLoop(input, false); + + // We exhausted the provided packet, but it wasn't enough for a frame. Ask + // for more data in order to fulfill this read. + if (queued_audio_.empty()) { + ReadFromDemuxerStream(); + return; + } + + // Execute callback to return the first frame we decoded. + base::ResetAndReturn(&read_cb_).Run( + queued_audio_.front().status, queued_audio_.front().buffer); + queued_audio_.pop_front(); +} + +void FFmpegAudioDecoder::ReadFromDemuxerStream() { + DCHECK(!read_cb_.is_null()); + + demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::DoDecodeBuffer, this)); +} + +bool FFmpegAudioDecoder::ConfigureDecoder() { + const AudioDecoderConfig& config = demuxer_stream_->audio_decoder_config(); + + if (!config.IsValidConfig()) { + DLOG(ERROR) << "Invalid audio stream -" + << " codec: " << config.codec() + << " channel layout: " << config.channel_layout() + << " bits per channel: " << config.bits_per_channel() + << " samples per second: " << config.samples_per_second(); + return false; + } + + if (config.is_encrypted()) { + DLOG(ERROR) << "Encrypted audio stream not supported"; + return false; + } + + if (codec_context_ && + (bits_per_channel_ != config.bits_per_channel() || + channel_layout_ != config.channel_layout() || + samples_per_second_ != config.samples_per_second())) { + DVLOG(1) << "Unsupported config change :"; + DVLOG(1) << "\tbits_per_channel : " << bits_per_channel_ + << " -> " << config.bits_per_channel(); + DVLOG(1) << "\tchannel_layout : " << channel_layout_ + << " -> " << config.channel_layout(); + DVLOG(1) << "\tsample_rate : " << samples_per_second_ + << " -> " << config.samples_per_second(); + return false; + } + + // Release existing decoder resources if necessary. + ReleaseFFmpegResources(); + + // Initialize AVCodecContext structure. + codec_context_ = avcodec_alloc_context3(NULL); + AudioDecoderConfigToAVCodecContext(config, codec_context_); + + AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); + if (!codec || avcodec_open2(codec_context_, codec, NULL) < 0) { + DLOG(ERROR) << "Could not initialize audio decoder: " + << codec_context_->codec_id; + return false; + } + + // Success! + av_frame_ = avcodec_alloc_frame(); + bits_per_channel_ = config.bits_per_channel(); + channel_layout_ = config.channel_layout(); + samples_per_second_ = config.samples_per_second(); + output_timestamp_helper_.reset(new AudioTimestampHelper( + config.bytes_per_frame(), config.samples_per_second())); + return true; +} + +void FFmpegAudioDecoder::ReleaseFFmpegResources() { + if (codec_context_) { + av_free(codec_context_->extradata); + avcodec_close(codec_context_); + av_free(codec_context_); + } + + if (av_frame_) { + av_free(av_frame_); + av_frame_ = NULL; + } +} + +void FFmpegAudioDecoder::ResetTimestampState() { + output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp()); + last_input_timestamp_ = kNoTimestamp(); + output_bytes_to_drop_ = 0; +} + +void FFmpegAudioDecoder::RunDecodeLoop( + const scoped_refptr& input, + bool skip_eos_append) { AVPacket packet; av_init_packet(&packet); packet.data = const_cast(input->GetData()); @@ -283,15 +361,16 @@ void FFmpegAudioDecoder::DoDecodeBuffer( packet.size -= result; packet.data += result; - if (output_timestamp_base_ == kNoTimestamp() && !input->IsEndOfStream()) { + if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() && + !input->IsEndOfStream()) { DCHECK(input->GetTimestamp() != kNoTimestamp()); if (output_bytes_to_drop_ > 0) { // Currently Vorbis is the only codec that causes us to drop samples. // If we have to drop samples it always means the timeline starts at 0. - DCHECK(is_vorbis); - output_timestamp_base_ = base::TimeDelta(); + DCHECK_EQ(codec_context_->codec_id, CODEC_ID_VORBIS); + output_timestamp_helper_->SetBaseTimestamp(base::TimeDelta()); } else { - output_timestamp_base_ = input->GetTimestamp(); + output_timestamp_helper_->SetBaseTimestamp(input->GetTimestamp()); } } @@ -325,18 +404,14 @@ void FFmpegAudioDecoder::DoDecodeBuffer( } if (decoded_audio_size > 0) { - DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0) - << "Decoder didn't output full frames"; - // Copy the audio samples into an output buffer. output = new DataBuffer(decoded_audio_data, decoded_audio_size); - - base::TimeDelta timestamp = GetNextOutputTimestamp(); - total_frames_decoded_ += decoded_audio_size / bytes_per_frame_; - - output->SetTimestamp(timestamp); - output->SetDuration(GetNextOutputTimestamp() - timestamp); - } else if (IsEndOfStream(result, decoded_audio_size, input)) { + output->SetTimestamp(output_timestamp_helper_->GetTimestamp()); + output->SetDuration( + output_timestamp_helper_->GetDuration(decoded_audio_size)); + output_timestamp_helper_->AddBytes(decoded_audio_size); + } else if (IsEndOfStream(result, decoded_audio_size, input) && + !skip_eos_append) { DCHECK_EQ(packet.size, 0); // Create an end of stream output buffer. output = new DataBuffer(0); @@ -354,31 +429,6 @@ void FFmpegAudioDecoder::DoDecodeBuffer( statistics_cb_.Run(statistics); } } while (packet.size > 0); - - // We exhausted the provided packet, but it wasn't enough for a frame. Ask - // for more data in order to fulfill this read. - if (queued_audio_.empty()) { - ReadFromDemuxerStream(); - return; - } - - // Execute callback to return the first frame we decoded. - base::ResetAndReturn(&read_cb_).Run( - queued_audio_.front().status, queued_audio_.front().buffer); - queued_audio_.pop_front(); -} - -void FFmpegAudioDecoder::ReadFromDemuxerStream() { - DCHECK(!read_cb_.is_null()); - - demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::DoDecodeBuffer, this)); -} - -base::TimeDelta FFmpegAudioDecoder::GetNextOutputTimestamp() const { - DCHECK(output_timestamp_base_ != kNoTimestamp()); - double decoded_us = (total_frames_decoded_ / samples_per_second_) * - base::Time::kMicrosecondsPerSecond; - return output_timestamp_base_ + base::TimeDelta::FromMicroseconds(decoded_us); } } // namespace media diff --git a/media/filters/ffmpeg_audio_decoder.h b/media/filters/ffmpeg_audio_decoder.h index ef6a9581268a14..ce096ee996479d 100644 --- a/media/filters/ffmpeg_audio_decoder.h +++ b/media/filters/ffmpeg_audio_decoder.h @@ -20,6 +20,7 @@ class MessageLoopProxy; namespace media { +class AudioTimestampHelper; class DataBuffer; class DecoderBuffer; struct QueuedAudioBuffer; @@ -55,10 +56,11 @@ class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder { // Reads from the demuxer stream with corresponding callback method. void ReadFromDemuxerStream(); - // Returns the timestamp that should be used for the next buffer returned - // via |read_cb_|. It is calculated from |output_timestamp_base_| and - // |total_frames_decoded_|. - base::TimeDelta GetNextOutputTimestamp() const; + bool ConfigureDecoder(); + void ReleaseFFmpegResources(); + void ResetTimestampState(); + void RunDecodeLoop(const scoped_refptr& input, + bool skip_eos_append); scoped_refptr message_loop_; @@ -72,9 +74,8 @@ class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder { int samples_per_second_; // Used for computing output timestamps. + scoped_ptr output_timestamp_helper_; int bytes_per_frame_; - base::TimeDelta output_timestamp_base_; - double total_frames_decoded_; base::TimeDelta last_input_timestamp_; // Number of output sample bytes to drop before generating diff --git a/media/media.gyp b/media/media.gyp index 428b1bbe541c59..963ee56ef12c90 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -180,6 +180,10 @@ 'base/audio_renderer_mixer.h', 'base/audio_renderer_mixer_input.cc', 'base/audio_renderer_mixer_input.h', + 'base/audio_splicer.cc', + 'base/audio_splicer.h', + 'base/audio_timestamp_helper.cc', + 'base/audio_timestamp_helper.h', 'base/bind_to_loop.h', 'base/bitstream_buffer.h', 'base/bit_reader.cc', @@ -671,6 +675,8 @@ 'base/audio_pull_fifo_unittest.cc', 'base/audio_renderer_mixer_input_unittest.cc', 'base/audio_renderer_mixer_unittest.cc', + 'base/audio_splicer_unittest.cc', + 'base/audio_timestamp_helper_unittest.cc', 'base/bit_reader_unittest.cc', 'base/bind_to_loop_unittest.cc', 'base/buffers_unittest.cc',