diff options
author | dalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-31 20:35:25 +0000 |
---|---|---|
committer | dalecurtis@google.com <dalecurtis@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-31 20:35:25 +0000 |
commit | f42d6a10c8645a83220341ef9cfd22b36b3b49b5 (patch) | |
tree | db76016f0b892d43961d1ead916de9f79534fa62 | |
parent | 665e4fbc3d7de70829f6db36a955f63f9c4d7f9f (diff) | |
download | chromium_src-f42d6a10c8645a83220341ef9cfd22b36b3b49b5.zip chromium_src-f42d6a10c8645a83220341ef9cfd22b36b3b49b5.tar.gz chromium_src-f42d6a10c8645a83220341ef9cfd22b36b3b49b5.tar.bz2 |
Introducing DecoderBuffer and general Buffer cleanup.
FFmpeg expects data to be padded and aligned in a certain way. It's
currently possible to do this incorrectly and introduce dangerous issues.
I enforce padding and alignment by introducing a new Buffer called
DecoderBuffer and forcing DemuxerStream::Read to only accept it for
transfer into decoders.
DecoderBuffer allocates all memory through av_malloc (which takes care of
alignment) with the appropriate padding size (except for Android, which
doesn't care about this).
Along the way it was necessary to clean up a large smattering of code to
replace usage of DataBuffer with DecoderBuffer.
I've rolled in several cleanup actions as well:
- Moved DecryptConfig from Buffer to DecoderBuffer.
- Replaced AVPacketBuffer and av_dup_packet with a DecoderBuffer::CopyFrom.
- Fixed a resultant issue with FFmpegBitStreamConverter after removing the
av_dup_packet functionality. Removed some unsupported bitstream filters.
- Reduce TestDataUtil::ReadTestDataFile() to a single method returning a
DecoderBuffer so unit tests will always have safe buffers.
- Replace new DataBuffer(0)/new DecoderBuffer(0) w/
DecoderBuffer::CreateEOSBuffer.
- Remove extraneous IsEndOfStream check from FFmpegAudioDecoder.
BUG=129843
TEST=media_unittests + valgrind, layout tests.
Review URL: https://chromiumcodereview.appspot.com/10447035
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@139857 0039d316-1c4b-4281-b951-d872f2087c98
36 files changed, 461 insertions, 326 deletions
diff --git a/media/audio/win/audio_low_latency_output_win_unittest.cc b/media/audio/win/audio_low_latency_output_win_unittest.cc index 298aaf7..c7e4ab5 100644 --- a/media/audio/win/audio_low_latency_output_win_unittest.cc +++ b/media/audio/win/audio_low_latency_output_win_unittest.cc @@ -18,6 +18,7 @@ #include "media/audio/audio_manager.h" #include "media/audio/audio_util.h" #include "media/audio/win/audio_low_latency_output_win.h" +#include "media/base/decoder_buffer.h" #include "media/base/seekable_buffer.h" #include "media/base/test_data_util.h" #include "testing/gmock_mutant.h" @@ -70,7 +71,7 @@ class ReadFromFileAudioSource : public AudioOutputStream::AudioSourceCallback { elements_to_write_(0) { // Reads a test file from media/test/data directory and stores it in // a scoped_array. - ReadTestDataFile(name, &file_, &file_size_); + file_ = ReadTestDataFile(name); // Creates an array that will store delta times between callbacks. // The content of this array will be written to a text file at @@ -115,8 +116,8 @@ class ReadFromFileAudioSource : public AudioOutputStream::AudioSourceCallback { // Use samples read from a data file and fill up the audio buffer // provided to us in the callback. - if (pos_ + static_cast<int>(max_size) > file_size_) - max_size = file_size_ - pos_; + if (pos_ + static_cast<int>(max_size) > file_size()) + max_size = file_size() - pos_; if (max_size) { memcpy(dest, &file_[pos_], max_size); pos_ += max_size; @@ -126,12 +127,11 @@ class ReadFromFileAudioSource : public AudioOutputStream::AudioSourceCallback { virtual void OnError(AudioOutputStream* stream, int code) {} - int file_size() { return file_size_; } + int file_size() { return file_->GetDataSize(); } private: - scoped_array<uint8> file_; + scoped_refptr<DecoderBuffer> file_; scoped_array<int> delta_times_; - int file_size_; int pos_; base::Time previous_call_time_; FILE* text_file_; diff --git a/media/base/buffers.cc b/media/base/buffers.cc index 743626a..63802c6 100644 --- a/media/base/buffers.cc +++ b/media/base/buffers.cc @@ -17,8 +17,4 @@ bool Buffer::IsEndOfStream() const { return GetData() == NULL; } -const DecryptConfig* Buffer::GetDecryptConfig() const { - return NULL; -} - } // namespace media diff --git a/media/base/buffers.h b/media/base/buffers.h index f4e4264..d14f4db 100644 --- a/media/base/buffers.h +++ b/media/base/buffers.h @@ -33,8 +33,6 @@ namespace media { -class DecryptConfig; - // Indicates an invalid or missing timestamp. MEDIA_EXPORT extern inline base::TimeDelta kNoTimestamp() { return base::TimeDelta::FromMicroseconds(kint64min); @@ -56,9 +54,6 @@ class MEDIA_EXPORT Buffer : public base::RefCountedThreadSafe<Buffer> { // If there's no data in this buffer, it represents end of stream. bool IsEndOfStream() const; - // Return DecryptConfig if buffer is encrypted, or NULL otherwise. - virtual const DecryptConfig* GetDecryptConfig() const; - base::TimeDelta GetTimestamp() const { return timestamp_; } diff --git a/media/base/buffers_unittest.cc b/media/base/buffers_unittest.cc index 6cf0e09..a96b40b 100644 --- a/media/base/buffers_unittest.cc +++ b/media/base/buffers_unittest.cc @@ -82,9 +82,4 @@ TEST(BufferTest, IsEndOfStream) { EXPECT_FALSE(buffer->IsEndOfStream()); } -TEST(BufferTest, GetDecryptConfig) { - scoped_refptr<TestBuffer> buffer = new TestBuffer(); - EXPECT_FALSE(buffer->GetDecryptConfig()); -} - } // namespace media diff --git a/media/base/data_buffer.cc b/media/base/data_buffer.cc index 9a491e3..56d858c 100644 --- a/media/base/data_buffer.cc +++ b/media/base/data_buffer.cc @@ -4,10 +4,6 @@ #include "base/logging.h" #include "media/base/data_buffer.h" -#include "media/base/decrypt_config.h" -#if !defined(OS_ANDROID) -#include "media/ffmpeg/ffmpeg_common.h" -#endif namespace media { @@ -20,38 +16,32 @@ DataBuffer::DataBuffer(scoped_array<uint8> buffer, int buffer_size) DataBuffer::DataBuffer(int buffer_size) : Buffer(base::TimeDelta(), base::TimeDelta()), - data_(new uint8[buffer_size]), buffer_size_(buffer_size), data_size_(0) { - CHECK(data_.get()) << "DataBuffer ctor failed to allocate memory"; - - // Prevent arbitrary pointers. - if (buffer_size == 0) - data_.reset(NULL); + Initialize(); } DataBuffer::DataBuffer(const uint8* data, int data_size) : Buffer(base::TimeDelta(), base::TimeDelta()), - buffer_size_(0), - data_size_(0) { - if (data_size == 0) - return; + buffer_size_(data_size), + data_size_(data_size) { + Initialize(); + memcpy(data_.get(), data, data_size_); +} - int padding_size = 0; -#if !defined(OS_ANDROID) - // FFmpeg assumes all input buffers are padded with this value. - padding_size = FF_INPUT_BUFFER_PADDING_SIZE; -#endif +DataBuffer::~DataBuffer() {} + +void DataBuffer::Initialize() { + // Prevent arbitrary pointers. + if (buffer_size_ <= 0) { + buffer_size_ = data_size_ = 0; + data_.reset(); + return; + } - buffer_size_ = data_size + padding_size; data_.reset(new uint8[buffer_size_]); - memcpy(data_.get(), data, data_size); - memset(data_.get() + data_size, 0, padding_size); - SetDataSize(data_size); } -DataBuffer::~DataBuffer() {} - scoped_refptr<DataBuffer> DataBuffer::CopyFrom(const uint8* data, int data_size) { return make_scoped_refptr(new DataBuffer(data, data_size)); @@ -65,10 +55,6 @@ int DataBuffer::GetDataSize() const { return data_size_; } -const DecryptConfig* DataBuffer::GetDecryptConfig() const { - return decrypt_config_.get(); -} - uint8* DataBuffer::GetWritableData() { return data_.get(); } @@ -82,8 +68,4 @@ int DataBuffer::GetBufferSize() const { return buffer_size_; } -void DataBuffer::SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config) { - decrypt_config_ = decrypt_config.Pass(); -} - } // namespace media diff --git a/media/base/data_buffer.h b/media/base/data_buffer.h index 136bb85..af05e81 100644 --- a/media/base/data_buffer.h +++ b/media/base/data_buffer.h @@ -30,7 +30,6 @@ class MEDIA_EXPORT DataBuffer : public Buffer { // Buffer implementation. virtual const uint8* GetData() const OVERRIDE; virtual int GetDataSize() const OVERRIDE; - virtual const DecryptConfig* GetDecryptConfig() const OVERRIDE; // Returns a read-write pointer to the buffer data. virtual uint8* GetWritableData(); @@ -42,21 +41,18 @@ class MEDIA_EXPORT DataBuffer : public Buffer { // Returns the size of the underlying buffer. virtual int GetBufferSize() const; - virtual void SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config); - protected: // Copies from [data,data+size) to owned array. DataBuffer(const uint8* data, int size); virtual ~DataBuffer(); private: - // Helper method to allocate |data_| with at least |buffer_size| bytes. - void AllocateBuffer(int buffer_size); + // Constructor helper method for memory allocations. + void Initialize(); scoped_array<uint8> data_; int buffer_size_; int data_size_; - scoped_ptr<DecryptConfig> decrypt_config_; DISALLOW_COPY_AND_ASSIGN(DataBuffer); }; diff --git a/media/base/decoder_buffer.cc b/media/base/decoder_buffer.cc new file mode 100644 index 0000000..527fb65 --- /dev/null +++ b/media/base/decoder_buffer.cc @@ -0,0 +1,87 @@ +// 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/decoder_buffer.h" + +#include "base/logging.h" +#include "media/base/decrypt_config.h" +#if !defined(OS_ANDROID) +#include "media/ffmpeg/ffmpeg_common.h" +#endif + +namespace media { + +DecoderBuffer::DecoderBuffer(int buffer_size) + : Buffer(base::TimeDelta(), base::TimeDelta()), + buffer_size_(buffer_size) { + Initialize(); +} + +DecoderBuffer::DecoderBuffer(const uint8* data, int buffer_size) + : Buffer(base::TimeDelta(), base::TimeDelta()), + buffer_size_(buffer_size) { + // Prevent invalid allocations. Also used to create end of stream buffers. + if (!data) { + buffer_size_ = 0; + data_ = NULL; + return; + } + + Initialize(); + memcpy(data_, data, buffer_size_); +} + +DecoderBuffer::~DecoderBuffer() { +#if !defined(OS_ANDROID) + av_free(data_); +#else + delete[] data_; +#endif +} + +void DecoderBuffer::Initialize() { + DCHECK_GE(buffer_size_, 0); +#if !defined(OS_ANDROID) + // Why FF_INPUT_BUFFER_PADDING_SIZE? FFmpeg assumes all input buffers are + // padded. Using av_malloc with padding ensures FFmpeg only recieves data + // padded and aligned to its specifications. + data_ = reinterpret_cast<uint8*>( + av_malloc(buffer_size_ + FF_INPUT_BUFFER_PADDING_SIZE)); + memset(data_ + buffer_size_, 0, FF_INPUT_BUFFER_PADDING_SIZE); +#else + data_ = new uint8[buffer_size_]; +#endif +} + +scoped_refptr<DecoderBuffer> DecoderBuffer::CopyFrom(const uint8* data, + int data_size) { + DCHECK(data); + return make_scoped_refptr(new DecoderBuffer(data, data_size)); +} + +scoped_refptr<DecoderBuffer> DecoderBuffer::CreateEOSBuffer() { + return make_scoped_refptr(new DecoderBuffer(NULL, 0)); +} + +const uint8* DecoderBuffer::GetData() const { + return data_; +} + +int DecoderBuffer::GetDataSize() const { + return buffer_size_; +} + +uint8* DecoderBuffer::GetWritableData() { + return data_; +} + +const DecryptConfig* DecoderBuffer::GetDecryptConfig() const { + return decrypt_config_.get(); +} + +void DecoderBuffer::SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config) { + decrypt_config_ = decrypt_config.Pass(); +} + +} // namespace media diff --git a/media/base/decoder_buffer.h b/media/base/decoder_buffer.h new file mode 100644 index 0000000..e822de5 --- /dev/null +++ b/media/base/decoder_buffer.h @@ -0,0 +1,66 @@ +// 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. + +// A specialized buffer for interfacing with audio / video decoders. +// +// Specifically ensures that data is aligned and padded as necessary by the +// underlying decoding framework. On desktop platforms this means memory is +// allocated using FFmpeg with particular alignment and padding requirements. +// +// Also includes decoder specific functionality for decryption. + +#ifndef MEDIA_BASE_DECODER_BUFFER_H_ +#define MEDIA_BASE_DECODER_BUFFER_H_ + +#include "base/memory/scoped_ptr.h" +#include "media/base/buffers.h" +#include "media/base/decrypt_config.h" + +namespace media { + +class MEDIA_EXPORT DecoderBuffer : public Buffer { + public: + // Allocates buffer of size |buffer_size| >= 0. Buffer will be padded and + // aligned as necessary. + explicit DecoderBuffer(int buffer_size); + + // Create a DecoderBuffer whose |data_| is copied from |data|. Buffer will be + // padded and aligned as necessary. |data| must not be NULL and |size| >= 0. + static scoped_refptr<DecoderBuffer> CopyFrom(const uint8* data, int size); + + // Create a DecoderBuffer indicating we've reached end of stream. GetData() + // and GetWritableData() will return NULL and GetDataSize() will return 0. + static scoped_refptr<DecoderBuffer> CreateEOSBuffer(); + + // Buffer implementation. + virtual const uint8* GetData() const OVERRIDE; + virtual int GetDataSize() const OVERRIDE; + + // Returns a read-write pointer to the buffer data. + virtual uint8* GetWritableData(); + + virtual const DecryptConfig* GetDecryptConfig() const; + virtual void SetDecryptConfig(scoped_ptr<DecryptConfig> decrypt_config); + + protected: + // Allocates a buffer of size |size| >= 0 and copies |data| into it. Buffer + // will be padded and aligned as necessary. If |data| is NULL then |data_| is + // set to NULL and |buffer_size_| to 0. + DecoderBuffer(const uint8* data, int size); + virtual ~DecoderBuffer(); + + private: + int buffer_size_; + uint8* data_; + scoped_ptr<DecryptConfig> decrypt_config_; + + // Constructor helper method for memory allocations. + void Initialize(); + + DISALLOW_COPY_AND_ASSIGN(DecoderBuffer); +}; + +} // namespace media + +#endif // MEDIA_BASE_DECODER_BUFFER_H_ diff --git a/media/base/decoder_buffer_unittest.cc b/media/base/decoder_buffer_unittest.cc new file mode 100644 index 0000000..f7ec578 --- /dev/null +++ b/media/base/decoder_buffer_unittest.cc @@ -0,0 +1,103 @@ +// 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/string_util.h" +#include "media/base/decoder_buffer.h" +#if !defined(OS_ANDROID) +#include "media/ffmpeg/ffmpeg_common.h" +#endif + +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +TEST(DecoderBufferTest, Constructors) { + scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(0)); + EXPECT_TRUE(buffer->GetData()); + EXPECT_EQ(0, buffer->GetDataSize()); + EXPECT_FALSE(buffer->IsEndOfStream()); + + const int kTestSize = 10; + scoped_refptr<DecoderBuffer> buffer3(new DecoderBuffer(kTestSize)); + ASSERT_TRUE(buffer3); + EXPECT_EQ(kTestSize, buffer3->GetDataSize()); +} + +TEST(DecoderBufferTest, CreateEOSBuffer) { + scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CreateEOSBuffer()); + EXPECT_TRUE(buffer->IsEndOfStream()); + EXPECT_FALSE(buffer->GetData()); + EXPECT_EQ(0, buffer->GetDataSize()); +} + +TEST(DecoderBufferTest, CopyFrom) { + const uint8 kData[] = "hello"; + const int kDataSize = arraysize(kData); + scoped_refptr<DecoderBuffer> buffer2(DecoderBuffer::CopyFrom( + reinterpret_cast<const uint8*>(&kData), kDataSize)); + ASSERT_TRUE(buffer2); + EXPECT_NE(kData, buffer2->GetData()); + EXPECT_EQ(buffer2->GetDataSize(), kDataSize); + EXPECT_EQ(0, memcmp(buffer2->GetData(), kData, kDataSize)); + EXPECT_FALSE(buffer2->IsEndOfStream()); +} + +#if !defined(OS_ANDROID) +TEST(DecoderBufferTest, PaddingAlignment) { + const uint8 kData[] = "hello"; + const int kDataSize = arraysize(kData); + scoped_refptr<DecoderBuffer> buffer2(DecoderBuffer::CopyFrom( + reinterpret_cast<const uint8*>(&kData), kDataSize)); + ASSERT_TRUE(buffer2); + + // FFmpeg padding data should always be zeroed. + for(int i = 0; i < FF_INPUT_BUFFER_PADDING_SIZE; i++) + EXPECT_EQ((buffer2->GetData() + kDataSize)[i], 0); + + // If the data is padded correctly we should be able to read and write past + // the end of the data by FF_INPUT_BUFFER_PADDING_SIZE bytes without crashing + // or Valgrind/ASAN throwing errors. + const uint8 kFillChar = 0xFF; + memset( + buffer2->GetWritableData() + kDataSize, kFillChar, + FF_INPUT_BUFFER_PADDING_SIZE); + for(int i = 0; i < FF_INPUT_BUFFER_PADDING_SIZE; i++) + EXPECT_EQ((buffer2->GetData() + kDataSize)[i], kFillChar); + + // These alignments will need to be updated to match FFmpeg when it changes. +#if defined(ARCH_CPU_ARM_FAMILY) + // FFmpeg data should be aligned on a 16 byte boundary for ARM. + const int kDataAlignment = 16; +#else + // FFmpeg data should be aligned on a 32 byte boundary for x86. + const int kDataAlignment = 32; +#endif + EXPECT_EQ(0u, reinterpret_cast<uintptr_t>( + buffer2->GetData()) & (kDataAlignment - 1)); +} +#endif + +TEST(DecoderBufferTest, ReadingWriting) { + const char kData[] = "hello"; + const int kDataSize = arraysize(kData); + + scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(kDataSize)); + ASSERT_TRUE(buffer); + + uint8* data = buffer->GetWritableData(); + ASSERT_TRUE(data); + ASSERT_EQ(kDataSize, buffer->GetDataSize()); + memcpy(data, kData, kDataSize); + const uint8* read_only_data = buffer->GetData(); + ASSERT_EQ(data, read_only_data); + ASSERT_EQ(0, memcmp(read_only_data, kData, kDataSize)); + EXPECT_FALSE(buffer->IsEndOfStream()); +} + +TEST(DecoderBufferTest, GetDecryptConfig) { + scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(0)); + EXPECT_FALSE(buffer->GetDecryptConfig()); +} + +} // namespace media diff --git a/media/base/demuxer_stream.h b/media/base/demuxer_stream.h index 457818c..4168d04 100644 --- a/media/base/demuxer_stream.h +++ b/media/base/demuxer_stream.h @@ -12,7 +12,7 @@ namespace media { class AudioDecoderConfig; -class Buffer; +class DecoderBuffer; class VideoDecoderConfig; class MEDIA_EXPORT DemuxerStream @@ -30,7 +30,7 @@ class MEDIA_EXPORT DemuxerStream // Non-NULL buffer pointers will contain media data or signal the end of the // stream. A NULL pointer indicates an aborted Read(). This can happen if the // DemuxerStream gets flushed and doesn't have any more data to return. - typedef base::Callback<void(const scoped_refptr<Buffer>&)> ReadCB; + typedef base::Callback<void(const scoped_refptr<DecoderBuffer>&)> ReadCB; virtual void Read(const ReadCB& read_cb) = 0; // Returns the audio decoder configuration. It is an error to call this method diff --git a/media/base/mock_reader.h b/media/base/mock_reader.h index 6098362..b007684 100644 --- a/media/base/mock_reader.h +++ b/media/base/mock_reader.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -70,7 +70,7 @@ class MockReader }; // Commonly used reader types. -typedef MockReader<DemuxerStream, Buffer> DemuxerStreamReader; +typedef MockReader<DemuxerStream, DecoderBuffer> DemuxerStreamReader; } // namespace media diff --git a/media/base/stream_parser_buffer.cc b/media/base/stream_parser_buffer.cc index b6c3970..6b9a66d 100644 --- a/media/base/stream_parser_buffer.cc +++ b/media/base/stream_parser_buffer.cc @@ -26,7 +26,7 @@ base::TimeDelta StreamParserBuffer::GetEndTimestamp() const { StreamParserBuffer::StreamParserBuffer(const uint8* data, int data_size, bool is_keyframe) - : DataBuffer(data, data_size), + : DecoderBuffer(data, data_size), is_keyframe_(is_keyframe) { SetDuration(kNoTimestamp()); } diff --git a/media/base/stream_parser_buffer.h b/media/base/stream_parser_buffer.h index cdd01c5..fa1b944 100644 --- a/media/base/stream_parser_buffer.h +++ b/media/base/stream_parser_buffer.h @@ -5,12 +5,12 @@ #ifndef MEDIA_BASE_STREAM_PARSER_BUFFER_H_ #define MEDIA_BASE_STREAM_PARSER_BUFFER_H_ -#include "media/base/data_buffer.h" +#include "media/base/decoder_buffer.h" #include "media/base/media_export.h" namespace media { -class MEDIA_EXPORT StreamParserBuffer : public DataBuffer { +class MEDIA_EXPORT StreamParserBuffer : public DecoderBuffer { public: static scoped_refptr<StreamParserBuffer> CreateEOSBuffer(); static scoped_refptr<StreamParserBuffer> CopyFrom( diff --git a/media/base/test_data_util.cc b/media/base/test_data_util.cc index bb08175..4cba4a8 100644 --- a/media/base/test_data_util.cc +++ b/media/base/test_data_util.cc @@ -7,9 +7,7 @@ #include "base/file_util.h" #include "base/logging.h" #include "base/path_service.h" -#include "media/base/buffers.h" -#include "media/base/data_buffer.h" -#include "media/ffmpeg/ffmpeg_common.h" +#include "media/base/decoder_buffer.h" namespace media { @@ -24,8 +22,7 @@ std::string GetTestDataURL(const std::string& name) { return file_path.MaybeAsASCII(); } -void ReadTestDataFile(const std::string& name, scoped_array<uint8>* buffer, - int* size) { +scoped_refptr<DecoderBuffer> ReadTestDataFile(const std::string& name) { FilePath file_path; CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &file_path)); @@ -38,34 +35,14 @@ void ReadTestDataFile(const std::string& name, scoped_array<uint8>* buffer, CHECK(file_util::GetFileSize(file_path, &tmp)) << "Failed to get file size for '" << name << "'"; - // Why FF_INPUT_BUFFER_PADDING_SIZE? FFmpeg assumes all input buffers are - // padded. Since most of our test data is passed to FFmpeg, it makes sense - // to do the padding here instead of scattering it around test code. int file_size = static_cast<int>(tmp); - int padded_size = file_size + FF_INPUT_BUFFER_PADDING_SIZE; - buffer->reset(reinterpret_cast<uint8_t*>(new uint8[padded_size])); - memset(buffer->get(), 0, padded_size); - CHECK(file_size == file_util::ReadFile(file_path, - reinterpret_cast<char*>(buffer->get()), - file_size)) - << "Failed to read '" << name << "'"; - *size = file_size; -} - -void ReadTestDataFile(const std::string& name, - scoped_refptr<DataBuffer>* buffer) { - scoped_array<uint8> buf; - int buf_size; - ReadTestDataFile(name, &buf, &buf_size); - *buffer = new DataBuffer(buf.Pass(), buf_size); -} + scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(file_size)); + CHECK_EQ(file_size, file_util::ReadFile( + file_path, reinterpret_cast<char*>(buffer->GetWritableData()), file_size)) + << "Failed to read '" << name << "'"; -void ReadTestDataFile(const std::string& name, - scoped_refptr<Buffer>* buffer) { - scoped_refptr<DataBuffer> data_buffer; - ReadTestDataFile(name, &data_buffer); - *buffer = data_buffer; + return buffer; } } // namespace media diff --git a/media/base/test_data_util.h b/media/base/test_data_util.h index a132875..81efa80 100644 --- a/media/base/test_data_util.h +++ b/media/base/test_data_util.h @@ -13,36 +13,18 @@ namespace media { -class Buffer; -class DataBuffer; +class DecoderBuffer; // Returns a URL path for a file in the media/test/data directory. std::string GetTestDataURL(const std::string& name); // Reads a test file from media/test/data directory and stores it in -// a scoped_array. +// a DecoderBuffer. Use DecoderBuffer vs DataBuffer to ensure no matter +// what a test does, it's safe to use FFmpeg methods. // // |name| - The name of the file. // |buffer| - The contents of the file. -// |size| - The size of the buffer. -void ReadTestDataFile(const std::string& name, - scoped_array<uint8>* buffer, - int* size); - -// Reads a test file from media/test/data directory and stores it in -// a DataBuffer. -// -// |name| - The name of the file. -// |buffer| - The contents of the file. -void ReadTestDataFile(const std::string& name, - scoped_refptr<DataBuffer>* buffer); - -// Reads a test file from media/test/data directory and stores it in -// a Buffer. -// -// |name| - The name of the file. -// |buffer| - The contents of the file. -void ReadTestDataFile(const std::string& name, scoped_refptr<Buffer>* buffer); +scoped_refptr<DecoderBuffer> ReadTestDataFile(const std::string& name); } // namespace media diff --git a/media/base/video_decoder.h b/media/base/video_decoder.h index 61b626e..31589d1 100644 --- a/media/base/video_decoder.h +++ b/media/base/video_decoder.h @@ -13,7 +13,6 @@ namespace media { -class Buffer; class DemuxerStream; class VideoFrame; diff --git a/media/crypto/aes_decryptor.cc b/media/crypto/aes_decryptor.cc index 8d56d76..cbb75b3 100644 --- a/media/crypto/aes_decryptor.cc +++ b/media/crypto/aes_decryptor.cc @@ -9,8 +9,7 @@ #include "base/string_piece.h" #include "crypto/encryptor.h" #include "crypto/symmetric_key.h" -#include "media/base/buffers.h" -#include "media/base/data_buffer.h" +#include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" namespace media { @@ -19,10 +18,10 @@ namespace media { static const char kInitialCounter[] = "0000000000000000"; // Decrypt |input| using |key|. -// Return a scoped_refptr to a Buffer object with the decrypted data on success. -// Return a scoped_refptr to NULL if the data could not be decrypted. -static scoped_refptr<Buffer> DecryptData(const Buffer& input, - crypto::SymmetricKey* key) { +// Return a DecoderBuffer with the decrypted data if decryption succeeded. +// Return NULL if decryption failed. +static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input, + crypto::SymmetricKey* key) { CHECK(input.GetDataSize()); CHECK(key); @@ -43,9 +42,8 @@ static scoped_refptr<Buffer> DecryptData(const Buffer& input, return NULL; } - // TODO(xhwang): Implement a string based Buffer implementation to avoid - // data copying. - return DataBuffer::CopyFrom( + // TODO(xhwang): Find a way to avoid this data copy. + return DecoderBuffer::CopyFrom( reinterpret_cast<const uint8*>(decrypted_text.data()), decrypted_text.size()); } @@ -81,8 +79,8 @@ void AesDecryptor::AddKey(const uint8* key_id, int key_id_size, key_map_[key_id_string] = symmetric_key; } -scoped_refptr<Buffer> AesDecryptor::Decrypt( - const scoped_refptr<Buffer>& encrypted) { +scoped_refptr<DecoderBuffer> AesDecryptor::Decrypt( + const scoped_refptr<DecoderBuffer>& encrypted) { CHECK(encrypted->GetDecryptConfig()); const uint8* key_id = encrypted->GetDecryptConfig()->key_id(); const int key_id_size = encrypted->GetDecryptConfig()->key_id_size(); @@ -101,7 +99,7 @@ scoped_refptr<Buffer> AesDecryptor::Decrypt( key = found->second; } - scoped_refptr<Buffer> decrypted = DecryptData(*encrypted, key); + scoped_refptr<DecoderBuffer> decrypted = DecryptData(*encrypted, key); if (decrypted) { decrypted->SetTimestamp(encrypted->GetTimestamp()); diff --git a/media/crypto/aes_decryptor.h b/media/crypto/aes_decryptor.h index 212c9ac..d62528f 100644 --- a/media/crypto/aes_decryptor.h +++ b/media/crypto/aes_decryptor.h @@ -19,7 +19,7 @@ class SymmetricKey; namespace media { -class Buffer; +class DecoderBuffer; // Decrypts AES encrypted buffer into unencrypted buffer. class MEDIA_EXPORT AesDecryptor { @@ -35,9 +35,10 @@ class MEDIA_EXPORT AesDecryptor { const uint8* key, int key_size); // Decrypt |input| buffer. The |input| should not be NULL. - // Return a Buffer that contains decrypted data if decryption succeeded. + // Return a DecoderBuffer with the decrypted data if decryption succeeded. // Return NULL if decryption failed. - scoped_refptr<Buffer> Decrypt(const scoped_refptr<Buffer>& input); + scoped_refptr<DecoderBuffer> Decrypt( + const scoped_refptr<DecoderBuffer>& input); private: // KeyMap owns the crypto::SymmetricKey* and must delete them when they are diff --git a/media/crypto/aes_decryptor_unittest.cc b/media/crypto/aes_decryptor_unittest.cc index 70b6881..0b33f61 100644 --- a/media/crypto/aes_decryptor_unittest.cc +++ b/media/crypto/aes_decryptor_unittest.cc @@ -4,7 +4,7 @@ #include <string> -#include "media/base/data_buffer.h" +#include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" #include "media/crypto/aes_decryptor.h" #include "testing/gtest/include/gtest/gtest.h" @@ -28,7 +28,8 @@ static const unsigned char kKeyId2[] = "Key ID 2."; class AesDecryptorTest : public testing::Test { public: AesDecryptorTest() { - encrypted_data_ = DataBuffer::CopyFrom(kEncryptedData, kEncryptedDataSize); + encrypted_data_ = DecoderBuffer::CopyFrom( + kEncryptedData, kEncryptedDataSize); } protected: @@ -38,7 +39,8 @@ class AesDecryptorTest : public testing::Test { } void DecryptAndExpectToSucceed() { - scoped_refptr<Buffer> decrypted = decryptor_.Decrypt(encrypted_data_); + scoped_refptr<DecoderBuffer> decrypted = + decryptor_.Decrypt(encrypted_data_); ASSERT_TRUE(decrypted); int data_length = sizeof(kOriginalData) - 1; ASSERT_EQ(data_length, decrypted->GetDataSize()); @@ -46,11 +48,12 @@ class AesDecryptorTest : public testing::Test { } void DecryptAndExpectToFail() { - scoped_refptr<Buffer> decrypted = decryptor_.Decrypt(encrypted_data_); + scoped_refptr<DecoderBuffer> decrypted = + decryptor_.Decrypt(encrypted_data_); EXPECT_FALSE(decrypted); } - scoped_refptr<DataBuffer> encrypted_data_; + scoped_refptr<DecoderBuffer> encrypted_data_; AesDecryptor decryptor_; }; diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index 8ec5161..39ceda4 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc @@ -189,7 +189,7 @@ void ChunkDemuxerStream::StartWaitingForSeek() { } for (ReadCBQueue::iterator it = read_cbs.begin(); it != read_cbs.end(); ++it) - it->Run(scoped_refptr<Buffer>()); + it->Run(NULL); } void ChunkDemuxerStream::Seek(base::TimeDelta time) { @@ -231,7 +231,7 @@ void ChunkDemuxerStream::Shutdown() { // Helper function that makes sure |read_cb| runs on |message_loop|. static void RunOnMessageLoop(const DemuxerStream::ReadCB& read_cb, MessageLoop* message_loop, - const scoped_refptr<Buffer>& buffer) { + const scoped_refptr<DecoderBuffer>& buffer) { if (MessageLoop::current() != message_loop) { message_loop->PostTask(FROM_HERE, base::Bind( &RunOnMessageLoop, read_cb, message_loop, buffer)); diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index aafaf28..a1de2d3 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -4,6 +4,7 @@ #include "base/bind.h" #include "media/base/audio_decoder_config.h" +#include "media/base/decoder_buffer.h" #include "media/base/mock_callback.h" #include "media/base/mock_demuxer_host.h" #include "media/base/test_data_util.h" @@ -70,7 +71,7 @@ MATCHER_P(HasTimestamp, timestamp_in_ms, "") { static void OnReadDone(const base::TimeDelta& expected_time, bool* called, - const scoped_refptr<Buffer>& buffer) { + const scoped_refptr<DecoderBuffer>& buffer) { EXPECT_EQ(expected_time, buffer->GetTimestamp()); *called = true; } @@ -115,63 +116,61 @@ class ChunkDemuxerTest : public testing::Test { void CreateInfoTracks(bool has_audio, bool has_video, bool video_content_encoded, scoped_array<uint8>* buffer, int* size) { - scoped_array<uint8> info; - int info_size = 0; - scoped_array<uint8> audio_track_entry; - int audio_track_entry_size = 0; - scoped_array<uint8> video_track_entry; - int video_track_entry_size = 0; - scoped_array<uint8> video_content_encodings; - int video_content_encodings_size = 0; + scoped_refptr<DecoderBuffer> info; + scoped_refptr<DecoderBuffer> audio_track_entry; + scoped_refptr<DecoderBuffer> video_track_entry; + scoped_refptr<DecoderBuffer> video_content_encodings; - ReadTestDataFile("webm_info_element", &info, &info_size); + info = ReadTestDataFile("webm_info_element"); int tracks_element_size = 0; if (has_audio) { - ReadTestDataFile("webm_vorbis_track_entry", &audio_track_entry, - &audio_track_entry_size); - tracks_element_size += audio_track_entry_size; + audio_track_entry = ReadTestDataFile("webm_vorbis_track_entry"); + tracks_element_size += audio_track_entry->GetDataSize(); } if (has_video) { - ReadTestDataFile("webm_vp8_track_entry", &video_track_entry, - &video_track_entry_size); - tracks_element_size += video_track_entry_size; + video_track_entry = ReadTestDataFile("webm_vp8_track_entry"); + tracks_element_size += video_track_entry->GetDataSize(); if (video_content_encoded) { - ReadTestDataFile("webm_content_encodings", &video_content_encodings, - &video_content_encodings_size); - tracks_element_size += video_content_encodings_size; + video_content_encodings = ReadTestDataFile("webm_content_encodings"); + tracks_element_size += video_content_encodings->GetDataSize(); } } - *size = info_size + kTracksHeaderSize + tracks_element_size; + *size = info->GetDataSize() + kTracksHeaderSize + tracks_element_size; buffer->reset(new uint8[*size]); uint8* buf = buffer->get(); - memcpy(buf, info.get(), info_size); - buf += info_size; + memcpy(buf, info->GetData(), info->GetDataSize()); + buf += info->GetDataSize(); memcpy(buf, kTracksHeader, kTracksHeaderSize); WriteInt64(buf + kTracksSizeOffset, tracks_element_size); buf += kTracksHeaderSize; if (has_audio) { - memcpy(buf, audio_track_entry.get(), audio_track_entry_size); - buf += audio_track_entry_size; + memcpy(buf, audio_track_entry->GetData(), + audio_track_entry->GetDataSize()); + buf += audio_track_entry->GetDataSize(); } if (has_video) { - memcpy(buf, video_track_entry.get(), video_track_entry_size); + memcpy(buf, video_track_entry->GetData(), + video_track_entry->GetDataSize()); if (video_content_encoded) { - memcpy(buf + video_track_entry_size, video_content_encodings.get(), - video_content_encodings_size); - video_track_entry_size += video_content_encodings_size; + memcpy(buf + video_track_entry->GetDataSize(), + video_content_encodings->GetData(), + video_content_encodings->GetDataSize()); WriteInt64(buf + kVideoTrackSizeOffset, - video_track_entry_size - kVideoTrackEntryHeaderSize); + video_track_entry->GetDataSize() + + video_content_encodings->GetDataSize() - + kVideoTrackEntryHeaderSize); + buf += video_content_encodings->GetDataSize(); } - buf += video_track_entry_size; + buf += video_track_entry->GetDataSize(); } } @@ -340,7 +339,7 @@ class ChunkDemuxerTest : public testing::Test { } } - MOCK_METHOD1(ReadDone, void(const scoped_refptr<Buffer>&)); + MOCK_METHOD1(ReadDone, void(const scoped_refptr<DecoderBuffer>&)); void ExpectRead(DemuxerStream* stream, int64 timestamp_in_ms) { EXPECT_CALL(*this, ReadDone(HasTimestamp(timestamp_in_ms))); @@ -365,9 +364,6 @@ class ChunkDemuxerTest : public testing::Test { bool ParseWebMFile(const std::string& filename, const BufferTimestamps* timestamps, const base::TimeDelta& duration) { - scoped_array<uint8> buffer; - int buffer_size = 0; - EXPECT_CALL(*client_, DemuxerOpened(_)); demuxer_->Initialize( &host_, CreateInitDoneCB(duration, PIPELINE_OK)); @@ -376,8 +372,8 @@ class ChunkDemuxerTest : public testing::Test { return false; // Read a WebM file into memory and send the data to the demuxer. - ReadTestDataFile(filename, &buffer, &buffer_size); - if (!AppendDataInPieces(buffer.get(), buffer_size, 512)) + scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename); + if (!AppendDataInPieces(buffer->GetData(), buffer->GetDataSize(), 512)) return false; scoped_refptr<DemuxerStream> audio = @@ -774,8 +770,8 @@ class EndOfStreamHelper { } private: - static void OnEndOfStreamReadDone(bool* called, - const scoped_refptr<Buffer>& buffer) { + static void OnEndOfStreamReadDone( + bool* called, const scoped_refptr<DecoderBuffer>& buffer) { EXPECT_TRUE(buffer->IsEndOfStream()); *called = true; } diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc index dc660e5..be750bf 100644 --- a/media/filters/ffmpeg_audio_decoder.cc +++ b/media/filters/ffmpeg_audio_decoder.cc @@ -7,6 +7,7 @@ #include "base/bind.h" #include "media/base/audio_decoder_config.h" #include "media/base/data_buffer.h" +#include "media/base/decoder_buffer.h" #include "media/base/demuxer.h" #include "media/base/pipeline.h" #include "media/ffmpeg/ffmpeg_common.h" @@ -168,7 +169,8 @@ void FFmpegAudioDecoder::DoRead(const ReadCB& read_cb) { ReadFromDemuxerStream(); } -void FFmpegAudioDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& input) { +void FFmpegAudioDecoder::DoDecodeBuffer( + const scoped_refptr<DecoderBuffer>& input) { DCHECK_EQ(MessageLoop::current(), message_loop_); DCHECK(!read_cb_.is_null()); @@ -190,13 +192,8 @@ void FFmpegAudioDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& input) { AVPacket packet; av_init_packet(&packet); - if (input->IsEndOfStream()) { - packet.data = NULL; - packet.size = 0; - } else { - packet.data = const_cast<uint8*>(input->GetData()); - packet.size = input->GetDataSize(); - } + packet.data = const_cast<uint8*>(input->GetData()); + packet.size = input->GetDataSize(); PipelineStatistics statistics; statistics.audio_bytes_decoded = input->GetDataSize(); @@ -256,7 +253,8 @@ void FFmpegAudioDecoder::ReadFromDemuxerStream() { demuxer_stream_->Read(base::Bind(&FFmpegAudioDecoder::DecodeBuffer, this)); } -void FFmpegAudioDecoder::DecodeBuffer(const scoped_refptr<Buffer>& buffer) { +void FFmpegAudioDecoder::DecodeBuffer( + const scoped_refptr<DecoderBuffer>& buffer) { // TODO(scherkus): fix FFmpegDemuxerStream::Read() to not execute our read // callback on the same execution stack so we can get rid of forced task post. message_loop_->PostTask(FROM_HERE, base::Bind( diff --git a/media/filters/ffmpeg_audio_decoder.h b/media/filters/ffmpeg_audio_decoder.h index 04739a2..5dbe895 100644 --- a/media/filters/ffmpeg_audio_decoder.h +++ b/media/filters/ffmpeg_audio_decoder.h @@ -16,6 +16,7 @@ struct AVCodecContext; namespace media { class DataBuffer; +class DecoderBuffer; class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder { public: @@ -41,11 +42,11 @@ class MEDIA_EXPORT FFmpegAudioDecoder : public AudioDecoder { const StatisticsCB& statistics_cb); void DoReset(const base::Closure& closure); void DoRead(const ReadCB& read_cb); - void DoDecodeBuffer(const scoped_refptr<Buffer>& input); + void DoDecodeBuffer(const scoped_refptr<DecoderBuffer>& input); // Reads from the demuxer stream with corresponding callback method. void ReadFromDemuxerStream(); - void DecodeBuffer(const scoped_refptr<Buffer>& buffer); + void DecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer); // Updates the output buffer's duration and timestamp based on the input // buffer. Will fall back to an estimated timestamp if the input lacks a diff --git a/media/filters/ffmpeg_audio_decoder_unittest.cc b/media/filters/ffmpeg_audio_decoder_unittest.cc index cbd1b10..b81b73b 100644 --- a/media/filters/ffmpeg_audio_decoder_unittest.cc +++ b/media/filters/ffmpeg_audio_decoder_unittest.cc @@ -7,7 +7,7 @@ #include "base/bind.h" #include "base/message_loop.h" #include "base/stringprintf.h" -#include "media/base/data_buffer.h" +#include "media/base/decoder_buffer.h" #include "media/base/mock_callback.h" #include "media/base/mock_filters.h" #include "media/base/test_data_util.h" @@ -35,14 +35,12 @@ class FFmpegAudioDecoderTest : public testing::Test { demuxer_(new StrictMock<MockDemuxerStream>()) { CHECK(FFmpegGlue::GetInstance()); - ReadTestDataFile("vorbis-extradata", - &vorbis_extradata_, - &vorbis_extradata_size_); + vorbis_extradata_ = ReadTestDataFile("vorbis-extradata"); // Refer to media/test/data/README for details on vorbis test data. for (int i = 0; i < 4; ++i) { - scoped_refptr<Buffer> buffer; - ReadTestDataFile(base::StringPrintf("vorbis-packet-%d", i), &buffer); + scoped_refptr<DecoderBuffer> buffer = + ReadTestDataFile(base::StringPrintf("vorbis-packet-%d", i)); if (i < 3) { buffer->SetTimestamp(base::TimeDelta()); @@ -55,14 +53,14 @@ class FFmpegAudioDecoderTest : public testing::Test { } // Push in an EOS buffer. - encoded_audio_.push_back(new DataBuffer(0)); + encoded_audio_.push_back(DecoderBuffer::CreateEOSBuffer()); config_.Initialize(kCodecVorbis, 16, CHANNEL_LAYOUT_STEREO, 44100, - vorbis_extradata_.get(), - vorbis_extradata_size_, + vorbis_extradata_->GetData(), + vorbis_extradata_->GetDataSize(), true); } @@ -83,7 +81,7 @@ class FFmpegAudioDecoderTest : public testing::Test { void ReadPacket(const DemuxerStream::ReadCB& read_cb) { CHECK(!encoded_audio_.empty()) << "ReadPacket() called too many times"; - scoped_refptr<Buffer> buffer(encoded_audio_.front()); + scoped_refptr<DecoderBuffer> buffer(encoded_audio_.front()); encoded_audio_.pop_front(); read_cb.Run(buffer); } @@ -117,10 +115,9 @@ class FFmpegAudioDecoderTest : public testing::Test { scoped_refptr<StrictMock<MockDemuxerStream> > demuxer_; MockStatisticsCB statistics_cb_; - scoped_array<uint8> vorbis_extradata_; - int vorbis_extradata_size_; + scoped_refptr<DecoderBuffer> vorbis_extradata_; - std::deque<scoped_refptr<Buffer> > encoded_audio_; + std::deque<scoped_refptr<DecoderBuffer> > encoded_audio_; std::deque<scoped_refptr<Buffer> > decoded_audio_; AudioDecoderConfig config_; diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc index 3d7a5fe..16c5447 100644 --- a/media/filters/ffmpeg_demuxer.cc +++ b/media/filters/ffmpeg_demuxer.cc @@ -16,7 +16,7 @@ #include "base/string_util.h" #include "base/time.h" #include "media/base/audio_decoder_config.h" -#include "media/base/data_buffer.h" +#include "media/base/decoder_buffer.h" #include "media/base/limits.h" #include "media/base/media_switches.h" #include "media/base/video_decoder_config.h" @@ -28,37 +28,6 @@ namespace media { // -// AVPacketBuffer -// -class AVPacketBuffer : public Buffer { - public: - AVPacketBuffer(scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket> packet, - const base::TimeDelta& timestamp, - const base::TimeDelta& duration) - : Buffer(timestamp, duration), - packet_(packet.Pass()) { - } - - // Buffer implementation. - virtual const uint8* GetData() const { - return reinterpret_cast<const uint8*>(packet_->data); - } - - virtual int GetDataSize() const { - return packet_->size; - } - - protected: - virtual ~AVPacketBuffer() {} - - private: - scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket> packet_; - - DISALLOW_COPY_AND_ASSIGN(AVPacketBuffer); -}; - - -// // FFmpegDemuxerStream // FFmpegDemuxerStream::FFmpegDemuxerStream(FFmpegDemuxer* demuxer, @@ -100,10 +69,6 @@ bool FFmpegDemuxerStream::HasPendingReads() { void FFmpegDemuxerStream::EnqueuePacket( scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket> packet) { DCHECK_EQ(MessageLoop::current(), demuxer_->message_loop()); - base::TimeDelta timestamp = - ConvertStreamTimestamp(stream_->time_base, packet->pts); - base::TimeDelta duration = - ConvertStreamTimestamp(stream_->time_base, packet->duration); base::AutoLock auto_lock(lock_); if (stopped_) { @@ -111,19 +76,26 @@ void FFmpegDemuxerStream::EnqueuePacket( return; } - // Convert if the packet if there is bitstream filter. - if (packet->data && bitstream_converter_.get() && - !bitstream_converter_->ConvertPacket(packet.get())) { - LOG(ERROR) << "Format converstion failed."; - } + scoped_refptr<DecoderBuffer> buffer; + if (!packet.get()) { + buffer = DecoderBuffer::CreateEOSBuffer(); + } else { + // Convert the packet if there is a bitstream filter. + if (packet->data && bitstream_converter_.get() && + !bitstream_converter_->ConvertPacket(packet.get())) { + LOG(ERROR) << "Format converstion failed."; + } - // Enqueue the callback and attempt to satisfy a read immediately. - scoped_refptr<Buffer> buffer( - new AVPacketBuffer(packet.Pass(), timestamp, duration)); - if (!buffer) { - NOTREACHED() << "Unable to allocate AVPacketBuffer"; - return; + // If a packet is returned by FFmpeg's av_parser_parse2() the packet will + // reference inner memory of FFmpeg. As such we should transfer the packet + // into memory we control. + buffer = DecoderBuffer::CopyFrom(packet->data, packet->size); + buffer->SetTimestamp(ConvertStreamTimestamp( + stream_->time_base, packet->pts)); + buffer->SetDuration(ConvertStreamTimestamp( + stream_->time_base, packet->duration)); } + buffer_queue_.push_back(buffer); FulfillPendingRead(); return; @@ -142,7 +114,7 @@ void FFmpegDemuxerStream::Stop() { buffer_queue_.clear(); for (ReadQueue::iterator it = read_queue_.begin(); it != read_queue_.end(); ++it) { - it->Run(scoped_refptr<Buffer>(new DataBuffer(0))); + it->Run(scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer())); } read_queue_.clear(); stopped_ = true; @@ -165,7 +137,7 @@ void FFmpegDemuxerStream::Read(const ReadCB& read_cb) { // // TODO(scherkus): it would be cleaner if we replied with an error message. if (stopped_) { - read_cb.Run(scoped_refptr<Buffer>(new DataBuffer(0))); + read_cb.Run(scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer())); return; } @@ -179,7 +151,7 @@ void FFmpegDemuxerStream::Read(const ReadCB& read_cb) { } // Send the oldest buffer back. - scoped_refptr<Buffer> buffer = buffer_queue_.front(); + scoped_refptr<DecoderBuffer> buffer = buffer_queue_.front(); buffer_queue_.pop_front(); read_cb.Run(buffer); } @@ -192,7 +164,7 @@ void FFmpegDemuxerStream::ReadTask(const ReadCB& read_cb) { // // TODO(scherkus): it would be cleaner if we replied with an error message. if (stopped_) { - read_cb.Run(scoped_refptr<Buffer>(new DataBuffer(0))); + read_cb.Run(scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer())); return; } @@ -214,7 +186,7 @@ void FFmpegDemuxerStream::FulfillPendingRead() { } // Dequeue a buffer and pending read pair. - scoped_refptr<Buffer> buffer = buffer_queue_.front(); + scoped_refptr<DecoderBuffer> buffer = buffer_queue_.front(); ReadCB read_cb(read_queue_.front()); buffer_queue_.pop_front(); read_queue_.pop_front(); @@ -233,22 +205,12 @@ void FFmpegDemuxerStream::EnableBitstreamConverter() { bitstream_converter_.reset( new FFmpegH264BitstreamConverter(stream_->codec)); CHECK(bitstream_converter_->Initialize()); - return; - } - - const char* filter_name = NULL; - if (stream_->codec->codec_id == CODEC_ID_MPEG4) { - filter_name = "mpeg4video_es"; - } else if (stream_->codec->codec_id == CODEC_ID_WMV3) { - filter_name = "vc1_asftorcv"; - } else if (stream_->codec->codec_id == CODEC_ID_VC1) { - filter_name = "vc1_asftoannexg"; - } - - if (filter_name) { + } else if (stream_->codec->codec_id == CODEC_ID_MPEG4) { bitstream_converter_.reset( - new FFmpegBitstreamConverter(filter_name, stream_->codec)); + new FFmpegBitstreamConverter("mpeg4video_es", stream_->codec)); CHECK(bitstream_converter_->Initialize()); + } else { + NOTREACHED() << "Unsupported bitstream format."; } } @@ -676,15 +638,6 @@ void FFmpegDemuxer::DemuxTask() { (!audio_disabled_ || streams_[packet->stream_index]->type() != DemuxerStream::AUDIO)) { FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index]; - - // If a packet is returned by FFmpeg's av_parser_parse2() - // the packet will reference an inner memory of FFmpeg. - // In this case, the packet's "destruct" member is NULL, - // and it MUST be duplicated. This fixes issue with MP3 and possibly - // other codecs. It is safe to call this function even if the packet does - // not refer to inner memory from FFmpeg. - av_dup_packet(packet.get()); - demuxer_stream->EnqueuePacket(packet.Pass()); } @@ -739,9 +692,8 @@ void FFmpegDemuxer::StreamHasEnded() { (audio_disabled_ && (*iter)->type() == DemuxerStream::AUDIO)) { continue; } - scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket> packet(new AVPacket()); - memset(packet.get(), 0, sizeof(*packet.get())); - (*iter)->EnqueuePacket(packet.Pass()); + (*iter)->EnqueuePacket( + scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket>()); } } diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h index ba7d1f2..dee6db3 100644 --- a/media/filters/ffmpeg_demuxer.h +++ b/media/filters/ffmpeg_demuxer.h @@ -29,7 +29,7 @@ #include "base/gtest_prod_util.h" #include "base/synchronization/waitable_event.h" #include "media/base/audio_decoder_config.h" -#include "media/base/buffers.h" +#include "media/base/decoder_buffer.h" #include "media/base/demuxer.h" #include "media/base/pipeline.h" #include "media/base/video_decoder_config.h" @@ -58,7 +58,8 @@ class FFmpegDemuxerStream : public DemuxerStream { // Safe to call on any thread. bool HasPendingReads(); - // Enqueues the given AVPacket. + // Enqueues the given AVPacket. If |packet| is NULL an end of stream packet + // is enqueued. void EnqueuePacket(scoped_ptr_malloc<AVPacket, ScopedPtrAVFreePacket> packet); // Signals to empty the buffer queue and mark next packet as discontinuous. @@ -115,7 +116,7 @@ class FFmpegDemuxerStream : public DemuxerStream { bool discontinuous_; bool stopped_; - typedef std::deque<scoped_refptr<Buffer> > BufferQueue; + typedef std::deque<scoped_refptr<DecoderBuffer> > BufferQueue; BufferQueue buffer_queue_; typedef std::deque<ReadCB> ReadQueue; diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc index 292bb2f..261578f 100644 --- a/media/filters/ffmpeg_demuxer_unittest.cc +++ b/media/filters/ffmpeg_demuxer_unittest.cc @@ -442,7 +442,7 @@ class MockReadCB : public base::RefCountedThreadSafe<MockReadCB> { MockReadCB() {} MOCK_METHOD0(OnDelete, void()); - MOCK_METHOD1(Run, void(const scoped_refptr<Buffer>& buffer)); + MOCK_METHOD1(Run, void(const scoped_refptr<DecoderBuffer>& buffer)); protected: virtual ~MockReadCB() { diff --git a/media/filters/ffmpeg_h264_bitstream_converter.cc b/media/filters/ffmpeg_h264_bitstream_converter.cc index 6220daf..2995083 100644 --- a/media/filters/ffmpeg_h264_bitstream_converter.cc +++ b/media/filters/ffmpeg_h264_bitstream_converter.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -91,7 +91,7 @@ bool FFmpegH264BitstreamConverter::ConvertPacket(AVPacket* packet) { } // At the end we must destroy the old packet. - packet->destruct(packet); + av_free_packet(packet); *packet = dest_packet; // Finally, replace the values in the input packet. return true; diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc index f85b6cb..930a290 100644 --- a/media/filters/ffmpeg_video_decoder.cc +++ b/media/filters/ffmpeg_video_decoder.cc @@ -9,6 +9,7 @@ #include "base/command_line.h" #include "base/message_loop.h" #include "base/string_number_conversions.h" +#include "media/base/decoder_buffer.h" #include "media/base/demuxer_stream.h" #include "media/base/limits.h" #include "media/base/media_switches.h" @@ -208,14 +209,16 @@ void FFmpegVideoDecoder::ReadFromDemuxerStream() { demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::DecodeBuffer, this)); } -void FFmpegVideoDecoder::DecodeBuffer(const scoped_refptr<Buffer>& buffer) { +void FFmpegVideoDecoder::DecodeBuffer( + const scoped_refptr<DecoderBuffer>& buffer) { // TODO(scherkus): fix FFmpegDemuxerStream::Read() to not execute our read // callback on the same execution stack so we can get rid of forced task post. message_loop_->PostTask(FROM_HERE, base::Bind( &FFmpegVideoDecoder::DoDecodeBuffer, this, buffer)); } -void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& buffer) { +void FFmpegVideoDecoder::DoDecodeBuffer( + const scoped_refptr<DecoderBuffer>& buffer) { DCHECK_EQ(MessageLoop::current(), message_loop_); DCHECK_NE(state_, kUninitialized); DCHECK_NE(state_, kDecodeFinished); @@ -262,7 +265,7 @@ void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& buffer) { state_ = kFlushCodec; } - scoped_refptr<Buffer> unencrypted_buffer = buffer; + scoped_refptr<DecoderBuffer> unencrypted_buffer = buffer; if (buffer->GetDecryptConfig() && buffer->GetDataSize()) { unencrypted_buffer = decryptor_.Decrypt(buffer); if (!unencrypted_buffer || !unencrypted_buffer->GetDataSize()) { @@ -303,7 +306,7 @@ void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& buffer) { } bool FFmpegVideoDecoder::Decode( - const scoped_refptr<Buffer>& buffer, + const scoped_refptr<DecoderBuffer>& buffer, scoped_refptr<VideoFrame>* video_frame) { DCHECK(video_frame); diff --git a/media/filters/ffmpeg_video_decoder.h b/media/filters/ffmpeg_video_decoder.h index d1d181d..4d99a43 100644 --- a/media/filters/ffmpeg_video_decoder.h +++ b/media/filters/ffmpeg_video_decoder.h @@ -12,6 +12,7 @@ #include "media/base/video_decoder.h" #include "media/crypto/aes_decryptor.h" +class DecoderBuffer; class MessageLoop; struct AVCodecContext; @@ -50,11 +51,11 @@ class MEDIA_EXPORT FFmpegVideoDecoder : public VideoDecoder { // Reads from the demuxer stream with corresponding callback method. void ReadFromDemuxerStream(); - void DecodeBuffer(const scoped_refptr<Buffer>& buffer); + void DecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer); // Carries out the decoding operation scheduled by DecodeBuffer(). - void DoDecodeBuffer(const scoped_refptr<Buffer>& buffer); - bool Decode(const scoped_refptr<Buffer>& buffer, + void DoDecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer); + bool Decode(const scoped_refptr<DecoderBuffer>& buffer, scoped_refptr<VideoFrame>* video_frame); // Delivers the frame to |read_cb_| and resets the callback. diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc index 24e716e..b0fc360 100644 --- a/media/filters/ffmpeg_video_decoder_unittest.cc +++ b/media/filters/ffmpeg_video_decoder_unittest.cc @@ -8,7 +8,7 @@ #include "base/message_loop.h" #include "base/memory/singleton.h" #include "base/string_util.h" -#include "media/base/data_buffer.h" +#include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" #include "media/base/limits.h" #include "media/base/mock_callback.h" @@ -57,11 +57,11 @@ class FFmpegVideoDecoderTest : public testing::Test { // Initialize various test buffers. frame_buffer_.reset(new uint8[kCodedSize.GetArea()]); - end_of_stream_buffer_ = new DataBuffer(0); - ReadTestDataFile("vp8-I-frame-320x240", &i_frame_buffer_); - ReadTestDataFile("vp8-corrupt-I-frame", &corrupt_i_frame_buffer_); - ReadTestDataFile("vp8-encrypted-I-frame-320x240", - &encrypted_i_frame_buffer_); + end_of_stream_buffer_ = DecoderBuffer::CreateEOSBuffer(); + i_frame_buffer_ = ReadTestDataFile("vp8-I-frame-320x240"); + corrupt_i_frame_buffer_ = ReadTestDataFile("vp8-corrupt-I-frame"); + encrypted_i_frame_buffer_ = ReadTestDataFile( + "vp8-encrypted-I-frame-320x240"); config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, kVideoFormat, kCodedSize, kVisibleRect, @@ -129,7 +129,7 @@ class FFmpegVideoDecoderTest : public testing::Test { // uncompressed output to |video_frame|. This method works with single // and multithreaded decoders. End of stream buffers are used to trigger // the frame to be returned in the multithreaded decoder case. - void DecodeSingleFrame(const scoped_refptr<Buffer>& buffer, + void DecodeSingleFrame(const scoped_refptr<DecoderBuffer>& buffer, VideoDecoder::DecoderStatus* status, scoped_refptr<VideoFrame>* video_frame) { EXPECT_CALL(*demuxer_, Read(_)) @@ -154,8 +154,7 @@ class FFmpegVideoDecoderTest : public testing::Test { scoped_refptr<VideoFrame> video_frame_a; scoped_refptr<VideoFrame> video_frame_b; - scoped_refptr<Buffer> buffer; - ReadTestDataFile(test_file_name, &buffer); + scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(test_file_name); EXPECT_CALL(*demuxer_, Read(_)) .WillOnce(ReturnBuffer(i_frame_buffer_)) @@ -204,10 +203,10 @@ class FFmpegVideoDecoderTest : public testing::Test { // Various buffers for testing. scoped_array<uint8_t> frame_buffer_; - scoped_refptr<Buffer> end_of_stream_buffer_; - scoped_refptr<Buffer> i_frame_buffer_; - scoped_refptr<Buffer> corrupt_i_frame_buffer_; - scoped_refptr<DataBuffer> encrypted_i_frame_buffer_; + scoped_refptr<DecoderBuffer> end_of_stream_buffer_; + scoped_refptr<DecoderBuffer> i_frame_buffer_; + scoped_refptr<DecoderBuffer> corrupt_i_frame_buffer_; + scoped_refptr<DecoderBuffer> encrypted_i_frame_buffer_; // Used for generating timestamped buffers. std::deque<int64> timestamps_; @@ -271,7 +270,7 @@ TEST_F(FFmpegVideoDecoderTest, DecodeFrame_Normal) { TEST_F(FFmpegVideoDecoderTest, DecodeFrame_0ByteFrame) { Initialize(); - scoped_refptr<DataBuffer> zero_byte_buffer = new DataBuffer(1); + scoped_refptr<DecoderBuffer> zero_byte_buffer = new DecoderBuffer(0); VideoDecoder::DecoderStatus status_a; VideoDecoder::DecoderStatus status_b; @@ -514,7 +513,7 @@ TEST_F(FFmpegVideoDecoderTest, AbortPendingRead) { Initialize(); EXPECT_CALL(*demuxer_, Read(_)) - .WillOnce(ReturnBuffer(scoped_refptr<Buffer>())); + .WillOnce(ReturnBuffer(scoped_refptr<DecoderBuffer>())); VideoDecoder::DecoderStatus status; scoped_refptr<VideoFrame> video_frame; diff --git a/media/filters/gpu_video_decoder.cc b/media/filters/gpu_video_decoder.cc index ee0ba30..9352790 100644 --- a/media/filters/gpu_video_decoder.cc +++ b/media/filters/gpu_video_decoder.cc @@ -8,6 +8,7 @@ #include "base/callback_helpers.h" #include "base/message_loop.h" #include "base/stl_util.h" +#include "media/base/decoder_buffer.h" #include "media/base/demuxer_stream.h" #include "media/base/filter_host.h" #include "media/base/pipeline.h" @@ -29,7 +30,8 @@ GpuVideoDecoder::SHMBuffer::SHMBuffer(base::SharedMemory* m, size_t s) GpuVideoDecoder::SHMBuffer::~SHMBuffer() {} GpuVideoDecoder::BufferPair::BufferPair( - SHMBuffer* s, const scoped_refptr<Buffer>& b) : shm_buffer(s), buffer(b) { + SHMBuffer* s, const scoped_refptr<DecoderBuffer>& b) + : shm_buffer(s), buffer(b) { } GpuVideoDecoder::BufferPair::~BufferPair() {} @@ -193,7 +195,8 @@ void GpuVideoDecoder::Read(const ReadCB& read_cb) { } } -void GpuVideoDecoder::RequestBufferDecode(const scoped_refptr<Buffer>& buffer) { +void GpuVideoDecoder::RequestBufferDecode( + const scoped_refptr<DecoderBuffer>& buffer) { if (!gvd_loop_proxy_->BelongsToCurrentThread()) { gvd_loop_proxy_->PostTask(FROM_HERE, base::Bind( &GpuVideoDecoder::RequestBufferDecode, this, buffer)); @@ -440,7 +443,7 @@ void GpuVideoDecoder::NotifyEndOfBitstreamBuffer(int32 id) { } PutSHM(it->second.shm_buffer); - const scoped_refptr<Buffer>& buffer = it->second.buffer; + const scoped_refptr<DecoderBuffer>& buffer = it->second.buffer; if (buffer->GetDataSize()) { PipelineStatistics statistics; statistics.video_bytes_decoded = buffer->GetDataSize(); diff --git a/media/filters/gpu_video_decoder.h b/media/filters/gpu_video_decoder.h index b91b015..4274d15 100644 --- a/media/filters/gpu_video_decoder.h +++ b/media/filters/gpu_video_decoder.h @@ -22,6 +22,8 @@ class SharedMemory; namespace media { +class DecoderBuffer; + // GPU-accelerated video decoder implementation. Relies on // AcceleratedVideoDecoderMsg_Decode and friends. // All methods internally trampoline to the |message_loop| passed to the ctor. @@ -98,7 +100,7 @@ class MEDIA_EXPORT GpuVideoDecoder void EnsureDemuxOrDecode(); // Callback to pass to demuxer_stream_->Read() for receiving encoded bits. - void RequestBufferDecode(const scoped_refptr<Buffer>& buffer); + void RequestBufferDecode(const scoped_refptr<DecoderBuffer>& buffer); // Enqueue a frame for later delivery (or drop it on the floor if a // vda->Reset() is in progress) and trigger out-of-line delivery of the oldest @@ -175,10 +177,10 @@ class MEDIA_EXPORT GpuVideoDecoder // Book-keeping variables. struct BufferPair { - BufferPair(SHMBuffer* s, const scoped_refptr<Buffer>& b); + BufferPair(SHMBuffer* s, const scoped_refptr<DecoderBuffer>& b); ~BufferPair(); SHMBuffer* shm_buffer; - scoped_refptr<Buffer> buffer; + scoped_refptr<DecoderBuffer> buffer; }; std::map<int32, BufferPair> bitstream_buffers_in_decoder_; std::map<int32, PictureBuffer> picture_buffers_in_decoder_; diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc index dae9423..c3f81e3 100644 --- a/media/filters/pipeline_integration_test.cc +++ b/media/filters/pipeline_integration_test.cc @@ -5,6 +5,7 @@ #include "media/filters/pipeline_integration_test_base.h" #include "base/bind.h" +#include "media/base/decoder_buffer.h" #include "media/base/test_data_util.h" #include "media/filters/chunk_demuxer_client.h" @@ -24,10 +25,10 @@ class MockMediaSource : public ChunkDemuxerClient { : url_(GetTestDataURL(filename)), current_position_(0), initial_append_size_(initial_append_size) { - ReadTestDataFile(filename, &file_data_, &file_data_size_); + file_data_ = ReadTestDataFile(filename); DCHECK_GT(initial_append_size_, 0); - DCHECK_LE(initial_append_size_, file_data_size_); + DCHECK_LE(initial_append_size_, file_data_->GetDataSize()); } virtual ~MockMediaSource() {} @@ -45,7 +46,7 @@ class MockMediaSource : public ChunkDemuxerClient { chunk_demuxer_->StartWaitingForSeek(); DCHECK_GE(new_position, 0); - DCHECK_LT(new_position, file_data_size_); + DCHECK_LT(new_position, file_data_->GetDataSize()); current_position_ = new_position; AppendData(seek_append_size); @@ -53,11 +54,10 @@ class MockMediaSource : public ChunkDemuxerClient { void AppendData(int size) { DCHECK(chunk_demuxer_.get()); - DCHECK_LT(current_position_, file_data_size_); - DCHECK_LE(current_position_ + size, file_data_size_); - CHECK(chunk_demuxer_->AppendData(kSourceId, - file_data_.get() + current_position_, - size)); + DCHECK_LT(current_position_, file_data_->GetDataSize()); + DCHECK_LE(current_position_ + size, file_data_->GetDataSize()); + CHECK(chunk_demuxer_->AppendData( + kSourceId, file_data_->GetData() + current_position_, size)); current_position_ += size; } @@ -98,8 +98,7 @@ class MockMediaSource : public ChunkDemuxerClient { private: std::string url_; - scoped_array<uint8> file_data_; - int file_data_size_; + scoped_refptr<DecoderBuffer> file_data_; int current_position_; int initial_append_size_; scoped_refptr<ChunkDemuxer> chunk_demuxer_; diff --git a/media/media.gyp b/media/media.gyp index 7d9b4eb..8c5a3b7 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -141,6 +141,8 @@ 'base/data_buffer.h', 'base/data_source.cc', 'base/data_source.h', + 'base/decoder_buffer.cc', + 'base/decoder_buffer.h', 'base/decrypt_config.cc', 'base/decrypt_config.h', 'base/demuxer.cc', @@ -662,6 +664,7 @@ 'base/clock_unittest.cc', 'base/composite_filter_unittest.cc', 'base/data_buffer_unittest.cc', + 'base/decoder_buffer_unittest.cc', 'base/djb2_unittest.cc', 'base/filter_collection_unittest.cc', 'base/h264_bitstream_converter_unittest.cc', diff --git a/media/tools/seek_tester/seek_tester.cc b/media/tools/seek_tester/seek_tester.cc index 901c1bd..f8e769d 100644 --- a/media/tools/seek_tester/seek_tester.cc +++ b/media/tools/seek_tester/seek_tester.cc @@ -41,7 +41,7 @@ void QuitMessageLoop(MessageLoop* loop, media::PipelineStatus status) { void TimestampExtractor(uint64* timestamp_ms, MessageLoop* loop, - const scoped_refptr<media::Buffer>& buffer) { + const scoped_refptr<media::DecoderBuffer>& buffer) { if (buffer->GetTimestamp() == media::kNoTimestamp()) *timestamp_ms = -1; else |