diff options
Diffstat (limited to 'media')
-rw-r--r-- | media/base/filters.h | 22 | ||||
-rw-r--r-- | media/base/mock_filters.h | 5 | ||||
-rw-r--r-- | media/base/pipeline_impl.cc | 6 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer.cc | 99 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer.h | 45 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer_unittest.cc | 111 | ||||
-rw-r--r-- | media/filters/ffmpeg_glue.cc | 88 | ||||
-rw-r--r-- | media/filters/ffmpeg_glue.h | 58 | ||||
-rw-r--r-- | media/filters/ffmpeg_glue_unittest.cc | 199 | ||||
-rw-r--r-- | media/filters/file_data_source.cc | 59 | ||||
-rw-r--r-- | media/filters/file_data_source.h | 5 | ||||
-rw-r--r-- | media/filters/file_data_source_unittest.cc | 39 |
12 files changed, 498 insertions, 238 deletions
diff --git a/media/base/filters.h b/media/base/filters.h index 84df0966..3029cbc 100644 --- a/media/base/filters.h +++ b/media/base/filters.h @@ -123,6 +123,9 @@ class MediaFilter : public base::RefCountedThreadSafe<MediaFilter> { class DataSource : public MediaFilter { public: + typedef Callback1<size_t>::Type ReadCallback; + static const size_t kReadError = static_cast<size_t>(-1); + static const FilterType filter_type() { return FILTER_DATA_SOURCE; } @@ -133,8 +136,6 @@ class DataSource : public MediaFilter { mime_type == mime_type::kURL); } - static const size_t kReadError = static_cast<size_t>(-1); - // Initialize a DataSource for the given URL, executing the callback upon // completion. virtual void Initialize(const std::string& url, FilterCallback* callback) = 0; @@ -142,16 +143,13 @@ class DataSource : public MediaFilter { // Returns the MediaFormat for this filter. virtual const MediaFormat& media_format() = 0; - // Read the given amount of bytes into data, returns the number of bytes read - // if successful, kReadError otherwise. - virtual size_t Read(uint8* data, size_t size) = 0; - - // Returns true and the current file position for this file, false if the - // file position could not be retrieved. - virtual bool GetPosition(int64* position_out) = 0; - - // Returns true if the file position could be set, false otherwise. - virtual bool SetPosition(int64 position) = 0; + // Reads |size| bytes from |position| into |data|. And when the read is done + // or failed, |read_callback| is called with the number of bytes read or + // kReadError in case of error. + // TODO(hclam): should change |size| to int! It makes the code so messy + // with size_t and int all over the place.. + virtual void Read(int64 position, size_t size, + uint8* data, ReadCallback* read_callback) = 0; // Returns true and the file size, false if the file size could not be // retrieved. diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h index 8c44996..c933bd2 100644 --- a/media/base/mock_filters.h +++ b/media/base/mock_filters.h @@ -99,9 +99,8 @@ class MockDataSource : public DataSource { MOCK_METHOD2(Initialize, void(const std::string& url, FilterCallback* callback)); const MediaFormat& media_format() { return media_format_; } - MOCK_METHOD2(Read, size_t(uint8* data, size_t size)); - MOCK_METHOD1(GetPosition, bool(int64* position_out)); - MOCK_METHOD1(SetPosition, bool(int64 position)); + MOCK_METHOD4(Read, void(int64 position, size_t size, uint8* data, + DataSource::ReadCallback* callback)); MOCK_METHOD1(GetSize, bool(int64* size_out)); MOCK_METHOD0(IsSeekable, bool()); diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc index 9c2fb10..7fc4695 100644 --- a/media/base/pipeline_impl.cc +++ b/media/base/pipeline_impl.cc @@ -524,7 +524,11 @@ void PipelineInternal::ErrorTask(PipelineError error) { DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; // Suppress executing additional error logic. - if (state_ == kError) { + // TODO(hclam): Remove the condition for kStopped. It is there only because + // FFmpegDemuxer submits a read error while reading after it is called to + // stop. After FFmpegDemuxer is cleaned up we should remove this condition + // and add an extra assert. + if (state_ == kError || state_ == kStopped) { return; } diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc index bf595e2..60c6fcd 100644 --- a/media/filters/ffmpeg_demuxer.cc +++ b/media/filters/ffmpeg_demuxer.cc @@ -215,7 +215,11 @@ base::TimeDelta FFmpegDemuxerStream::ConvertTimestamp(int64 timestamp) { // FFmpegDemuxer // FFmpegDemuxer::FFmpegDemuxer() - : format_context_(NULL) { + : format_context_(NULL), + read_event_(false, false), + read_has_failed_(false), + last_read_bytes_(0), + read_position_(0) { } FFmpegDemuxer::~FFmpegDemuxer() { @@ -259,6 +263,9 @@ void FFmpegDemuxer::Stop() { // Post a task to notify the streams to stop as well. message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &FFmpegDemuxer::StopTask)); + + // Then wakes up the thread from reading. + SignalReadCompleted(DataSource::kReadError); } void FFmpegDemuxer::Seek(base::TimeDelta time, FilterCallback* callback) { @@ -273,7 +280,7 @@ void FFmpegDemuxer::Seek(base::TimeDelta time, FilterCallback* callback) { void FFmpegDemuxer::Initialize(DataSource* data_source, FilterCallback* callback) { message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(this, &FFmpegDemuxer::InititalizeTask, data_source, + NewRunnableMethod(this, &FFmpegDemuxer::InitializeTask, data_source, callback)); } @@ -287,30 +294,76 @@ scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream(int stream) { return streams_[stream].get(); } -void FFmpegDemuxer::InititalizeTask(DataSource* data_source, - FilterCallback* callback) { +int FFmpegDemuxer::Read(int size, uint8* data) { + DCHECK(data_source_); + + // If read has ever failed, return with an error. + // TODO(hclam): use a more meaningful constant as error. + if (read_has_failed_) + return AVERROR_IO; + + // Asynchronous read from data source. + data_source_->Read(read_position_, size, data, + NewCallback(this, &FFmpegDemuxer::OnReadCompleted)); + + // TODO(hclam): The method is called on the demuxer thread and this method + // call will block the thread. We need to implemented an additional thread to + // let FFmpeg demuxer methods to run on. + size_t last_read_bytes = WaitForRead(); + if (last_read_bytes == DataSource::kReadError) { + host()->SetError(PIPELINE_ERROR_READ); + + // Returns with a negative number to signal an error to FFmpeg. + read_has_failed_ = true; + return AVERROR_IO; + } + read_position_ += last_read_bytes; + return last_read_bytes; +} + +bool FFmpegDemuxer::GetPosition(int64* position_out) { + *position_out = read_position_; + return true; +} + +bool FFmpegDemuxer::SetPosition(int64 position) { + DCHECK(data_source_); + + int64 file_size; + if (!data_source_->GetSize(&file_size) || position >= file_size) + return false; + + read_position_ = position; + return true; +} + +bool FFmpegDemuxer::GetSize(int64* size_out) { + DCHECK(data_source_); + + return data_source_->GetSize(size_out); +} + +bool FFmpegDemuxer::IsStreamed() { + return false; +} + +void FFmpegDemuxer::InitializeTask(DataSource* data_source, + FilterCallback* callback) { DCHECK_EQ(MessageLoop::current(), message_loop()); scoped_ptr<FilterCallback> c(callback); - // In order to get FFmpeg to use |data_source| for file IO we must transfer - // ownership via FFmpegGlue. We'll add |data_source| to FFmpegGlue and pass - // the resulting key to FFmpeg. FFmpeg will pass the key to FFmpegGlue which - // will take care of attaching |data_source| to an FFmpeg context. After - // we finish initializing the FFmpeg context we can remove |data_source| from - // FFmpegGlue. - // - // Refer to media/filters/ffmpeg_glue.h for details. + data_source_ = data_source; - // Add our data source and get our unique key. - std::string key = FFmpegGlue::get()->AddDataSource(data_source); + // Add ourself to Protocol list and get our unique key. + std::string key = FFmpegGlue::get()->AddProtocol(this); // Open FFmpeg AVFormatContext. DCHECK(!format_context_); AVFormatContext* context = NULL; int result = av_open_input_file(&context, key.c_str(), NULL, 0, NULL); - // Remove our data source. - FFmpegGlue::get()->RemoveDataSource(data_source); + // Remove ourself from protocol list. + FFmpegGlue::get()->RemoveProtocol(this); if (result < 0) { host()->SetError(DEMUXER_ERROR_COULD_NOT_OPEN); @@ -473,4 +526,18 @@ void FFmpegDemuxer::StreamHasEnded() { } } +void FFmpegDemuxer::OnReadCompleted(size_t size) { + SignalReadCompleted(size); +} + +size_t FFmpegDemuxer::WaitForRead() { + read_event_.Wait(); + return last_read_bytes_; +} + +void FFmpegDemuxer::SignalReadCompleted(size_t size) { + last_read_bytes_ = size; + read_event_.Signal(); +} + } // namespace media diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h index 20b77e4..ebd1274 100644 --- a/media/filters/ffmpeg_demuxer.h +++ b/media/filters/ffmpeg_demuxer.h @@ -25,11 +25,14 @@ #include <deque> #include <vector> +#include "base/waitable_event.h" #include "media/base/buffers.h" #include "media/base/factory.h" #include "media/base/filters.h" #include "media/base/media_format.h" +#include "media/filters/ffmpeg_glue.h" #include "media/filters/ffmpeg_interfaces.h" +#include "testing/gtest/include/gtest/gtest_prod.h" // FFmpeg forward declarations. struct AVCodecContext; @@ -108,7 +111,8 @@ class FFmpegDemuxerStream : public DemuxerStream, public AVStreamProvider { DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxerStream); }; -class FFmpegDemuxer : public Demuxer { +class FFmpegDemuxer : public Demuxer, + public FFmpegURLProtocol { public: // FilterFactory provider. static FilterFactory* CreateFilterFactory() { @@ -127,14 +131,24 @@ class FFmpegDemuxer : public Demuxer { virtual size_t GetNumberOfStreams(); virtual scoped_refptr<DemuxerStream> GetStream(int stream_id); + // FFmpegProtocol implementation. + virtual int Read(int size, uint8* data); + virtual bool GetPosition(int64* position_out); + virtual bool SetPosition(int64 position); + virtual bool GetSize(int64* size_out); + virtual bool IsStreamed(); + private: // Only allow a factory to create this class. friend class FilterFactoryImpl0<FFmpegDemuxer>; + friend class MockFFmpegDemuxer; + FRIEND_TEST(FFmpegDemuxerTest, ProtocolRead); + FFmpegDemuxer(); virtual ~FFmpegDemuxer(); // Carries out initialization on the demuxer thread. - void InititalizeTask(DataSource* data_source, FilterCallback* callback); + void InitializeTask(DataSource* data_source, FilterCallback* callback); // Carries out a seek on the demuxer thread. void SeekTask(base::TimeDelta time, FilterCallback* callback); @@ -157,6 +171,17 @@ class FFmpegDemuxer : public Demuxer { // Must be called on the demuxer thread. void StreamHasEnded(); + // Read callback method to be passed to DataSource. When the asynchronous + // read has completed, this method will be called from DataSource with + // number of bytes read or kDataSource in case of error. + void OnReadCompleted(size_t size); + + // Wait for asynchronous read to complete and return number of bytes read. + virtual size_t WaitForRead(); + + // Signal that read has completed, and |size| bytes have been read. + virtual void SignalReadCompleted(size_t size); + // FFmpeg context handle. AVFormatContext* format_context_; @@ -178,6 +203,22 @@ class FFmpegDemuxer : public Demuxer { StreamVector streams_; StreamVector packet_streams_; + // Reference to the data source. Asynchronous read requests are submitted to + // this object. + scoped_refptr<DataSource> data_source_; + + // This member is used to block on read method calls from FFmpeg and wait + // until the asynchronous reads in the data source to complete. It is also + // signaled when the demuxer is being stopped. + base::WaitableEvent read_event_; + + // Flag to indicate if read has ever failed. Once set to true, it will + // never be reset. This flag is set true and accessed in Read(). + bool read_has_failed_; + + size_t last_read_bytes_; + int64 read_position_; + DISALLOW_COPY_AND_ASSIGN(FFmpegDemuxer); }; diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc index 6f05004..08580da 100644 --- a/media/filters/ffmpeg_demuxer_unittest.cc +++ b/media/filters/ffmpeg_demuxer_unittest.cc @@ -4,6 +4,7 @@ #include <deque> +#include "base/thread.h" #include "media/base/filters.h" #include "media/base/mock_ffmpeg.h" #include "media/base/mock_filter_host.h" @@ -16,9 +17,12 @@ using ::testing::_; using ::testing::DoAll; using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NotNull; using ::testing::Return; using ::testing::SetArgumentPointee; using ::testing::StrictMock; +using ::testing::WithArgs; namespace media { @@ -76,7 +80,7 @@ class FFmpegDemuxerTest : public testing::Test { memset(&streams_, 0, sizeof(streams_)); memset(&codecs_, 0, sizeof(codecs_)); - // Initialize AVCodexContext structures. + // Initialize AVCodecContext structures. codecs_[AV_STREAM_DATA].codec_type = CODEC_TYPE_DATA; codecs_[AV_STREAM_DATA].codec_id = CODEC_ID_NONE; @@ -662,4 +666,109 @@ TEST_F(FFmpegDemuxerTest, Stop) { MockFFmpeg::get()->CheckPoint(1); } +class MockFFmpegDemuxer : public FFmpegDemuxer { + public: + MockFFmpegDemuxer() {} + virtual ~MockFFmpegDemuxer() {} + + MOCK_METHOD0(WaitForRead, size_t()); + MOCK_METHOD1(SignalReadCompleted, void(size_t size)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockFFmpegDemuxer); +}; + +// A gmock helper method to execute the callback and deletes it. +void RunCallback(size_t size, DataSource::ReadCallback* callback) { + DCHECK(callback); + callback->RunWithParams(Tuple1<size_t>(size)); + delete callback; +} + +TEST_F(FFmpegDemuxerTest, ProtocolRead) { + // Creates a demuxer. + scoped_refptr<MockFFmpegDemuxer> demuxer = new MockFFmpegDemuxer(); + ASSERT_TRUE(demuxer); + demuxer->set_host(&host_); + demuxer->set_message_loop(&message_loop_); + demuxer->data_source_ = data_source_; + + uint8 kBuffer[1]; + InSequence s; + // Actions taken in the first read. + EXPECT_CALL(*data_source_, Read(0, 512, kBuffer, NotNull())) + .WillOnce(WithArgs<1, 3>(Invoke(&RunCallback))); + EXPECT_CALL(*demuxer, SignalReadCompleted(512)); + EXPECT_CALL(*demuxer, WaitForRead()) + .WillOnce(Return(512)); + + // Second read. + EXPECT_CALL(*data_source_, Read(512, 512, kBuffer, NotNull())) + .WillOnce(WithArgs<1, 3>(Invoke(&RunCallback))); + EXPECT_CALL(*demuxer, SignalReadCompleted(512)); + EXPECT_CALL(*demuxer, WaitForRead()) + .WillOnce(Return(512)); + + // Called during SetPosition(). + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + + // This read complete signal is generated when demuxer is stopped. + EXPECT_CALL(*demuxer, SignalReadCompleted(DataSource::kReadError)); + + EXPECT_EQ(512, demuxer->Read(512, kBuffer)); + int64 position; + EXPECT_TRUE(demuxer->GetPosition(&position)); + EXPECT_EQ(512, position); + EXPECT_EQ(512, demuxer->Read(512, kBuffer)); + EXPECT_FALSE(demuxer->SetPosition(1024)); + + demuxer->Stop(); +} + +TEST_F(FFmpegDemuxerTest, ProtocolGetSetPosition) { + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } + + InSequence s; + + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + + int64 position; + EXPECT_TRUE(demuxer_->GetPosition(&position)); + EXPECT_EQ(0, position); + + EXPECT_TRUE(demuxer_->SetPosition(512)); + EXPECT_FALSE(demuxer_->SetPosition(2048)); + EXPECT_TRUE(demuxer_->GetPosition(&position)); + EXPECT_EQ(512, position); +} + +TEST_F(FFmpegDemuxerTest, ProtocolGetSize) { + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } + + EXPECT_CALL(*data_source_, GetSize(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(1024), Return(true))); + + int64 size; + EXPECT_TRUE(demuxer_->GetSize(&size)); + EXPECT_EQ(1024, size); +} + +TEST_F(FFmpegDemuxerTest, ProtocolIsStreamed) { + { + SCOPED_TRACE(""); + InitializeDemuxer(); + } + EXPECT_FALSE(demuxer_->IsStreamed()); +} + } // namespace media diff --git a/media/filters/ffmpeg_glue.cc b/media/filters/ffmpeg_glue.cc index ca3ed1d..135b3bb 100644 --- a/media/filters/ffmpeg_glue.cc +++ b/media/filters/ffmpeg_glue.cc @@ -9,24 +9,26 @@ namespace { +media::FFmpegURLProtocol* ToProtocol(void* data) { + return reinterpret_cast<media::FFmpegURLProtocol*>(data); +} + // FFmpeg protocol interface. int OpenContext(URLContext* h, const char* filename, int flags) { - scoped_refptr<media::DataSource> data_source; - media::FFmpegGlue::get()->GetDataSource(filename, &data_source); - if (!data_source) + media::FFmpegURLProtocol* protocol; + media::FFmpegGlue::get()->GetProtocol(filename, &protocol); + if (!protocol) return AVERROR_IO; - data_source->AddRef(); - h->priv_data = data_source; + h->priv_data = protocol; h->flags = URL_RDONLY; - h->is_streamed = !data_source->IsSeekable(); + h->is_streamed = protocol->IsStreamed(); return 0; } int ReadContext(URLContext* h, unsigned char* buf, int size) { - media::DataSource* data_source = - reinterpret_cast<media::DataSource*>(h->priv_data); - int result = data_source->Read(buf, size); + media::FFmpegURLProtocol* protocol = ToProtocol(h->priv_data); + int result = protocol->Read(size, buf); if (result < 0) result = AVERROR_IO; return result; @@ -38,33 +40,32 @@ int WriteContext(URLContext* h, unsigned char* buf, int size) { } offset_t SeekContext(URLContext* h, offset_t offset, int whence) { - media::DataSource* data_source = - reinterpret_cast<media::DataSource*>(h->priv_data); + media::FFmpegURLProtocol* protocol = ToProtocol(h->priv_data); offset_t new_offset = AVERROR_IO; switch (whence) { case SEEK_SET: - if (data_source->SetPosition(offset)) - data_source->GetPosition(&new_offset); + if (protocol->SetPosition(offset)) + protocol->GetPosition(&new_offset); break; case SEEK_CUR: int64 pos; - if (!data_source->GetPosition(&pos)) + if (!protocol->GetPosition(&pos)) break; - if (data_source->SetPosition(pos + offset)) - data_source->GetPosition(&new_offset); + if (protocol->SetPosition(pos + offset)) + protocol->GetPosition(&new_offset); break; case SEEK_END: int64 size; - if (!data_source->GetSize(&size)) + if (!protocol->GetSize(&size)) break; - if (data_source->SetPosition(size + offset)) - data_source->GetPosition(&new_offset); + if (protocol->SetPosition(size + offset)) + protocol->GetPosition(&new_offset); break; case AVSEEK_SIZE: - data_source->GetSize(&new_offset); + protocol->GetSize(&new_offset); break; default: @@ -76,9 +77,6 @@ offset_t SeekContext(URLContext* h, offset_t offset, int whence) { } int CloseContext(URLContext* h) { - media::DataSource* data_source = - reinterpret_cast<media::DataSource*>(h->priv_data); - data_source->Release(); h->priv_data = NULL; return 0; } @@ -93,7 +91,7 @@ namespace media { static const char kProtocol[] = "http"; // Fill out our FFmpeg protocol definition. -static URLProtocol kFFmpegProtocol = { +static URLProtocol kFFmpegURLProtocol = { kProtocol, &OpenContext, &ReadContext, @@ -105,7 +103,7 @@ static URLProtocol kFFmpegProtocol = { FFmpegGlue::FFmpegGlue() { // Register our protocol glue code with FFmpeg. avcodec_init(); - av_register_protocol(&kFFmpegProtocol); + av_register_protocol(&kFFmpegURLProtocol); // Now register the rest of FFmpeg. av_register_all(); @@ -114,43 +112,43 @@ FFmpegGlue::FFmpegGlue() { FFmpegGlue::~FFmpegGlue() { } -std::string FFmpegGlue::AddDataSource(DataSource* data_source) { +std::string FFmpegGlue::AddProtocol(FFmpegURLProtocol* protocol) { AutoLock auto_lock(lock_); - std::string key = GetDataSourceKey(data_source); - if (data_sources_.find(key) == data_sources_.end()) { - data_sources_[key] = data_source; + std::string key = GetProtocolKey(protocol); + if (protocols_.find(key) == protocols_.end()) { + protocols_[key] = protocol; } return key; } -void FFmpegGlue::RemoveDataSource(DataSource* data_source) { +void FFmpegGlue::RemoveProtocol(FFmpegURLProtocol* protocol) { AutoLock auto_lock(lock_); - for (DataSourceMap::iterator cur, iter = data_sources_.begin(); - iter != data_sources_.end();) { + for (ProtocolMap::iterator cur, iter = protocols_.begin(); + iter != protocols_.end();) { cur = iter; iter++; - if (cur->second == data_source) - data_sources_.erase(cur); + if (cur->second == protocol) + protocols_.erase(cur); } } -void FFmpegGlue::GetDataSource(const std::string& key, - scoped_refptr<DataSource>* data_source) { +void FFmpegGlue::GetProtocol(const std::string& key, + FFmpegURLProtocol** protocol) { AutoLock auto_lock(lock_); - DataSourceMap::iterator iter = data_sources_.find(key); - if (iter == data_sources_.end()) { - *data_source = NULL; + ProtocolMap::iterator iter = protocols_.find(key); + if (iter == protocols_.end()) { + *protocol = NULL; return; } - *data_source = iter->second; + *protocol = iter->second; } -std::string FFmpegGlue::GetDataSourceKey(DataSource* data_source) { - // Use the DataSource's memory address to generate the unique string. This - // also has the nice property that adding the same DataSource reference will - // not generate duplicate entries. - return StringPrintf("%s://0x%lx", kProtocol, static_cast<void*>(data_source)); +std::string FFmpegGlue::GetProtocolKey(FFmpegURLProtocol* protocol) { + // Use the FFmpegURLProtocol's memory address to generate the unique string. + // This also has the nice property that adding the same FFmpegURLProtocol + // reference will not generate duplicate entries. + return StringPrintf("%s://0x%lx", kProtocol, static_cast<void*>(protocol)); } } // namespace media diff --git a/media/filters/ffmpeg_glue.h b/media/filters/ffmpeg_glue.h index 0db1ce8..228c964 100644 --- a/media/filters/ffmpeg_glue.h +++ b/media/filters/ffmpeg_glue.h @@ -38,22 +38,50 @@ typedef int64 offset_t; namespace media { -class DataSource; +class FFmpegURLProtocol { + public: + FFmpegURLProtocol() { + } + + virtual ~FFmpegURLProtocol() { + } + + // Read the given amount of bytes into data, returns the number of bytes read + // if successful, kReadError otherwise. + virtual int Read(int size, uint8* data) = 0; + + // Returns true and the current file position for this file, false if the + // file position could not be retrieved. + virtual bool GetPosition(int64* position_out) = 0; + + // Returns true if the file position could be set, false otherwise. + virtual bool SetPosition(int64 position) = 0; + + // Returns true and the file size, false if the file size could not be + // retrieved. + virtual bool GetSize(int64* size_out) = 0; + + // Returns false if this protocol supports random seeking. + virtual bool IsStreamed() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(FFmpegURLProtocol); +}; class FFmpegGlue : public Singleton<FFmpegGlue> { public: - // Adds a DataSource to the FFmpeg glue layer and returns a unique string that - // can be passed to FFmpeg to identify the data source. - std::string AddDataSource(DataSource* data_source); + // Adds a FFmpegProtocol to the FFmpeg glue layer and returns a unique string + // that can be passed to FFmpeg to identify the data source. + std::string AddProtocol(FFmpegURLProtocol* protocol); - // Removes a DataSource from the FFmpeg glue layer. Using strings from - // previously added DataSources will no longer work. - void RemoveDataSource(DataSource* data_source); + // Removes a FFmpegProtocol from the FFmpeg glue layer. Using strings from + // previously added FFmpegProtocols will no longer work. + void RemoveProtocol(FFmpegURLProtocol* protocol); - // Assigns the DataSource identified with by the given key to |data_source|, - // or assigns NULL if no such DataSource could be found. - void GetDataSource(const std::string& key, - scoped_refptr<DataSource>* data_source); + // Assigns the FFmpegProtocol identified with by the given key to + // |protocol|, or assigns NULL if no such FFmpegProtocol could be found. + void GetProtocol(const std::string& key, + FFmpegURLProtocol** protocol); private: // Only allow Singleton to create and delete FFmpegGlue. @@ -63,14 +91,14 @@ class FFmpegGlue : public Singleton<FFmpegGlue> { // Returns the unique key for this data source, which can be passed to // av_open_input_file as the filename. - std::string GetDataSourceKey(DataSource* data_source); + std::string GetProtocolKey(FFmpegURLProtocol* protocol); // Mutual exclusion while adding/removing items from the map. Lock lock_; - // Map between keys and DataSource references. - typedef std::map< std::string, scoped_refptr<DataSource> > DataSourceMap; - DataSourceMap data_sources_; + // Map between keys and FFmpegProtocol references. + typedef std::map<std::string, FFmpegURLProtocol*> ProtocolMap; + ProtocolMap protocols_; DISALLOW_COPY_AND_ASSIGN(FFmpegGlue); }; diff --git a/media/filters/ffmpeg_glue_unittest.cc b/media/filters/ffmpeg_glue_unittest.cc index 9f60868..904f6a5 100644 --- a/media/filters/ffmpeg_glue_unittest.cc +++ b/media/filters/ffmpeg_glue_unittest.cc @@ -17,6 +17,21 @@ using ::testing::StrictMock; namespace media { +class MockProtocol : public FFmpegURLProtocol { + public: + MockProtocol() { + } + + MOCK_METHOD2(Read, int(int size, uint8* data)); + MOCK_METHOD1(GetPosition, bool(int64* position_out)); + MOCK_METHOD1(SetPosition, bool(int64 position)); + MOCK_METHOD1(GetSize, bool(int64* size_out)); + MOCK_METHOD0(IsStreamed, bool()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockProtocol); +}; + class FFmpegGlueTest : public ::testing::Test { public: FFmpegGlueTest() { @@ -27,17 +42,17 @@ class FFmpegGlueTest : public ::testing::Test { MockFFmpeg::set(NULL); } - // Helper to open a URLContext pointing to the given mocked data source. + // Helper to open a URLContext pointing to the given mocked protocol. // Callers are expected to close the context at the end of their test. - virtual void OpenContext(MockDataSource* data_source, URLContext* context) { - // IsSeekable() is called when opening. - EXPECT_CALL(*data_source, IsSeekable()).WillOnce(Return(false)); + virtual void OpenContext(MockProtocol* protocol, URLContext* context) { + // IsStreamed() is called when opening. + EXPECT_CALL(*protocol, IsStreamed()).WillOnce(Return(true)); - // Add the data source to the glue layer and open a context. - std::string key = FFmpegGlue::get()->AddDataSource(data_source); + // Add the protocol to the glue layer and open a context. + std::string key = FFmpegGlue::get()->AddProtocol(protocol); memset(context, 0, sizeof(*context)); EXPECT_EQ(0, protocol_->url_open(context, key.c_str(), 0)); - FFmpegGlue::get()->RemoveDataSource(data_source); + FFmpegGlue::get()->RemoveProtocol(protocol); } protected: @@ -68,55 +83,55 @@ TEST_F(FFmpegGlueTest, InitializeFFmpeg) { EXPECT_TRUE(protocol_->url_write); } -TEST_F(FFmpegGlueTest, AddRemoveGetDataSource) { +TEST_F(FFmpegGlueTest, AddRemoveGetProtocol) { // Prepare testing data. FFmpegGlue* glue = FFmpegGlue::get(); - // Create our data sources and add them to the glue layer. - scoped_refptr<StrictMock<Destroyable<MockDataSource> > > data_source_a - = new StrictMock<Destroyable<MockDataSource> >(); - scoped_refptr<StrictMock<Destroyable<MockDataSource> > > data_source_b - = new StrictMock<Destroyable<MockDataSource> >(); + // Create our protocols and add them to the glue layer. + scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol_a( + new StrictMock<Destroyable<MockProtocol> >()); + scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol_b( + new StrictMock<Destroyable<MockProtocol> >()); // Make sure the keys are unique. - std::string key_a = glue->AddDataSource(data_source_a); - std::string key_b = glue->AddDataSource(data_source_b); + std::string key_a = glue->AddProtocol(protocol_a.get()); + std::string key_b = glue->AddProtocol(protocol_b.get()); EXPECT_EQ(0u, key_a.find("http://")); EXPECT_EQ(0u, key_b.find("http://")); EXPECT_NE(key_a, key_b); - // Our keys should return our data sources. - scoped_refptr<DataSource> data_source_c; - scoped_refptr<DataSource> data_source_d; - glue->GetDataSource(key_a, &data_source_c); - glue->GetDataSource(key_b, &data_source_d); - EXPECT_EQ(data_source_a, data_source_c); - EXPECT_EQ(data_source_b, data_source_d); + // Our keys should return our protocols. + FFmpegURLProtocol* protocol_c; + FFmpegURLProtocol* protocol_d; + glue->GetProtocol(key_a, &protocol_c); + glue->GetProtocol(key_b, &protocol_d); + EXPECT_EQ(protocol_a.get(), protocol_c); + EXPECT_EQ(protocol_b.get(), protocol_d); - // Adding the same DataSource should create the same key and not add an extra + // Adding the same Protocol should create the same key and not add an extra // reference. - std::string key_a2 = glue->AddDataSource(data_source_a); + std::string key_a2 = glue->AddProtocol(protocol_a.get()); EXPECT_EQ(key_a, key_a2); - glue->GetDataSource(key_a2, &data_source_c); - EXPECT_EQ(data_source_a, data_source_c); + glue->GetProtocol(key_a2, &protocol_c); + EXPECT_EQ(protocol_a.get(), protocol_c); - // Removes the data sources then releases our references. They should be + // Removes the protocols then releases our references. They should be // destroyed. InSequence s; - EXPECT_CALL(*data_source_a, OnDestroy()); - EXPECT_CALL(*data_source_b, OnDestroy()); + EXPECT_CALL(*protocol_a, OnDestroy()); + EXPECT_CALL(*protocol_b, OnDestroy()); EXPECT_CALL(mock_ffmpeg_, CheckPoint(0)); - glue->RemoveDataSource(data_source_a); - glue->GetDataSource(key_a, &data_source_c); - EXPECT_FALSE(data_source_c); - glue->GetDataSource(key_b, &data_source_d); - EXPECT_EQ(data_source_b, data_source_d); - glue->RemoveDataSource(data_source_b); - glue->GetDataSource(key_b, &data_source_d); - EXPECT_FALSE(data_source_d); - data_source_a = NULL; - data_source_b = NULL; + glue->RemoveProtocol(protocol_a.get()); + glue->GetProtocol(key_a, &protocol_c); + EXPECT_FALSE(protocol_c); + glue->GetProtocol(key_b, &protocol_d); + EXPECT_EQ(protocol_b.get(), protocol_d); + glue->RemoveProtocol(protocol_b.get()); + glue->GetProtocol(key_b, &protocol_d); + EXPECT_FALSE(protocol_d); + protocol_a.reset(); + protocol_b.reset(); // Data sources should be deleted by this point. mock_ffmpeg_.CheckPoint(0); @@ -126,41 +141,41 @@ TEST_F(FFmpegGlueTest, OpenClose) { // Prepare testing data. FFmpegGlue* glue = FFmpegGlue::get(); - // Create our data source and add them to the glue layer. - scoped_refptr<StrictMock<Destroyable<MockDataSource> > > data_source - = new StrictMock<Destroyable<MockDataSource> >(); - EXPECT_CALL(*data_source, IsSeekable()).WillOnce(Return(false)); - std::string key = glue->AddDataSource(data_source); + // Create our protocol and add them to the glue layer. + scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol( + new StrictMock<Destroyable<MockProtocol> >()); + EXPECT_CALL(*protocol, IsStreamed()).WillOnce(Return(true)); + std::string key = glue->AddProtocol(protocol.get()); // Prepare FFmpeg URLContext structure. URLContext context; memset(&context, 0, sizeof(context)); - // Test opening a URLContext with a data source that doesn't exist. + // Test opening a URLContext with a protocol that doesn't exist. EXPECT_EQ(AVERROR_IO, protocol_->url_open(&context, "foobar", 0)); - // Test opening a URLContext with our data source. + // Test opening a URLContext with our protocol. EXPECT_EQ(0, protocol_->url_open(&context, key.c_str(), 0)); EXPECT_EQ(URL_RDONLY, context.flags); - EXPECT_EQ(data_source, context.priv_data); + EXPECT_EQ(protocol.get(), context.priv_data); EXPECT_TRUE(context.is_streamed); // We're going to remove references one by one until the last reference is - // held by FFmpeg. Once we close the URLContext, the data source should be + // held by FFmpeg. Once we close the URLContext, the protocol should be // destroyed. InSequence s; EXPECT_CALL(mock_ffmpeg_, CheckPoint(0)); EXPECT_CALL(mock_ffmpeg_, CheckPoint(1)); - EXPECT_CALL(*data_source, OnDestroy()); + EXPECT_CALL(*protocol, OnDestroy()); EXPECT_CALL(mock_ffmpeg_, CheckPoint(2)); - // Remove the data source from the glue layer, releasing a reference. - glue->RemoveDataSource(data_source); + // Remove the protocol from the glue layer, releasing a reference. + glue->RemoveProtocol(protocol.get()); mock_ffmpeg_.CheckPoint(0); // Remove our own reference -- URLContext should maintain a reference. - data_source = NULL; mock_ffmpeg_.CheckPoint(1); + protocol.reset(); // Close the URLContext, which should release the final reference. EXPECT_EQ(0, protocol_->url_close(&context)); @@ -168,64 +183,64 @@ TEST_F(FFmpegGlueTest, OpenClose) { } TEST_F(FFmpegGlueTest, Write) { - scoped_refptr<StrictMock<MockDataSource> > data_source - = new StrictMock<MockDataSource>(); + scoped_ptr<StrictMock<MockProtocol> > protocol( + new StrictMock<MockProtocol>()); URLContext context; - OpenContext(data_source, &context); + OpenContext(protocol.get(), &context); const int kBufferSize = 16; uint8 buffer[kBufferSize]; - // Writing should always fail and never call the data source. + // Writing should always fail and never call the protocol. EXPECT_EQ(AVERROR_IO, protocol_->url_write(&context, NULL, 0)); EXPECT_EQ(AVERROR_IO, protocol_->url_write(&context, buffer, 0)); EXPECT_EQ(AVERROR_IO, protocol_->url_write(&context, buffer, kBufferSize)); - // Destroy the data source. + // Destroy the protocol. protocol_->url_close(&context); } TEST_F(FFmpegGlueTest, Read) { - scoped_refptr<StrictMock<MockDataSource> > data_source - = new StrictMock<MockDataSource>(); + scoped_ptr<StrictMock<MockProtocol> > protocol( + new StrictMock<MockProtocol>()); URLContext context; - OpenContext(data_source, &context); + OpenContext(protocol.get(), &context); const int kBufferSize = 16; uint8 buffer[kBufferSize]; // Reads are for the most part straight-through calls to Read(). InSequence s; - EXPECT_CALL(*data_source, Read(buffer, 0)) + EXPECT_CALL(*protocol, Read(0, buffer)) .WillOnce(Return(0)); - EXPECT_CALL(*data_source, Read(buffer, kBufferSize)) + EXPECT_CALL(*protocol, Read(kBufferSize, buffer)) .WillOnce(Return(kBufferSize)); - EXPECT_CALL(*data_source, Read(buffer, kBufferSize)) + EXPECT_CALL(*protocol, Read(kBufferSize, buffer)) .WillOnce(Return(DataSource::kReadError)); EXPECT_EQ(0, protocol_->url_read(&context, buffer, 0)); EXPECT_EQ(kBufferSize, protocol_->url_read(&context, buffer, kBufferSize)); EXPECT_EQ(AVERROR_IO, protocol_->url_read(&context, buffer, kBufferSize)); - // Destroy the data source. + // Destroy the protocol. protocol_->url_close(&context); } TEST_F(FFmpegGlueTest, Seek) { - scoped_refptr<StrictMock<MockDataSource> > data_source - = new StrictMock<MockDataSource>(); + scoped_ptr<StrictMock<MockProtocol> > protocol( + new StrictMock<MockProtocol>()); URLContext context; - OpenContext(data_source, &context); + OpenContext(protocol.get(), &context); // SEEK_SET should be a straight-through call to SetPosition(), which when // successful will return the result from GetPosition(). InSequence s; - EXPECT_CALL(*data_source, SetPosition(-16)) + EXPECT_CALL(*protocol, SetPosition(-16)) .WillOnce(Return(false)); - EXPECT_CALL(*data_source, SetPosition(16)) + EXPECT_CALL(*protocol, SetPosition(16)) .WillOnce(Return(true)); - EXPECT_CALL(*data_source, GetPosition(_)) + EXPECT_CALL(*protocol, GetPosition(_)) .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); EXPECT_EQ(AVERROR_IO, protocol_->url_seek(&context, -16, SEEK_SET)); @@ -233,19 +248,19 @@ TEST_F(FFmpegGlueTest, Seek) { // SEEK_CUR should call GetPosition() first, and if it succeeds add the offset // to the result then call SetPosition()+GetPosition(). - EXPECT_CALL(*data_source, GetPosition(_)) + EXPECT_CALL(*protocol, GetPosition(_)) .WillOnce(Return(false)); - EXPECT_CALL(*data_source, GetPosition(_)) + EXPECT_CALL(*protocol, GetPosition(_)) .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); - EXPECT_CALL(*data_source, SetPosition(16)) + EXPECT_CALL(*protocol, SetPosition(16)) .WillOnce(Return(false)); - EXPECT_CALL(*data_source, GetPosition(_)) + EXPECT_CALL(*protocol, GetPosition(_)) .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); - EXPECT_CALL(*data_source, SetPosition(16)) + EXPECT_CALL(*protocol, SetPosition(16)) .WillOnce(Return(true)); - EXPECT_CALL(*data_source, GetPosition(_)) + EXPECT_CALL(*protocol, GetPosition(_)) .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); EXPECT_EQ(AVERROR_IO, protocol_->url_seek(&context, 8, SEEK_CUR)); @@ -254,19 +269,19 @@ TEST_F(FFmpegGlueTest, Seek) { // SEEK_END should call GetSize() first, and if it succeeds add the offset // to the result then call SetPosition()+GetPosition(). - EXPECT_CALL(*data_source, GetSize(_)) + EXPECT_CALL(*protocol, GetSize(_)) .WillOnce(Return(false)); - EXPECT_CALL(*data_source, GetSize(_)) + EXPECT_CALL(*protocol, GetSize(_)) .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); - EXPECT_CALL(*data_source, SetPosition(8)) + EXPECT_CALL(*protocol, SetPosition(8)) .WillOnce(Return(false)); - EXPECT_CALL(*data_source, GetSize(_)) + EXPECT_CALL(*protocol, GetSize(_)) .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); - EXPECT_CALL(*data_source, SetPosition(8)) + EXPECT_CALL(*protocol, SetPosition(8)) .WillOnce(Return(true)); - EXPECT_CALL(*data_source, GetPosition(_)) + EXPECT_CALL(*protocol, GetPosition(_)) .WillOnce(DoAll(SetArgumentPointee<0>(8), Return(true))); EXPECT_EQ(AVERROR_IO, protocol_->url_seek(&context, -8, SEEK_END)); @@ -274,34 +289,34 @@ TEST_F(FFmpegGlueTest, Seek) { EXPECT_EQ(8, protocol_->url_seek(&context, -8, SEEK_END)); // AVSEEK_SIZE should be a straight-through call to GetSize(). - EXPECT_CALL(*data_source, GetSize(_)) + EXPECT_CALL(*protocol, GetSize(_)) .WillOnce(Return(false)); - EXPECT_CALL(*data_source, GetSize(_)) + EXPECT_CALL(*protocol, GetSize(_)) .WillOnce(DoAll(SetArgumentPointee<0>(16), Return(true))); EXPECT_EQ(AVERROR_IO, protocol_->url_seek(&context, 0, AVSEEK_SIZE)); EXPECT_EQ(16, protocol_->url_seek(&context, 0, AVSEEK_SIZE)); - // Destroy the data source. + // Destroy the protocol. protocol_->url_close(&context); } TEST_F(FFmpegGlueTest, Destroy) { - // Create our data source and add them to the glue layer. - scoped_refptr<StrictMock<Destroyable<MockDataSource> > > data_source - = new StrictMock<Destroyable<MockDataSource> >(); - std::string key = FFmpegGlue::get()->AddDataSource(data_source); + // Create our protocol and add them to the glue layer. + scoped_ptr<StrictMock<Destroyable<MockProtocol> > > protocol( + new StrictMock<Destroyable<MockProtocol> >()); + std::string key = FFmpegGlue::get()->AddProtocol(protocol.get()); - // We should expect the data source to get destroyed when the unit test + // We should expect the protocol to get destroyed when the unit test // exits. InSequence s; EXPECT_CALL(mock_ffmpeg_, CheckPoint(0)); - EXPECT_CALL(*data_source, OnDestroy()); + EXPECT_CALL(*protocol, OnDestroy()); // Remove our own reference, we shouldn't be destroyed yet. - data_source = NULL; mock_ffmpeg_.CheckPoint(0); + protocol.reset(); // ~FFmpegGlue() will be called when this unit test finishes execution. By // leaving something inside FFmpegGlue's map we get to test our cleanup code. diff --git a/media/filters/file_data_source.cc b/media/filters/file_data_source.cc index 36e0582..d440918 100644 --- a/media/filters/file_data_source.cc +++ b/media/filters/file_data_source.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <limits> + #include "base/file_util.h" #include "base/string_util.h" #include "media/base/filter_host.h" @@ -59,50 +61,35 @@ const MediaFormat& FileDataSource::media_format() { return media_format_; } -size_t FileDataSource::Read(uint8* data, size_t size) { +void FileDataSource::Read(int64 position, size_t size, uint8* data, + ReadCallback* read_callback) { DCHECK(file_); AutoLock l(lock_); if (file_) { +#if defined(OS_WIN) + if (_fseeki64(file_, position, SEEK_SET)) { + read_callback->RunWithParams( + Tuple1<size_t>(static_cast<size_t>(DataSource::kReadError))); + return; + } +#else + CHECK(position <= std::numeric_limits<int32>::max()); + // TODO(hclam): Change fseek() to support 64-bit position. + if (fseek(file_, static_cast<int32>(position), SEEK_SET)) { + read_callback->RunWithParams( + Tuple1<size_t>(static_cast<size_t>(DataSource::kReadError))); + return; + } +#endif size_t size_read = fread(data, 1, size, file_); if (size_read == size || !ferror(file_)) { - return size_read; + read_callback->RunWithParams( + Tuple1<size_t>(static_cast<size_t>(size_read))); + return; } } - return kReadError; -} - -bool FileDataSource::GetPosition(int64* position_out) { - DCHECK(position_out); - DCHECK(file_); - AutoLock l(lock_); - if (!file_) { - *position_out = 0; - return false; - } -// Linux and mac libraries don't seem to support 64 versions of seek and -// ftell. TODO(ralph): Try to figure out how to enable int64 versions on -// these platforms. -#if defined(OS_WIN) - *position_out = _ftelli64(file_); -#else - *position_out = ftell(file_); -#endif - return true; -} -bool FileDataSource::SetPosition(int64 position) { - DCHECK(file_); - AutoLock l(lock_); -#if defined(OS_WIN) - if (file_ && 0 == _fseeki64(file_, position, SEEK_SET)) { - return true; - } -#else - if (file_ && 0 == fseek(file_, static_cast<int32>(position), SEEK_SET)) { - return true; - } -#endif - return false; + read_callback->RunWithParams(Tuple1<size_t>(static_cast<size_t>(kReadError))); } bool FileDataSource::GetSize(int64* size_out) { diff --git a/media/filters/file_data_source.h b/media/filters/file_data_source.h index 5c91ce1..760b7c5 100644 --- a/media/filters/file_data_source.h +++ b/media/filters/file_data_source.h @@ -28,9 +28,8 @@ class FileDataSource : public DataSource { // Implementation of DataSource. virtual void Initialize(const std::string& url, FilterCallback* callback); virtual const MediaFormat& media_format(); - virtual size_t Read(uint8* data, size_t size); - virtual bool GetPosition(int64* position_out); - virtual bool SetPosition(int64 position); + virtual void Read(int64 position, size_t size, uint8* data, + ReadCallback* read_callback); virtual bool GetSize(int64* size_out); virtual bool IsSeekable(); diff --git a/media/filters/file_data_source_unittest.cc b/media/filters/file_data_source_unittest.cc index 9c4b9ee8..193f31a 100644 --- a/media/filters/file_data_source_unittest.cc +++ b/media/filters/file_data_source_unittest.cc @@ -14,6 +14,21 @@ using ::testing::NiceMock; using ::testing::StrictMock; +namespace { + +class ReadCallbackHandler { + public: + ReadCallbackHandler() { + } + + MOCK_METHOD1(ReadCallback, void(size_t size)); + + private: + DISALLOW_COPY_AND_ASSIGN(ReadCallbackHandler); +}; + +} // namespace + namespace media { // Returns a path to the test file which contains the string "0123456789" @@ -52,7 +67,6 @@ TEST(FileDataSourceTest, OpenFile) { // Use the mock filter host to directly call the Read and GetPosition methods. TEST(FileDataSourceTest, ReadData) { - int64 position; int64 size; uint8 ten_bytes[10]; @@ -60,28 +74,29 @@ TEST(FileDataSourceTest, ReadData) { NiceMock<MockFilterHost> host; NiceMock<MockFilterCallback> callback; scoped_refptr<FileDataSource> filter = new FileDataSource(); + filter->set_host(&host); filter->Initialize(TestFileURL(), callback.NewCallback()); EXPECT_TRUE(filter->GetSize(&size)); EXPECT_EQ(10, size); - EXPECT_TRUE(filter->GetPosition(&position)); - EXPECT_EQ(0, position); - - EXPECT_EQ(10u, filter->Read(ten_bytes, sizeof(ten_bytes))); + ReadCallbackHandler handler; + EXPECT_CALL(handler, ReadCallback(10)); + filter->Read(0, 10, ten_bytes, + NewCallback(&handler, &ReadCallbackHandler::ReadCallback)); EXPECT_EQ('0', ten_bytes[0]); EXPECT_EQ('5', ten_bytes[5]); EXPECT_EQ('9', ten_bytes[9]); - EXPECT_TRUE(filter->GetPosition(&position)); - EXPECT_EQ(10, position); - EXPECT_EQ(0u, filter->Read(ten_bytes, sizeof(ten_bytes))); - EXPECT_TRUE(filter->SetPosition(5)); - EXPECT_EQ(5u, filter->Read(ten_bytes, sizeof(ten_bytes))); + EXPECT_CALL(handler, ReadCallback(0)); + filter->Read(10, 10, ten_bytes, + NewCallback(&handler, &ReadCallbackHandler::ReadCallback)); + + EXPECT_CALL(handler, ReadCallback(5)); + filter->Read(5, 10, ten_bytes, + NewCallback(&handler, &ReadCallbackHandler::ReadCallback)); EXPECT_EQ('5', ten_bytes[0]); - EXPECT_TRUE(filter->GetPosition(&position)); - EXPECT_EQ(10, position); } // Test that FileDataSource does nothing on Seek(). |