diff options
author | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-17 19:03:59 +0000 |
---|---|---|
committer | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-17 19:03:59 +0000 |
commit | 19ffd67a4c32136d1d2abc07c97dcbb7ec5af216 (patch) | |
tree | 15ed2566ef839af4e218fc1d96880a9d1dc5ac44 /media | |
parent | 483a075709bb435ddf380aac050dc3b7a586d244 (diff) | |
download | chromium_src-19ffd67a4c32136d1d2abc07c97dcbb7ec5af216.zip chromium_src-19ffd67a4c32136d1d2abc07c97dcbb7ec5af216.tar.gz chromium_src-19ffd67a4c32136d1d2abc07c97dcbb7ec5af216.tar.bz2 |
API to allow strategy class to work on the output buffer of OpenMAX
Added interface of OmxOutputSink to interact with OmxCodec to
perform buffer negotiation and buffer read signaling.
TEST=media_unittests --gtest_filter=Omx*
BUG=32753
BUG=32754
BUG=32870
Review URL: http://codereview.chromium.org/593047
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@39247 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/filters/omx_video_decode_engine.cc | 58 | ||||
-rw-r--r-- | media/filters/omx_video_decode_engine.h | 30 | ||||
-rw-r--r-- | media/media.gyp | 11 | ||||
-rw-r--r-- | media/omx/mock_omx.cc | 139 | ||||
-rw-r--r-- | media/omx/mock_omx.h | 94 | ||||
-rw-r--r-- | media/omx/omx_codec.cc | 282 | ||||
-rw-r--r-- | media/omx/omx_codec.h | 106 | ||||
-rw-r--r-- | media/omx/omx_codec_unittest.cc | 490 | ||||
-rw-r--r-- | media/omx/omx_output_sink.h | 144 | ||||
-rw-r--r-- | media/tools/omx_test/file_sink.cc (renamed from media/tools/omx_test/file_writer_util.cc) | 63 | ||||
-rw-r--r-- | media/tools/omx_test/file_sink.h (renamed from media/tools/omx_test/file_writer_util.h) | 33 | ||||
-rw-r--r-- | media/tools/omx_test/omx_test.cc | 40 |
12 files changed, 1343 insertions, 147 deletions
diff --git a/media/filters/omx_video_decode_engine.cc b/media/filters/omx_video_decode_engine.cc index 9a22302..034104c 100644 --- a/media/filters/omx_video_decode_engine.cc +++ b/media/filters/omx_video_decode_engine.cc @@ -57,7 +57,9 @@ void OmxVideoDecodeEngine::Initialize(AVStream* stream, Task* done_cb) { memset(&output_format, 0, sizeof(output_format)); input_format.codec = OmxConfigurator::kCodecH264; output_format.codec = OmxConfigurator::kCodecRaw; - omx_codec_->Setup(new OmxDecoderConfigurator(input_format, output_format)); + omx_configurator_.reset( + new OmxDecoderConfigurator(input_format, output_format)); + omx_codec_->Setup(omx_configurator_.get(), NULL); omx_codec_->SetErrorCallback( NewCallback(this, &OmxVideoDecodeEngine::OnHardwareError)); omx_codec_->SetFormatCallback( @@ -143,8 +145,39 @@ void OmxVideoDecodeEngine::Stop(Callback0::Type* done_cb) { state_ = kStopped; } -void OmxVideoDecodeEngine::OnReadComplete(uint8* buffer, int size) { +bool OmxVideoDecodeEngine::AllocateEGLImages( + int width, int height, std::vector<EGLImageKHR>* images) { + NOTREACHED() << "This method is never used"; + return false; +} + +void OmxVideoDecodeEngine::ReleaseEGLImages( + const std::vector<EGLImageKHR>& images) { + NOTREACHED() << "This method is never used"; +} + +void OmxVideoDecodeEngine::UseThisBuffer(int buffer_id, + OMX_BUFFERHEADERTYPE* buffer) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + omx_buffers_.push_back(std::make_pair(buffer_id, buffer)); +} + +void OmxVideoDecodeEngine::StopUsingThisBuffer(int id) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + omx_buffers_.erase(FindBuffer(id)); +} + +void OmxVideoDecodeEngine::BufferReady(int buffer_id, + BufferUsedCallback* callback) { DCHECK_EQ(message_loop_, MessageLoop::current()); + CHECK(buffer_id != OmxCodec::kEosBuffer); + CHECK(callback); + + // Obtain the corresponding OMX_BUFFERHEADERTYPE. + OmxBufferList::iterator i = FindBuffer(buffer_id); + uint8* buffer = i->second->pBuffer; + int size = i->second->nFilledLen; + if ((size_t)size != frame_bytes_) { LOG(ERROR) << "Read completed with weird size: " << size; } @@ -183,7 +216,17 @@ void OmxVideoDecodeEngine::OnReadComplete(uint8* buffer, int size) { frame->data + pixels + pixels /4, pixels / 4); } + + // Notify the read request to this object has been fulfilled. request.done_cb->Run(); + + // Notify OmxCodec that we have finished using this buffer. + callback->RunWithParams(MakeTuple(buffer_id)); +} + +void OmxVideoDecodeEngine::OnReadComplete( + int buffer_id, OmxOutputSink::BufferUsedCallback* callback) { + BufferReady(buffer_id, callback); } bool OmxVideoDecodeEngine::IsFrameComplete(const YuvFrame* frame) { @@ -212,4 +255,15 @@ void OmxVideoDecodeEngine::MergeBytesFrameQueue(uint8* buffer, int size) { } } +OmxVideoDecodeEngine::OmxBufferList::iterator +OmxVideoDecodeEngine::FindBuffer(int buffer_id) { + for (OmxVideoDecodeEngine::OmxBufferList::iterator i = omx_buffers_.begin(); + i != omx_buffers_.end(); ++i) { + if (i->first == buffer_id) + return i; + } + NOTREACHED() << "Invalid buffer id received"; + return omx_buffers_.end(); +} + } // namespace media diff --git a/media/filters/omx_video_decode_engine.h b/media/filters/omx_video_decode_engine.h index 71a006f..4325853 100644 --- a/media/filters/omx_video_decode_engine.h +++ b/media/filters/omx_video_decode_engine.h @@ -5,7 +5,9 @@ #ifndef MEDIA_FILTERS_OMX_VIDEO_DECODE_ENGINE_H_ #define MEDIA_FILTERS_OMX_VIDEO_DECODE_ENGINE_H_ +#include <functional> #include <list> +#include <vector> #include "base/lock.h" #include "base/task.h" @@ -22,7 +24,10 @@ struct AVStream; namespace media { -class OmxVideoDecodeEngine : public VideoDecodeEngine { +// TODO(hclam): +// Extract the OmxOutputSink implementation to a strategy class. +class OmxVideoDecodeEngine : public VideoDecodeEngine, + public OmxOutputSink { public: OmxVideoDecodeEngine(); virtual ~OmxVideoDecodeEngine(); @@ -46,7 +51,19 @@ class OmxVideoDecodeEngine : public VideoDecodeEngine { message_loop_ = message_loop; }; + // Implementation of OmxOutputSink interface. + virtual bool ProvidesEGLImages() const { return false; } + virtual bool AllocateEGLImages(int width, int height, + std::vector<EGLImageKHR>* images); + virtual void ReleaseEGLImages(const std::vector<EGLImageKHR>& images); + virtual void UseThisBuffer(int buffer_id, OMX_BUFFERHEADERTYPE* buffer); + virtual void StopUsingThisBuffer(int id); + virtual void BufferReady(int buffer_id, BufferUsedCallback* callback); + private: + typedef std::pair<int, OMX_BUFFERHEADERTYPE*> OmxOutputUnit; + typedef std::vector<OmxOutputUnit> OmxBufferList; + struct YuvFrame { // TODO(ajwong): Please avoid ever using this class anywhere else until // we've consolidated the buffer struct. @@ -79,7 +96,8 @@ class OmxVideoDecodeEngine : public VideoDecodeEngine { virtual void OnFeedDone(OmxInputBuffer* buffer); virtual void OnHardwareError(); - virtual void OnReadComplete(uint8* buffer, int size); + virtual void OnReadComplete( + int buffer_id, OmxOutputSink::BufferUsedCallback* callback); virtual void OnFormatChange( const OmxConfigurator::MediaFormat& input_format, const OmxConfigurator::MediaFormat& output_format); @@ -87,6 +105,7 @@ class OmxVideoDecodeEngine : public VideoDecodeEngine { virtual bool DecodedFrameAvailable(); virtual void MergeBytesFrameQueue(uint8* buffer, int size); virtual bool IsFrameComplete(const YuvFrame* frame); + virtual OmxBufferList::iterator FindBuffer(int buffer_id); State state_; size_t frame_bytes_; @@ -98,14 +117,17 @@ class OmxVideoDecodeEngine : public VideoDecodeEngine { // TODO(hclam): We should let OmxCodec handle this case. bool has_fed_on_eos_; // Used to avoid sending an end of stream to - // OpenMax twice since OpenMax does not always - // handle this nicely. + // OpenMax twice since OpenMax does not always + // handle this nicely. std::list<YuvFrame*> yuv_frame_queue_; std::list<DecodeRequest> decode_request_queue_; scoped_refptr<media::OmxCodec> omx_codec_; + scoped_ptr<media::OmxConfigurator> omx_configurator_; MessageLoop* message_loop_; + OmxBufferList omx_buffers_; + DISALLOW_COPY_AND_ASSIGN(OmxVideoDecodeEngine); }; diff --git a/media/media.gyp b/media/media.gyp index eb5c392..8391bb1 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -159,6 +159,10 @@ '../testing/gmock.gyp:gmock', '../testing/gtest.gyp:gtest', '../third_party/ffmpeg/ffmpeg.gyp:ffmpeg', + '../third_party/openmax/openmax.gyp:il', + ], + 'sources!': [ + '../third_party/openmax/omx_stub.cc', ], 'sources': [ 'audio/audio_util_unittest.cc', @@ -192,6 +196,9 @@ 'filters/file_data_source_unittest.cc', 'filters/video_decoder_impl_unittest.cc', 'filters/video_renderer_base_unittest.cc', + 'omx/mock_omx.cc', + 'omx/mock_omx.h', + 'omx/omx_codec_unittest.cc', 'omx/omx_input_buffer_unittest.cc', ], 'conditions': [ @@ -268,8 +275,8 @@ 'tools/omx_test/color_space_util.h', 'tools/omx_test/file_reader_util.cc', 'tools/omx_test/file_reader_util.h', - 'tools/omx_test/file_writer_util.cc', - 'tools/omx_test/file_writer_util.h', + 'tools/omx_test/file_sink.cc', + 'tools/omx_test/file_sink.h', 'tools/omx_test/omx_test.cc', ], }, diff --git a/media/omx/mock_omx.cc b/media/omx/mock_omx.cc new file mode 100644 index 0000000..010661f --- /dev/null +++ b/media/omx/mock_omx.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2010 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/omx/mock_omx.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +MockOmx* MockOmx::instance_ = NULL; + +// Static stub methods. They redirect method calls back to the mock object. +static OMX_ERRORTYPE MockSendCommand(OMX_HANDLETYPE component, + OMX_COMMANDTYPE command, + OMX_U32 param1, + OMX_PTR command_data) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->SendCommand(command, param1, command_data); +} + +static OMX_ERRORTYPE MockGetParameter(OMX_HANDLETYPE component, + OMX_INDEXTYPE param_index, + OMX_PTR structure) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->GetParameter(param_index, structure); +} + +static OMX_ERRORTYPE MockSetParameter(OMX_HANDLETYPE component, + OMX_INDEXTYPE param_index, + OMX_PTR structure) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->SetParameter(param_index, structure); +} + +static OMX_ERRORTYPE MockGetConfig(OMX_HANDLETYPE component, + OMX_INDEXTYPE index, + OMX_PTR structure) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->GetConfig(index, structure); +} + +static OMX_ERRORTYPE MockSetConfig(OMX_HANDLETYPE component, + OMX_INDEXTYPE index, + OMX_PTR structure) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->SetConfig(index, structure); +} + +static OMX_ERRORTYPE MockAllocateBuffer(OMX_HANDLETYPE component, + OMX_BUFFERHEADERTYPE** buffer, + OMX_U32 port_index, + OMX_PTR app_private, + OMX_U32 size_bytes) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->AllocateBuffer(buffer, port_index, app_private, + size_bytes); +} + +static OMX_ERRORTYPE MockFreeBuffer(OMX_HANDLETYPE component, + OMX_U32 port_index, + OMX_BUFFERHEADERTYPE* buffer) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->FreeBuffer(port_index, buffer); +} + +static OMX_ERRORTYPE MockEmptyThisBuffer(OMX_HANDLETYPE component, + OMX_BUFFERHEADERTYPE* buffer) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->EmptyThisBuffer(buffer); +} + +static OMX_ERRORTYPE MockFillThisBuffer(OMX_HANDLETYPE component, + OMX_BUFFERHEADERTYPE* buffer) { + CHECK(MockOmx::get()->component() == (OMX_COMPONENTTYPE*)component); + return MockOmx::get()->FillThisBuffer(buffer); +} + +// Stub methods to export symbols used for OpenMAX. +extern "C" { + +OMX_ERRORTYPE OMX_Init() { + return MockOmx::get()->Init(); +} + +OMX_ERRORTYPE OMX_Deinit() { + return MockOmx::get()->Deinit(); +} + +OMX_ERRORTYPE OMX_GetHandle( + OMX_HANDLETYPE* handle, OMX_STRING name, OMX_PTR app_private, + OMX_CALLBACKTYPE* callbacks) { + return MockOmx::get()->GetHandle(handle, name, app_private, callbacks); +} + +OMX_ERRORTYPE OMX_FreeHandle(OMX_HANDLETYPE handle) { + return MockOmx::get()->FreeHandle(handle); +} + +OMX_ERRORTYPE OMX_GetComponentsOfRole(OMX_STRING name, OMX_U32* roles, + OMX_U8** component_names) { + return MockOmx::get()->GetComponentsOfRole(name, roles, component_names); +} + +} // extern "C" + +MockOmx::MockOmx() { + memset(&callbacks_, 0, sizeof(callbacks_)); + memset(&component_, 0, sizeof(component_)); + + // Setup the function pointers to the static methods. They will redirect back + // to this mock object. + component_.SendCommand = &MockSendCommand; + component_.GetParameter = &MockGetParameter; + component_.SetParameter = &MockSetParameter; + component_.GetConfig = &MockGetConfig; + component_.SetConfig = &MockSetConfig; + component_.AllocateBuffer = &MockAllocateBuffer; + component_.FreeBuffer = &MockFreeBuffer; + component_.EmptyThisBuffer = &MockEmptyThisBuffer; + component_.FillThisBuffer = &MockFillThisBuffer; + + // Save this instance to static member. + CHECK(!instance_); + instance_ = this; +} + +MockOmx::~MockOmx() { + CHECK(instance_); + instance_ = NULL; +} + +// static +MockOmx* MockOmx::get() { + return instance_; +} + +} // namespace media diff --git a/media/omx/mock_omx.h b/media/omx/mock_omx.h new file mode 100644 index 0000000..00ec378 --- /dev/null +++ b/media/omx/mock_omx.h @@ -0,0 +1,94 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_OMX_MOCK_OMX_H_ +#define MEDIA_OMX_MOCK_OMX_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "third_party/openmax/il/OMX_Component.h" +#include "third_party/openmax/il/OMX_Core.h" + +namespace media { + +class MockOmx { + public: + MockOmx(); + virtual ~MockOmx(); + + // The following mock methods are component specific. + MOCK_METHOD3(SendCommand, OMX_ERRORTYPE( + OMX_COMMANDTYPE command, + OMX_U32 param1, + OMX_PTR command_data)); + + MOCK_METHOD2(GetParameter, OMX_ERRORTYPE( + OMX_INDEXTYPE param_index, + OMX_PTR structure)); + + MOCK_METHOD2(SetParameter, OMX_ERRORTYPE( + OMX_INDEXTYPE param_index, + OMX_PTR structure)); + + MOCK_METHOD2(GetConfig, OMX_ERRORTYPE( + OMX_INDEXTYPE index, + OMX_PTR structure)); + + MOCK_METHOD2(SetConfig, OMX_ERRORTYPE( + OMX_INDEXTYPE index, + OMX_PTR structure)); + + MOCK_METHOD4(AllocateBuffer, OMX_ERRORTYPE( + OMX_BUFFERHEADERTYPE** buffer, + OMX_U32 port_index, + OMX_PTR app_private, + OMX_U32 size_bytes)); + + MOCK_METHOD2(FreeBuffer, OMX_ERRORTYPE( + OMX_U32 port_index, + OMX_BUFFERHEADERTYPE* buffer)); + + MOCK_METHOD1(EmptyThisBuffer, OMX_ERRORTYPE( + OMX_BUFFERHEADERTYPE* buffer)); + + MOCK_METHOD1(FillThisBuffer, OMX_ERRORTYPE( + OMX_BUFFERHEADERTYPE* buffer)); + + // The following mock methods are defined global. + MOCK_METHOD0(Init, OMX_ERRORTYPE()); + MOCK_METHOD0(Deinit, OMX_ERRORTYPE()); + + MOCK_METHOD4(GetHandle, OMX_ERRORTYPE( + OMX_HANDLETYPE* handle, + OMX_STRING name, + OMX_PTR app_private, + OMX_CALLBACKTYPE* callbacks)); + + MOCK_METHOD1(FreeHandle, OMX_ERRORTYPE( + OMX_HANDLETYPE handle)); + + MOCK_METHOD3(GetComponentsOfRole, OMX_ERRORTYPE( + OMX_STRING name, + OMX_U32* roles, + OMX_U8** component_names)); + + OMX_CALLBACKTYPE* callbacks() { return &callbacks_; } + OMX_COMPONENTTYPE* component() { return &component_; } + + // Getter for the global instance of MockOmx. + static MockOmx* get(); + + private: + static MockOmx* instance_; + + OMX_CALLBACKTYPE callbacks_; + OMX_COMPONENTTYPE component_; + + DISALLOW_COPY_AND_ASSIGN(MockOmx); +}; + +} // namespace media + +#endif // MEDIA_OMX_MOCK_OMX_H_ diff --git a/media/omx/omx_codec.cc b/media/omx/omx_codec.cc index b464af7..7699513 100644 --- a/media/omx/omx_codec.cc +++ b/media/omx/omx_codec.cc @@ -2,6 +2,7 @@ // source code is governed by a BSD-style license that can be found in the // LICENSE file. +#include <algorithm> #include <string> #include "base/logging.h" @@ -10,9 +11,14 @@ #include "base/string_util.h" #include "media/omx/omx_codec.h" #include "media/omx/omx_input_buffer.h" +#include "media/omx/omx_output_sink.h" namespace media { +#if !defined(COMPILER_MSVC) +int const OmxCodec::kEosBuffer; +#endif + template <typename T> static void ResetPortHeader(const OmxCodec& dec, T* param) { memset(param, 0, sizeof(T)); @@ -40,16 +46,20 @@ OmxCodec::~OmxCodec() { DCHECK_EQ(0u, input_buffers_.size()); DCHECK_EQ(0u, output_buffers_.size()); DCHECK(available_input_buffers_.empty()); - DCHECK(available_output_buffers_.empty()); + DCHECK(output_buffers_in_use_.empty()); DCHECK(input_queue_.empty()); DCHECK(output_queue_.empty()); } -void OmxCodec::Setup(OmxConfigurator* configurator) { +void OmxCodec::Setup(OmxConfigurator* configurator, + OmxOutputSink* output_sink) { DCHECK_EQ(kEmpty, state_); CHECK(configurator); - configurator_.reset(configurator); + CHECK(output_sink); + + configurator_ = configurator; + output_sink_ = output_sink; } void OmxCodec::SetErrorCallback(Callback* callback) { @@ -63,7 +73,8 @@ void OmxCodec::SetFormatCallback(FormatCallback* callback) { } void OmxCodec::Start() { - CHECK(configurator_.get()); + CHECK(configurator_); + CHECK(output_sink_); message_loop_->PostTask( FROM_HERE, @@ -130,6 +141,11 @@ void OmxCodec::StopTask(Callback* callback) { FreeInputQueue(); FreeOutputQueue(); + // TODO(hclam): We should wait for all output buffers to come back from + // output sink to proceed to stop. The proper way to do this is + // transition to a StopWaitingForBuffers state and wait until all buffers + // are received to proceed. + if (GetState() == kExecuting) StateTransitionTask(kIdle); // TODO(hclam): The following two transitions may not be correct. @@ -148,17 +164,22 @@ void OmxCodec::ReadTask(ReadCallback* callback) { // Don't accept read request on error state. if (!CanAcceptOutput()) { - callback->RunWithParams(MakeTuple(static_cast<uint8*>(NULL), 0)); + callback->Run( + kEosBuffer, + static_cast<OmxOutputSink::BufferUsedCallback*>(NULL)); delete callback; return; } + // If output has been reached then enqueue an end-of-stream buffer. + if (output_eos_) + output_buffers_ready_.push(kEosBuffer); + // Queue this request. output_queue_.push(callback); - // Make our best effort to serve the request and read - // from the decoder. - FillBufferTask(); + // Make our best effort to serve the request. + FulfillOneRead(); } void OmxCodec::FeedTask(scoped_refptr<OmxInputBuffer> buffer, @@ -185,7 +206,7 @@ void OmxCodec::FeedTask(scoped_refptr<OmxInputBuffer> buffer, bool OmxCodec::AllocateInputBuffers() { DCHECK_EQ(message_loop_, MessageLoop::current()); - for(int i = 0; i < input_buffer_count_; ++i) { + for (int i = 0; i < input_buffer_count_; ++i) { OMX_BUFFERHEADERTYPE* buffer; OMX_ERRORTYPE error = OMX_AllocateBuffer(component_handle_, &buffer, input_port_, @@ -198,22 +219,39 @@ bool OmxCodec::AllocateInputBuffers() { return true; } -// This method assumes OMX_AllocateBuffer() will allocate -// buffer internally. If this is not the case we need to -// call OMX_UseBuffer() to allocate buffer manually and -// assign to the headers. +// This method assumes OMX_AllocateBuffer() will allocate buffer +// header internally. In additional to that memory that holds the +// header, the same method call will allocate memory for holding +// output data. If we use EGL images for holding output data, +// the memory allocation will be done externally. bool OmxCodec::AllocateOutputBuffers() { DCHECK_EQ(message_loop_, MessageLoop::current()); - for(int i = 0; i < output_buffer_count_; ++i) { - OMX_BUFFERHEADERTYPE* buffer; - OMX_ERRORTYPE error = - OMX_AllocateBuffer(component_handle_, &buffer, output_port_, - NULL, output_buffer_size_); - if (error != OMX_ErrorNone) - return false; - output_buffers_.push_back(buffer); + if (output_sink_->ProvidesEGLImages()) { + // TODO(hclam): Do the following things here: + // 1. Call output_sink_->AllocateEGLImages() to allocate some + // EGL images from the sink component. + // 2. Call OMX_UseEGLImage() to assign the images to the output + // port. + NOTIMPLEMENTED(); + } else { + for (int i = 0; i < output_buffer_count_; ++i) { + OMX_BUFFERHEADERTYPE* buffer; + OMX_ERRORTYPE error = + OMX_AllocateBuffer(component_handle_, &buffer, output_port_, + NULL, output_buffer_size_); + if (error != OMX_ErrorNone) + return false; + output_buffers_.push_back(buffer); + } } + + // Tell the sink component to use the buffers for output. + for (size_t i = 0; i < output_buffers_.size(); ++i) { + output_sink_->UseThisBuffer(static_cast<int>(i), + output_buffers_[i]); + } + return true; } @@ -221,7 +259,7 @@ void OmxCodec::FreeInputBuffers() { DCHECK_EQ(message_loop_, MessageLoop::current()); // Calls to OMX to free buffers. - for(size_t i = 0; i < input_buffers_.size(); ++i) + for (size_t i = 0; i < input_buffers_.size(); ++i) OMX_FreeBuffer(component_handle_, input_port_, input_buffers_[i]); input_buffers_.clear(); @@ -234,14 +272,24 @@ void OmxCodec::FreeInputBuffers() { void OmxCodec::FreeOutputBuffers() { DCHECK_EQ(message_loop_, MessageLoop::current()); + // First we need to make sure output sink is not using the buffer so + // tell it to stop using our buffer headers. + // TODO(hclam): We should make this an asynchronous call so that + // we'll wait until all buffers are not used. + for (size_t i = 0; i < output_buffers_.size(); ++i) + output_sink_->StopUsingThisBuffer(static_cast<int>(i)); + // Calls to OMX to free buffers. - for(size_t i = 0; i < output_buffers_.size(); ++i) + for (size_t i = 0; i < output_buffers_.size(); ++i) OMX_FreeBuffer(component_handle_, output_port_, output_buffers_[i]); output_buffers_.clear(); - // Empty available buffer queue. - while (!available_output_buffers_.empty()) { - available_output_buffers_.pop(); + // If we have asked the sink component to provide us EGL images for + // output we need to tell it we are done with those images. + // TODO(hclam): Implement this correctly. + if (output_sink_->ProvidesEGLImages()) { + std::vector<EGLImageKHR> images; + output_sink_->ReleaseEGLImages(images); } } @@ -261,9 +309,7 @@ void OmxCodec::FreeOutputQueue() { DCHECK_EQ(message_loop_, MessageLoop::current()); while (!output_queue_.empty()) { - ReadCallback* callback = output_queue_.front(); - callback->Run(static_cast<uint8*>(NULL), 0); - delete callback; + delete output_queue_.front(); output_queue_.pop(); } } @@ -301,8 +347,8 @@ void OmxCodec::Transition_EmptyToLoaded() { std::string role_name = configurator_->GetRoleName(); OMX_U32 roles = 0; omxresult = OMX_GetComponentsOfRole( - const_cast<OMX_STRING>(role_name.c_str()), - &roles, 0); + const_cast<OMX_STRING>(role_name.c_str()), + &roles, 0); if (omxresult != OMX_ErrorNone || roles == 0) { LOG(ERROR) << "Unsupported Role: " << role_name.c_str(); StateTransitionTask(kError); @@ -317,13 +363,14 @@ void OmxCodec::Transition_EmptyToLoaded() { component_names[i] = new OMX_U8[kMaxComponentNameLength]; omxresult = OMX_GetComponentsOfRole( - const_cast<OMX_STRING>(role_name.c_str()), - &roles, component_names); + const_cast<OMX_STRING>(role_name.c_str()), + &roles, component_names); // Use first component only. Copy the name of the first component // so that we could free the memory. + std::string component_name; if (omxresult == OMX_ErrorNone) - component_name_ = reinterpret_cast<char*>(component_names[0]); + component_name = reinterpret_cast<char*>(component_names[0]); for (size_t i = 0; i < roles; ++i) delete [] component_names[i]; @@ -337,7 +384,7 @@ void OmxCodec::Transition_EmptyToLoaded() { // 3. Get the handle to the component. After OMX_GetHandle(), // the component is in loaded state. - OMX_STRING component = const_cast<OMX_STRING>(component_name_.c_str()); + OMX_STRING component = const_cast<OMX_STRING>(component_name.c_str()); OMX_HANDLETYPE handle = reinterpret_cast<OMX_HANDLETYPE>(component_handle_); omxresult = OMX_GetHandle(&handle, component, this, &callback); component_handle_ = reinterpret_cast<OMX_COMPONENTTYPE*>(handle); @@ -659,7 +706,7 @@ void OmxCodec::Transition_LoadedToEmpty() { OMX_ERRORTYPE result = OMX_FreeHandle(component_handle_); if (result != OMX_ErrorNone) { LOG(ERROR) << "Terminate: OMX_FreeHandle() error. " - "Error code: " << result; + << "Error code: " << result; } component_handle_ = NULL; @@ -889,7 +936,7 @@ bool OmxCodec::ConfigureIOPorts() { &input_port_def); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "GetParameter(OMX_IndexParamPortDefinition) " - "for input port failed"; + << "for input port failed"; return false; } if (OMX_DirInput != input_port_def.eDir) { @@ -905,7 +952,7 @@ bool OmxCodec::ConfigureIOPorts() { &output_port_def); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "GetParameter(OMX_IndexParamPortDefinition) " - "for output port failed"; + << "for output port failed"; return false; } if (OMX_DirOutput != output_port_def.eDir) { @@ -927,7 +974,8 @@ bool OmxCodec::CanEmptyBuffer() { } bool OmxCodec::CanFillBuffer() { - // Make sure that we are staying in the executing state. + // Make sure that we are staying in the executing state and end-of-stream + // has not been reached. return GetState() == kExecuting && GetState() == GetNextState(); } @@ -1002,50 +1050,126 @@ void OmxCodec::EmptyBufferTask() { void OmxCodec::FillBufferCompleteTask(OMX_BUFFERHEADERTYPE* buffer) { DCHECK_EQ(message_loop_, MessageLoop::current()); + // If we are not in a right state to receive this buffer then return + // immediately. + // This condition is hit when we disable the output port and we are + // not in executing state. In that case ignore the buffer. + // TODO(hclam): We should count the number of buffers received with + // this condition to make sure the disable command has completed. if (!CanFillBuffer()) return; - // Enqueue the decoded buffer. - available_output_buffers_.push(buffer); + // This buffer is received with decoded frame. Enqueue it and make it + // ready to be consumed by reads. + int buffer_id = kEosBuffer; + for (size_t i = 0; output_buffers_.size(); ++i) { + if (output_buffers_[i] == buffer) { + buffer_id = i; + break; + } + } - // Fulfill read requests and read more from decoder. - FillBufferTask(); + // If the buffer received from the component doesn't exist in our + // list then we have an error. + if (buffer_id == kEosBuffer) { + LOG(ERROR) << "Received an unknown output buffer"; + StateTransitionTask(kError); + return; + } + + // Determine if the buffer received is a end-of-stream buffer. If + // the condition is true then assign a EOS id to the buffer. + if (buffer->nFlags & OMX_BUFFERFLAG_EOS || !buffer->nFilledLen) { + buffer_id = kEosBuffer; + output_eos_ = true; + } + output_buffers_ready_.push(buffer_id); + + // Try to fulfill one read request. + FulfillOneRead(); } -void OmxCodec::FillBufferTask() { +void OmxCodec::FulfillOneRead() { DCHECK_EQ(message_loop_, MessageLoop::current()); - if (!CanFillBuffer()) - return; - - // Loop for all available output buffers and output requests. When we hit - // EOS then stop. - while (!output_queue_.empty() && - !available_output_buffers_.empty() && - !output_eos_) { + if (!output_queue_.empty() && !output_buffers_ready_.empty()) { + int buffer_id = output_buffers_ready_.front(); + output_buffers_ready_.pop(); ReadCallback* callback = output_queue_.front(); output_queue_.pop(); - OMX_BUFFERHEADERTYPE* omx_buffer = available_output_buffers_.front(); - available_output_buffers_.pop(); - - // Give the output data to the callback but it doesn't own this buffer. - int filled = omx_buffer->nFilledLen; - if (omx_buffer->nFlags & OMX_BUFFERFLAG_EOS) { - output_eos_ = true; - filled = 0; + + // If the buffer is real then save it to the in-use list. + // Otherwise if it is an end-of-stream buffer then just drop it. + if (buffer_id != kEosBuffer) { + output_buffers_in_use_.push_back(buffer_id); + callback->Run(buffer_id, + NewCallback(this, &OmxCodec::BufferUsedCallback)); + } else { + callback->Run(kEosBuffer, + static_cast<OmxOutputSink::BufferUsedCallback*>(NULL)); } - callback->RunWithParams(MakeTuple(omx_buffer->pBuffer, filled)); delete callback; + } +} - omx_buffer->nOutputPortIndex = output_port_; - omx_buffer->pAppPrivate = this; - omx_buffer->nFlags &= ~OMX_BUFFERFLAG_EOS; - OMX_ERRORTYPE ret = OMX_FillThisBuffer(component_handle_, omx_buffer); - if (OMX_ErrorNone != ret) { - LOG(ERROR) << "OMX_FillThisBuffer() failed with result " << ret; - StateTransitionTask(kError); - return; - } +void OmxCodec::BufferUsedCallback(int buffer_id) { + // If this method is called on the message loop where OmxCodec belongs, we + // execute the task directly to save posting another task. + if (message_loop_ == MessageLoop::current()) { + BufferUsedTask(buffer_id); + return; + } + + message_loop_->PostTask( + FROM_HERE, + NewRunnableMethod(this, &OmxCodec::BufferUsedTask, buffer_id)); +} + +// Handling end-of-stream: +// Note that after we first receive the end-of-stream, we'll continue +// to call FillThisBuffer() with the next buffer receievd from +// OmxOutputSink. The end result is we'll call at most +// |output_buffer_count_| of FillThisBuffer() that are expected to +// receive end-of-stream buffers from OpenMAX. +// It is possible to not submit FillThisBuffer() after the first +// end-of-stream buffer is received from OpenMAX, but that will complicate +// the logic and so we rely on OpenMAX to do the right thing. +void OmxCodec::BufferUsedTask(int buffer_id) { + DCHECK_EQ(message_loop_, MessageLoop::current()); + + // Make sure an end-of-stream buffer id is not received here. + CHECK(buffer_id != kEosBuffer); + + // The use of |output_buffers_in_use_| is just a precaution. So we can + // actually move it to a debugging section. + OutputBuffersInUseSet::iterator iter = + std::find(output_buffers_in_use_.begin(), + output_buffers_in_use_.end(), + buffer_id); + + if (iter == output_buffers_in_use_.end()) { + LOG(ERROR) << "Received an unknown buffer id: " << buffer_id; + StateTransitionTask(kError); + } + output_buffers_in_use_.erase(iter); + + // We'll try to issue more FillThisBuffer() to the decoder. + // If we can't do it now then just return. + if (!CanFillBuffer()) + return; + + CHECK(buffer_id >= 0 && + buffer_id < static_cast<int>(output_buffers_.size())); + OMX_BUFFERHEADERTYPE* omx_buffer = output_buffers_[buffer_id]; + + omx_buffer->nOutputPortIndex = output_port_; + omx_buffer->nFlags &= ~OMX_BUFFERFLAG_EOS; + omx_buffer->pAppPrivate = this; + OMX_ERRORTYPE ret = OMX_FillThisBuffer(component_handle_, omx_buffer); + if (OMX_ErrorNone != ret) { + LOG(ERROR) << "OMX_FillThisBuffer() failed with result " << ret; + StateTransitionTask(kError); + return; } } @@ -1065,19 +1189,13 @@ void OmxCodec::InitialFillBuffer() { if (!CanFillBuffer()) return; - // We'll use all the available output buffers so clear the queue - // just to be safe. - while (!available_output_buffers_.empty()) { - available_output_buffers_.pop(); - } - // Ask the decoder to fill the output buffers. for (size_t i = 0; i < output_buffers_.size(); ++i) { OMX_BUFFERHEADERTYPE* omx_buffer = output_buffers_[i]; omx_buffer->nOutputPortIndex = output_port_; - omx_buffer->pAppPrivate = this; // Need to clear the EOS flag. omx_buffer->nFlags &= ~OMX_BUFFERFLAG_EOS; + omx_buffer->pAppPrivate = this; OMX_ERRORTYPE ret = OMX_FillThisBuffer(component_handle_, omx_buffer); if (OMX_ErrorNone != ret) { @@ -1145,11 +1263,11 @@ void OmxCodec::FillBufferCallbackInternal( // static OMX_ERRORTYPE OmxCodec::EventHandler(OMX_HANDLETYPE component, - OMX_PTR priv_data, - OMX_EVENTTYPE event, - OMX_U32 data1, - OMX_U32 data2, - OMX_PTR event_data) { + OMX_PTR priv_data, + OMX_EVENTTYPE event, + OMX_U32 data1, + OMX_U32 data2, + OMX_PTR event_data) { OmxCodec* decoder = static_cast<OmxCodec*>(priv_data); decoder->EventHandlerInternal(component, event, data1, data2, event_data); return OMX_ErrorNone; diff --git a/media/omx/omx_codec.h b/media/omx/omx_codec.h index 9c82bd5..ebc1e74 100644 --- a/media/omx/omx_codec.h +++ b/media/omx/omx_codec.h @@ -10,19 +10,56 @@ // Operations on this object are all asynchronous and this object // requires a message loop that it works on. // +// OWNERSHIP +// +// The OmxCodec works with two external objects, they are: +// 1. OmxConfigurator +// This object is given to OmxCodec to perform port configuration. +// 2. OmxOutputSink +// This object is given to OmxCodec to perform output buffer negotiation. +// +// These two external objects are provided and destroyed externally. Their +// references are given to OmxCodec and client application is responsible +// for cleaning them. +// +// INTERACTION WITH EXTERNAL OBJECTS +// +// ................ ............ +// | Configurator | <------------- | OmxCodec | +// ................ ............ +// Read / Feed -----------' ' +// .-----------' v Buffer Allocation +// .......... ............... +// | Client | ------------------> | OutputSink | +// .......... Buffer Ready ............... +// +// THREADING +// +// OmxCodec is given a message loop to run on. There is a strong gurantee +// that all callbacks given to it will be executed on this message loop. +// Communicatations with OmxConfigurator and OmxOutputSink are also done +// on this thread. +// +// Public methods can be called on any thread. +// // USAGES // // // Initialization. // MessageLoop message_loop; // OmxCodec* decoder = new OmxCodec(&message_loop); +// // OmxConfigurator::MediaFormat input_format, output_format; // input_format.codec = OmxCodec::kCodecH264; // output_format.codec = OmxCodec::kCodecRaw; -// decoder->Setup(new OmxDecoderConfigurator(input_format, output_format)); +// scoped_ptr<OmxConfigurator> configurator( +// new OmxDecoderConfigurator(input_format, output_format)); +// scoped_ptr<OmxOutputSink> output_sink(new CustomOutputSink()); +// +// decoder->Setup(configurator.get(), output_sink.get()); // decoder->SetErrorCallback(NewCallback(this, &Client::ErrorCallback)); // decoder->SetFormatCallback(NewCallback(this, &Client::FormatCallback)); // -// // Start is asynchronous. But we don't need to wait for it to proceed. +// // Start is asynchronous. We don't need to wait for it to proceed. // decoder->Start(); // // // We can start giving buffer to the decoder right after start. It will @@ -42,12 +79,26 @@ // A typical FeedCallback will look like: // void Client::FeedCallback(OmxInputBuffer* buffer) { // // We have read to the end so stop feeding. -// if (buffer->Eos()) +// if (buffer->IsEndOfStream()) // return; // PrepareInputBuffer(buffer); // decoder->Feed(buffer, NewCallback(this, &Client::FeedCallback)); // } // +// A typical ReadCallback will look like: +// void Client::ReadCallback(int buffer_id, +// OmxOutputSink::BufferUsedCallback* callback) { +// // Detect end-of-stream state. +// if (buffer_id == OmxCodec::kEosBuffer) +// return; +// +// // Issue a new read immediately. +// decoder->Read(NewCallback(this, &Client::ReadCallback)); +// +// // Pass the buffer to OmxOutputSink. +// output_sink->BufferReady(buffer_id, callback); +// } +// // EXTERNAL STATES // // Client of this class will only see four states from the decoder: @@ -91,6 +142,7 @@ #include "base/scoped_ptr.h" #include "base/task.h" #include "media/omx/omx_configurator.h" +#include "media/omx/omx_output_sink.h" #include "third_party/openmax/il/OMX_Component.h" #include "third_party/openmax/il/OMX_Core.h" #include "third_party/openmax/il/OMX_Video.h" @@ -108,7 +160,8 @@ class OmxCodec : public base::RefCountedThreadSafe<OmxCodec> { const OmxConfigurator::MediaFormat&, const OmxConfigurator::MediaFormat&>::Type FormatCallback; typedef Callback1<OmxInputBuffer*>::Type FeedCallback; - typedef Callback2<uint8*, int>::Type ReadCallback; + typedef Callback2<int, + OmxOutputSink::BufferUsedCallback*>::Type ReadCallback; typedef Callback0::Type Callback; // Initialize an OmxCodec object that runs on |message_loop|. It is @@ -116,9 +169,9 @@ class OmxCodec : public base::RefCountedThreadSafe<OmxCodec> { explicit OmxCodec(MessageLoop* message_loop); virtual ~OmxCodec(); - // Setup OmxCodec using |configurator|. Ownership of |configurator| - // is passed to this class. It is then used for configuration. - void Setup(OmxConfigurator* configurator); + // Setup OmxCodec using |configurator|. |configurator| and |output_sink| + // are not owned by this class and should be cleaned up externally. + void Setup(OmxConfigurator* configurator, OmxOutputSink* output_sink); // Set the error callback. In case of error the callback will be called. void SetErrorCallback(Callback* callback); @@ -136,6 +189,14 @@ class OmxCodec : public base::RefCountedThreadSafe<OmxCodec> { // Read decoded buffer from the decoder. When there is decoded data // ready to be consumed |callback| is called. + // The callback will be called with two parameters: + // 1. Buffer ID. + // To identify the buffer which contains the decoded frame. If + // the value is kEosBuffer then end-of-stream has been reached. + // 2. Buffer used callback + // When the buffer is used, client should call this callback + // with the given buffer id to return the buffer to OmxCodec. + // This callback can be made from any thread. void Read(ReadCallback* callback); // Feed the decoder with |buffer|. When the decoder has consumed the @@ -148,6 +209,8 @@ class OmxCodec : public base::RefCountedThreadSafe<OmxCodec> { // Subclass can provide a different value. virtual int current_omx_spec_version() const { return 0x00000101; } + static const int kEosBuffer = -1; + private: enum State { kEmpty, @@ -241,7 +304,15 @@ class OmxCodec : public base::RefCountedThreadSafe<OmxCodec> { // Methods to handle outgoing (decoded) buffers. void FillBufferCompleteTask(OMX_BUFFERHEADERTYPE* buffer); - void FillBufferTask(); + + // Take on decoded buffer to fulfill one read request. + void FulfillOneRead(); + + // Callback method to be called from a buffer output sink. + // BufferUsedTask() is the corresponding task that runs on + // |message_loop_|. + void BufferUsedCallback(int buffer_id); + void BufferUsedTask(int buffer_id); // Methods that do initial reads to kick start the decoding process. void InitialFillBuffer(); @@ -300,10 +371,9 @@ class OmxCodec : public base::RefCountedThreadSafe<OmxCodec> { State state_; State next_state_; - std::string role_name_; - std::string component_name_; OMX_COMPONENTTYPE* component_handle_; - scoped_ptr<OmxConfigurator> configurator_; + OmxConfigurator* configurator_; + OmxOutputSink* output_sink_; MessageLoop* message_loop_; scoped_ptr<FormatCallback> format_callback_; @@ -315,9 +385,19 @@ class OmxCodec : public base::RefCountedThreadSafe<OmxCodec> { std::queue<InputUnit> input_queue_; std::queue<ReadCallback*> output_queue_; - // Input and output buffers that we can use to feed the decoder. + // Available input OpenMAX buffers that we can use to issue + // OMX_EmptyThisBuffer() call. std::queue<OMX_BUFFERHEADERTYPE*> available_input_buffers_; - std::queue<OMX_BUFFERHEADERTYPE*> available_output_buffers_; + + // A queue of buffers that carries decoded video frames. They are + // ready to return to client. + // TOOD(hclam): extract it to a separate class. + std::queue<int> output_buffers_ready_; + + // A set of buffers that are currently in use by the client. + // TODO(hclam): extract it to a separate class. + typedef std::vector<int> OutputBuffersInUseSet; + OutputBuffersInUseSet output_buffers_in_use_; private: DISALLOW_COPY_AND_ASSIGN(OmxCodec); diff --git a/media/omx/omx_codec_unittest.cc b/media/omx/omx_codec_unittest.cc new file mode 100644 index 0000000..1d3df2c --- /dev/null +++ b/media/omx/omx_codec_unittest.cc @@ -0,0 +1,490 @@ +// Copyright (c) 2010 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. + +#define _CRT_SECURE_NO_WARNINGS + +#include <deque> + +#include "base/message_loop.h" +#include "media/base/mock_filters.h" +#include "media/omx/mock_omx.h" +#include "media/omx/omx_codec.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::SetArgumentPointee; +using ::testing::StrEq; +using ::testing::StrictMock; + +namespace { + +const int kBufferCount = 3; +const int kBufferSize = 4096; +const char* kRoleName = "awsome"; +const char* kComponentName = "OMX.google.mock.awsome"; + +} // namespace + +namespace media { + +ACTION(ReturnComponentName) { + strcpy(((char**)arg2)[0], kComponentName); +} + +ACTION(GetHandle) { + *arg0 = MockOmx::get()->component(); + MockOmx::get()->component()->pApplicationPrivate = arg2; + memcpy(MockOmx::get()->callbacks(), arg3, sizeof(OMX_CALLBACKTYPE)); +} + +ACTION(GetParameterVideoInit) { + ((OMX_PORT_PARAM_TYPE*)arg1)->nStartPortNumber = 0; +} + +ACTION(GetParameterPortDefinition) { + OMX_PARAM_PORTDEFINITIONTYPE* port_format = + (OMX_PARAM_PORTDEFINITIONTYPE*)arg1; + CHECK(port_format->nPortIndex == 0 || port_format->nPortIndex == 1); + if (port_format->nPortIndex == 0) + port_format->eDir = OMX_DirInput; + else + port_format->eDir = OMX_DirOutput; + port_format->nBufferCountMin = kBufferCount; + port_format->nBufferSize = kBufferSize; +} + +ACTION(AllocateBuffer) { + *arg0 = new OMX_BUFFERHEADERTYPE(); + memset(*arg0, 0, sizeof(OMX_BUFFERHEADERTYPE)); + (*arg0)->pBuffer = new uint8[kBufferSize]; +} + +ACTION(FreeBuffer) { + delete [] arg1->pBuffer; + delete arg1; +} + +ACTION_P2(SendEvent, event, data1) { + // TODO(hclam): pass data2 and event data. + (*MockOmx::get()->callbacks()->EventHandler)( + MockOmx::get()->component(), + MockOmx::get()->component()->pApplicationPrivate, + event, static_cast<OMX_U32>(data1), 0, NULL); +} + +ACTION(FillBuffer) { + arg0->nFlags = 0; + arg0->nFilledLen = kBufferSize; + (*MockOmx::get()->callbacks()->FillBufferDone)( + MockOmx::get()->component(), + MockOmx::get()->component()->pApplicationPrivate, + arg0); +} + +ACTION(FillEosBuffer) { + arg0->nFlags = OMX_BUFFERFLAG_EOS; + arg0->nFilledLen = 0; + (*MockOmx::get()->callbacks()->FillBufferDone)( + MockOmx::get()->component(), + MockOmx::get()->component()->pApplicationPrivate, + arg0); +} + +class MockOmxConfigurator : public OmxConfigurator { + public: + MockOmxConfigurator() + : OmxConfigurator(MediaFormat(), MediaFormat()) {} + + MOCK_CONST_METHOD0(GetRoleName, std::string()); + MOCK_CONST_METHOD3(ConfigureIOPorts, + bool(OMX_COMPONENTTYPE* component, + OMX_PARAM_PORTDEFINITIONTYPE* input_fef, + OMX_PARAM_PORTDEFINITIONTYPE* output_def)); +}; + +class MockOmxOutputSink : public OmxOutputSink { + public: + MOCK_CONST_METHOD0(ProvidesEGLImages, bool()); + MOCK_METHOD3(AllocateEGLImages, + bool(int width, int height, + std::vector<EGLImageKHR>* images)); + MOCK_METHOD1(ReleaseEGLImages, + void(const std::vector<EGLImageKHR>& images)); + MOCK_METHOD2(UseThisBuffer, void(int buffer_id, + OMX_BUFFERHEADERTYPE* buffer)); + MOCK_METHOD1(StopUsingThisBuffer, void(int buffer_id)); + MOCK_METHOD2(BufferReady, void(int buffer_id, + BufferUsedCallback* callback)); +}; + +class OmxCodecTest : public testing::Test { + public: + OmxCodecTest () + : omx_codec_(new OmxCodec(&message_loop_)) { + omx_codec_->Setup(&mock_configurator_, &mock_output_sink_); + } + + ~OmxCodecTest() { + CHECK(output_units_.size() == 0); + } + + protected: + void ExpectSettings() { + // Return the component name. + EXPECT_CALL(*MockOmx::get(), GetComponentsOfRole(_, _, IsNull())) + .WillOnce(DoAll(SetArgumentPointee<1>(1), + Return(OMX_ErrorNone))); + EXPECT_CALL(*MockOmx::get(), GetComponentsOfRole(_, _, NotNull())) + .WillOnce(DoAll(SetArgumentPointee<1>(1), + ReturnComponentName(), + Return(OMX_ErrorNone))); + + // Handle get parameter calls. + EXPECT_CALL(*MockOmx::get(), + GetParameter(OMX_IndexParamVideoInit, NotNull())) + .WillRepeatedly(DoAll(GetParameterVideoInit(), Return(OMX_ErrorNone))); + EXPECT_CALL(*MockOmx::get(), + GetParameter(OMX_IndexParamPortDefinition, NotNull())) + .WillRepeatedly(DoAll(GetParameterPortDefinition(), + Return(OMX_ErrorNone))); + + // Ignore all set parameter calls. + EXPECT_CALL(*MockOmx::get(), SetParameter(_, _)) + .WillRepeatedly(Return(OMX_ErrorNone)); + + // Expect calling to configurator once. + EXPECT_CALL(mock_configurator_, + ConfigureIOPorts(MockOmx::get()->component(), _, _)) + .WillOnce(Return(true)); + + EXPECT_CALL(mock_configurator_, GetRoleName()) + .WillRepeatedly(Return(kRoleName)); + } + + void ExpectToLoaded() { + InSequence s; + + // Expect initialization. + EXPECT_CALL(*MockOmx::get(), Init()) + .WillOnce(Return(OMX_ErrorNone)); + + // Return the handle. + EXPECT_CALL(*MockOmx::get(), + GetHandle(NotNull(), StrEq(kComponentName), + NotNull(), NotNull())) + .WillOnce(DoAll(GetHandle(), + Return(OMX_ErrorNone))); + } + + void ExpectLoadedToIdle() { + InSequence s; + + // Expect transition to idle. + EXPECT_CALL(*MockOmx::get(), + SendCommand(OMX_CommandStateSet, OMX_StateIdle, _)) + .WillOnce( + DoAll( + SendEvent(OMX_EventCmdComplete, OMX_CommandStateSet), + Return(OMX_ErrorNone))); + + // Expect allocation of buffers. + EXPECT_CALL(*MockOmx::get(), + AllocateBuffer(NotNull(), 0, IsNull(), kBufferSize)) + .Times(kBufferCount) + .WillRepeatedly(DoAll(AllocateBuffer(), Return(OMX_ErrorNone))); + + // Don't support EGL images in this case. + EXPECT_CALL(mock_output_sink_, ProvidesEGLImages()) + .WillOnce(Return(false)); + + // Expect allocation of output buffers and send command complete. + EXPECT_CALL(*MockOmx::get(), + AllocateBuffer(NotNull(), 1, IsNull(), kBufferSize)) + .Times(kBufferCount) + .WillRepeatedly(DoAll(AllocateBuffer(), Return(OMX_ErrorNone))); + + // The allocate output buffers will then be passed to output sink. + for (int i = 0; i < kBufferCount; ++i) { + EXPECT_CALL(mock_output_sink_, UseThisBuffer(i, _)); + } + } + + void ExpectToExecuting() { + InSequence s; + + // Expect transition to executing. + EXPECT_CALL(*MockOmx::get(), + SendCommand(OMX_CommandStateSet, OMX_StateExecuting, _)) + .WillOnce(DoAll( + SendEvent(OMX_EventCmdComplete, OMX_CommandStateSet), + Return(OMX_ErrorNone))); + + // Expect initial FillThisBuffer() calls. + EXPECT_CALL(*MockOmx::get(), FillThisBuffer(NotNull())) + .Times(kBufferCount) + .WillRepeatedly(DoAll(FillBuffer(), Return(OMX_ErrorNone))); + } + + void ExpectToIdle() { + // Expect going to idle + EXPECT_CALL(*MockOmx::get(), + SendCommand(OMX_CommandStateSet, OMX_StateIdle, _)) + .WillOnce(DoAll( + SendEvent(OMX_EventCmdComplete, OMX_CommandStateSet), + Return(OMX_ErrorNone))); + } + + void ExpectIdleToLoaded() { + InSequence s; + + // Expect transition to loaded. + EXPECT_CALL(*MockOmx::get(), + SendCommand(OMX_CommandStateSet, OMX_StateLoaded, _)) + .WillOnce(DoAll( + SendEvent(OMX_EventCmdComplete, OMX_CommandStateSet), + Return(OMX_ErrorNone))); + + // Expect free buffer for input port. + EXPECT_CALL(*MockOmx::get(), FreeBuffer(0, NotNull())) + .Times(kBufferCount) + .WillRepeatedly(DoAll(FreeBuffer(), Return(OMX_ErrorNone))); + + // Expect free output buffer. + for (int i = 0; i < kBufferCount; ++i) { + EXPECT_CALL(mock_output_sink_, StopUsingThisBuffer(i)); + } + + EXPECT_CALL(*MockOmx::get(), FreeBuffer(1, NotNull())) + .Times(kBufferCount) + .WillRepeatedly(DoAll(FreeBuffer(), Return(OMX_ErrorNone))); + + // Report that the sink don't provide EGL images. + EXPECT_CALL(mock_output_sink_, ProvidesEGLImages()) + .WillOnce(Return(false)); + } + + void ExpectToEmpty() { + InSequence s; + + EXPECT_CALL(*MockOmx::get(), FreeHandle(MockOmx::get()->component())) + .WillOnce(Return(OMX_ErrorNone)); + EXPECT_CALL(*MockOmx::get(), Deinit()) + .WillOnce(Return(OMX_ErrorNone)); + } + + // TODO(hclam): Make a more generic about when to stop. + void ExpectStart() { + ExpectToLoaded(); + ExpectLoadedToIdle(); + ExpectToExecuting(); + } + + void ExpectStop() { + ExpectToIdle(); + ExpectIdleToLoaded(); + ExpectToEmpty(); + } + + void ReadCallback(int buffer_id, + OmxOutputSink::BufferUsedCallback* callback) { + output_units_.push_back(std::make_pair(buffer_id, callback)); + } + + void MakeReadRequest() { + omx_codec_->Read(NewCallback(this, &OmxCodecTest::ReadCallback)); + } + + void SaveFillThisBuffer(OMX_BUFFERHEADERTYPE* buffer) { + fill_this_buffer_received_.push_back(buffer); + } + + void ExpectAndSaveFillThisBuffer() { + EXPECT_CALL(*MockOmx::get(), FillThisBuffer(NotNull())) + .WillOnce(DoAll(Invoke(this, &OmxCodecTest::SaveFillThisBuffer), + Return(OMX_ErrorNone))) + .RetiresOnSaturation(); + } + + typedef std::pair<int, OmxOutputSink::BufferUsedCallback*> OutputUnit; + std::deque<OutputUnit> output_units_; + std::deque<OMX_BUFFERHEADERTYPE*> fill_this_buffer_received_; + + MockOmx mock_omx_; + MockOmxConfigurator mock_configurator_; + MockOmxOutputSink mock_output_sink_; + + MessageLoop message_loop_; + scoped_refptr<OmxCodec> omx_codec_; + + MockFilterCallback stop_callback_; + + private: + DISALLOW_COPY_AND_ASSIGN(OmxCodecTest); +}; + +TEST_F(OmxCodecTest, SimpleStartAndStop) { + ExpectSettings(); + ExpectStart(); + omx_codec_->Start(); + message_loop_.RunAllPending(); + + EXPECT_CALL(stop_callback_, OnFilterCallback()); + EXPECT_CALL(stop_callback_, OnCallbackDestroyed()); + ExpectStop(); + omx_codec_->Stop(stop_callback_.NewCallback()); + message_loop_.RunAllPending(); +} + +TEST_F(OmxCodecTest, EndOfStream) { + ExpectSettings(); + ExpectStart(); + omx_codec_->Start(); + message_loop_.RunAllPending(); + + // Make read requests, OmxCodec should have gotten kBufferCount + // buffers already. + EXPECT_EQ(0u, output_units_.size()); + for (int i = 0; i < kBufferCount; ++i) { + MakeReadRequest(); + } + message_loop_.RunAllPending(); + CHECK(kBufferCount == static_cast<int>(output_units_.size())); + + // Make sure buffers received are in order. + for (int i = 0; i < kBufferCount; ++i) { + EXPECT_EQ(i, output_units_[i].first); + EXPECT_TRUE(output_units_[i].second != NULL); + } + + // Give buffers back to OmxCodec. OmxCodec will make a new + // FillThisBuffer() call for each read. + for (int i = kBufferCount - 1; i >= 0; --i) { + EXPECT_CALL(*MockOmx::get(), FillThisBuffer(NotNull())) + .WillOnce(DoAll(FillEosBuffer(), Return(OMX_ErrorNone))) + .RetiresOnSaturation(); + output_units_[i].second->Run(output_units_[i].first); + delete output_units_[i].second; + } + output_units_.clear(); + + // Make some read requests and make sure end-of-stream buffer id + // are received. + EXPECT_EQ(0u, output_units_.size()); + for (int i = 0; i < 2 * kBufferCount; ++i) { + MakeReadRequest(); + } + message_loop_.RunAllPending(); + EXPECT_EQ(2 * kBufferCount, static_cast<int>(output_units_.size())); + + for (size_t i = 0; i <output_units_.size(); ++i) { + EXPECT_EQ(OmxCodec::kEosBuffer, output_units_[i].first); + EXPECT_EQ(NULL, output_units_[i].second); + } + output_units_.clear(); + + // Stop OmxCodec. + EXPECT_CALL(stop_callback_, OnFilterCallback()); + EXPECT_CALL(stop_callback_, OnCallbackDestroyed()); + ExpectStop(); + omx_codec_->Stop(stop_callback_.NewCallback()); + message_loop_.RunAllPending(); +} + +TEST_F(OmxCodecTest, OutputFlowControl) { + ExpectSettings(); + ExpectStart(); + omx_codec_->Start(); + message_loop_.RunAllPending(); + + // Since initial FillThisBuffer() calls are made and fulfilled during + // start. Reads issued to OmxCodec will be fulfilled now. + EXPECT_EQ(0u, output_units_.size()); + for (int i = 0; i < kBufferCount; ++i) { + MakeReadRequest(); + } + message_loop_.RunAllPending(); + CHECK(kBufferCount == static_cast<int>(output_units_.size())); + + // Make sure buffers received are in order. + for (int i = 0; i < kBufferCount; ++i) { + EXPECT_EQ(i, output_units_[i].first); + EXPECT_TRUE(output_units_[i].second != NULL); + } + + // Give the buffer back in reverse order. + for (int i = kBufferCount - 1; i >= 0; --i) { + ExpectAndSaveFillThisBuffer(); + output_units_[i].second->Run(output_units_[i].first); + delete output_units_[i].second; + } + output_units_.clear(); + + // In each loop, perform the following actions: + // 1. Make a read request to OmxCodec. + // 2. Fake a response for FillBufferDone(). + // 3. Expect read response received. + // 4. Give the buffer read back to OmxCodec. + // 5. Expect a FillThisBuffer() is called to OpenMAX. + for (int i = 0; i < kBufferCount; ++i) { + // 1. First make a read request. + MakeReadRequest(); + + // 2. Then fake a response from OpenMAX. + OMX_BUFFERHEADERTYPE* buffer = fill_this_buffer_received_.front(); + fill_this_buffer_received_.pop_front(); + buffer->nFlags = 0; + buffer->nFilledLen = kBufferSize; + (*MockOmx::get()->callbacks()->FillBufferDone)( + MockOmx::get()->component(), + MockOmx::get()->component()->pApplicationPrivate, + buffer); + + // Make sure actions are completed. + message_loop_.RunAllPending(); + + // 3. Expect read response received. + // The above action will cause a read callback be called and we should + // receive one buffer now. Also expect the buffer id be received in + // reverse order. + EXPECT_EQ(1u, output_units_.size()); + EXPECT_EQ(kBufferCount - i - 1, output_units_.front().first); + + // 4. Since a buffer is given back to OmxCodec. A FillThisBuffer() is called + // to OmxCodec. + EXPECT_CALL(*MockOmx::get(), FillThisBuffer(NotNull())) + .WillOnce(Return(OMX_ErrorNone)) + .RetiresOnSaturation(); + + // 5. Give this buffer back to OmxCodec. + output_units_.front().second->Run(output_units_.front().first); + delete output_units_.front().second; + output_units_.pop_front(); + + // Make sure actions are completed. + message_loop_.RunAllPending(); + } + + // Now issue kBufferCount reads to OmxCodec. + EXPECT_EQ(0u, output_units_.size()); + + // Stop OmxCodec. + EXPECT_CALL(stop_callback_, OnFilterCallback()); + EXPECT_CALL(stop_callback_, OnCallbackDestroyed()); + ExpectStop(); + omx_codec_->Stop(stop_callback_.NewCallback()); + message_loop_.RunAllPending(); +} + +// TODO(hclam): Add test case for dynamic port config. +// TODO(hclam): Create a more complicated test case so that read +// requests and reply from FillThisBuffer() arrives out of order. +// TODO(hclam): Add test case for Feed(). + +} // namespace media diff --git a/media/omx/omx_output_sink.h b/media/omx/omx_output_sink.h new file mode 100644 index 0000000..504e4b0 --- /dev/null +++ b/media/omx/omx_output_sink.h @@ -0,0 +1,144 @@ +// Copyright (c) 2010 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. +// +// An abstract class to define the behavior of an output buffer sink for +// media::OmxCodec. It is responsible for negotiation of buffer allocation +// for output of media::OmxCodec. It is also responsible for processing buffer +// output from OpenMAX decoder. It is important to note that implementor must +// assure usage of this class is safe on the thread where media::OmxCodec +// lives. Ownership of this object is described in src/media/omx/omx_codec.h. +// +// BUFFER NEGOTIATION +// +// One of the key function of OmxOutputSink is to negotiate output buffer +// allocation with OmxCodec. There are two modes of operation: +// +// 1. Buffer is provided by OmxCodec. +// +// In this case ProvidesEGLImage() will return false and OmxCodec allocates +// output buffers during initialization. Here's a sample sequence of actions: +// +// - OmxCodec allocated buffers during initialization. +// - OmxOutputSink::UseThisBuffer(id, buffer) is called to assign an output +// buffer header to the sink component. +// - OmxOutputSink maps the output buffer as a texture for rendering. +// - OmxCodec received dynamic port reconfiguration from the hardware. +// - OmxOutputSink::StopUsingThisBuffer(id) is called to release the buffer. +// - OmxOutputSink unmaps the output buffer as texture and make sure there +// is not reference to it. +// - OmxOutputSink::UseThisBuffer(id, buffer) is called to assign a new +// output buffer to the sink component. +// - ... +// +// 1. Buffer is provided by OmxCodec as EGL image. +// +// - OmxOutputSink::AllocateEGLImages(...) is called to request an EGL image +// from the sink component, an associated buffer header is created. +// - OmxOutputSink::UseThisBuffer(id, buffer) is called to assign an output +// buffer header to the sink component. +// - OmxOutputSink maps the output buffer as a texture for rendering. +// - OmxCodec received dynamic port reconfiguration from the hardware. +// - OmxOutputSink::StopUsingThisBuffer(id) is called to release the buffer. +// - OmxOutputSink unmaps the output buffer as texture and make sure there +// is not reference to it. +// - OmxOutputSink::ReleaseEGLImages() is called to tell the sink component +// to release all allocated EGL images. +// - OmxOutputSink::AllocateEGLImages(...) is called to allocate EGL images. +// - ... +// +// BUFFER READY SIGNALING +// +// Another key part of OmxOutputSink is to process the output buffer given +// by OmxCodec. This is done by signaling the sink component that a +// particular buffer is ready. +// +// This is done through the following sequence: +// +// - Owner of this object calls BufferReady(buffer_id, callback). +// - OmxOutputSink uses the buffer for rendering or other operations +// asynchronously. +// - Callback provided by BufferReady() is called along with a buffer id to +// notify the buffer has been consumed. +// +// It is up to implementor to decide which thread this callback is executed. +// +// THREADING +// +// OmxOutputSink::BufferReady(buffer_id) is called from the owner of this +// object which can be made from any thread. All other methods are made on +// the thread where OmxCodec lives. +// +// When the sink component is notified of buffer ready and the buffer is +// used BufferUsedCallback is called. There is no gurantee which thread +// this call is made. +// +// OWNERSHIP +// +// Described in src/media/omx/omx_codec.h. + +#ifndef MEDIA_OMX_OMX_OUTPUT_SINK_ +#define MEDIA_OMX_OMX_OUTPUT_SINK_ + +#include <vector> + +#include "base/task.h" +#include "third_party/openmax/il/OMX_Core.h" + +// TODO(hclam): This is just to get the build going. Remove this when we +// include the GLES header. +typedef void* EGLImageKHR; + +namespace media { + +class OmxOutputSink { + public: + typedef Callback1<int>::Type BufferUsedCallback; + virtual ~OmxOutputSink() {} + + // Returns true if this sink component provides EGL images as output + // buffers. + virtual bool ProvidesEGLImages() const = 0; + + // Returns a list of EGL images allocated by the sink component. The + // EGL images will then be given to the hardware decoder for port + // configuration. The amount of EGL images created is controlled by the + // sink component. The EGL image allocated is owned by the sink + // component. + // Returns true if allocate was successful. + // TODO(hclam): Improve this API once we know what to do with it. + virtual bool AllocateEGLImages(int width, int height, + std::vector<EGLImageKHR>* images) = 0; + + // Notify the sink component that OmxCodec has done with the EGL + // image allocated by AllocateEGLImages(). They can be released by + // the sink component any time. + virtual void ReleaseEGLImages( + const std::vector<EGLImageKHR>& images) = 0; + + // Assign a buffer header to the sink component for output. The sink + // component will read from the assicated buffer for the decoded frame. + // There is also an associated buffer id to identify the buffer. This id + // is used in subsequent steps for identifying the right buffer. + // Note that the sink component doesn't own the buffer header. + // Note that this method is used to assign buffer only at the + // initialization stage and is not used for data delivery. + virtual void UseThisBuffer(int buffer_id, + OMX_BUFFERHEADERTYPE* buffer) = 0; + + // Tell the sink component to stop using the buffer identified by the + // buffer id. + // TODO(hclam): Should accept a callback so notify the operation has + // completed. + virtual void StopUsingThisBuffer(int id) = 0; + + // Notify the sink component that the buffer identified by buffer id + // is ready to be consumed. When the buffer is used, |callback| is + // called. Ths callback can be made on any thread. + virtual void BufferReady(int buffer_id, + BufferUsedCallback* callback) = 0; +}; + +} + +#endif // MEDIA_OMX_OMX_OUTPUT_SINK_ diff --git a/media/tools/omx_test/file_writer_util.cc b/media/tools/omx_test/file_sink.cc index 0b96896..abee305 100644 --- a/media/tools/omx_test/file_writer_util.cc +++ b/media/tools/omx_test/file_sink.cc @@ -2,7 +2,7 @@ // source code is governed by a BSD-style license that can be found in the // LICENSE file. -#include "media/tools/omx_test/file_writer_util.h" +#include "media/tools/omx_test/file_sink.h" #include "base/file_util.h" #include "base/logging.h" @@ -10,24 +10,36 @@ namespace media { -bool FileWriter::Initialize() { - // Opens the output file for writing. - if (!output_filename_.empty()) { - output_file_.Set(file_util::OpenFile(output_filename_, "wb")); - if (!output_file_.get()) { - LOG(ERROR) << "can't open dump file %s" << output_filename_; - return false; - } - } - return true; +bool FileSink::AllocateEGLImages(int width, int height, + std::vector<EGLImageKHR>* images) { + NOTREACHED() << "This method is never used"; + return false; } -void FileWriter::UpdateSize(int width, int height) { - width_ = width; - height_ = height; +void FileSink::ReleaseEGLImages(const std::vector<EGLImageKHR>& images) { + NOTREACHED() << "This method is never used"; +} + +void FileSink::UseThisBuffer(int buffer_id, OMX_BUFFERHEADERTYPE* buffer) { + CHECK(omx_buffers_.find(buffer_id) == omx_buffers_.end()); + omx_buffers_[buffer_id] = buffer; } -void FileWriter::Write(uint8* buffer, int size) { +void FileSink::StopUsingThisBuffer(int id) { + omx_buffers_.erase(id); +} + +void FileSink::BufferReady(int buffer_id, BufferUsedCallback* callback) { + CHECK(omx_buffers_.find(buffer_id) != omx_buffers_.end()); + CHECK(callback); + + OMX_BUFFERHEADERTYPE* omx_buffer = omx_buffers_[buffer_id]; + uint8* buffer = omx_buffer->pBuffer; + int size = omx_buffer->nFilledLen; + + // We never receive an end-of-stream buffer here. + CHECK(!(omx_buffer->nFlags & OMX_BUFFERFLAG_EOS)); + if (size > copy_buf_size_) { copy_buf_.reset(new uint8[size]); copy_buf_size_ = size; @@ -50,6 +62,27 @@ void FileWriter::Write(uint8* buffer, int size) { if (output_file_.get()) fwrite(out_buffer, sizeof(uint8), size, output_file_.get()); + + // Always make the callback. + callback->Run(buffer_id); + delete callback; +} + +bool FileSink::Initialize() { + // Opens the output file for writing. + if (!output_filename_.empty()) { + output_file_.Set(file_util::OpenFile(output_filename_, "wb")); + if (!output_file_.get()) { + LOG(ERROR) << "can't open dump file %s" << output_filename_; + return false; + } + } + return true; +} + +void FileSink::UpdateSize(int width, int height) { + width_ = width; + height_ = height; } } // namespace media diff --git a/media/tools/omx_test/file_writer_util.h b/media/tools/omx_test/file_sink.h index f1427e9..58edb04 100644 --- a/media/tools/omx_test/file_writer_util.h +++ b/media/tools/omx_test/file_sink.h @@ -2,24 +2,26 @@ // source code is governed by a BSD-style license that can be found in the // LICENSE file. -#ifndef MEDIA_TOOLS_OMX_TEST_FILE_WRITER_UTIL_H_ -#define MEDIA_TOOLS_OMX_TEST_FILE_WRITER_UTIL_H_ +#ifndef MEDIA_TOOLS_OMX_TEST_FILE_SINK_H_ +#define MEDIA_TOOLS_OMX_TEST_FILE_SINK_H_ +#include <map> #include <string> #include "base/basictypes.h" #include "base/scoped_handle.h" #include "base/scoped_ptr.h" +#include "media/omx/omx_output_sink.h" namespace media { // This class writes output of a frame decoded by OmxCodec and save it to // a file. -class FileWriter { +class FileSink : public OmxOutputSink { public: - FileWriter(std::string output_filename, - bool simulate_copy, - bool enable_csc) + FileSink(std::string output_filename, + bool simulate_copy, + bool enable_csc) : output_filename_(output_filename), simulate_copy_(simulate_copy), enable_csc_(enable_csc), @@ -29,6 +31,15 @@ class FileWriter { csc_buf_size_(0) { } + // OmxOutputSink implementations. + virtual bool ProvidesEGLImages() const { return false; } + virtual bool AllocateEGLImages(int width, int height, + std::vector<EGLImageKHR>* images); + virtual void ReleaseEGLImages(const std::vector<EGLImageKHR>& images); + virtual void UseThisBuffer(int buffer_id, OMX_BUFFERHEADERTYPE* buffer); + virtual void StopUsingThisBuffer(int id); + virtual void BufferReady(int buffer_id, BufferUsedCallback* callback); + // Initialize this object. Returns true if successful. bool Initialize(); @@ -43,16 +54,22 @@ class FileWriter { bool simulate_copy_; bool enable_csc_; ScopedStdioHandle output_file_; + + // Image properties. int width_; int height_; + + // Buffers for copying and color space conversion. scoped_array<uint8> copy_buf_; int copy_buf_size_; scoped_array<uint8> csc_buf_; int csc_buf_size_; - DISALLOW_COPY_AND_ASSIGN(FileWriter); + std::map<int, OMX_BUFFERHEADERTYPE*> omx_buffers_; + + DISALLOW_COPY_AND_ASSIGN(FileSink); }; } // namespace media -#endif // MEDIA_TOOLS_OMX_TEST_FILE_WRITER_UTIL_H_ +#endif // MEDIA_TOOLS_OMX_TEST_FILE_SINK_H_ diff --git a/media/tools/omx_test/omx_test.cc b/media/tools/omx_test/omx_test.cc index d7dc672..3085181 100644 --- a/media/tools/omx_test/omx_test.cc +++ b/media/tools/omx_test/omx_test.cc @@ -19,20 +19,22 @@ #include "media/filters/bitstream_converter.h" #include "media/omx/omx_codec.h" #include "media/omx/omx_input_buffer.h" +#include "media/omx/omx_output_sink.h" #include "media/tools/omx_test/color_space_util.h" #include "media/tools/omx_test/file_reader_util.h" -#include "media/tools/omx_test/file_writer_util.h" +#include "media/tools/omx_test/file_sink.h" using media::BlockFileReader; using media::FFmpegFileReader; using media::FileReader; -using media::FileWriter; +using media::FileSink; using media::H264FileReader; using media::OmxCodec; using media::OmxConfigurator; using media::OmxDecoderConfigurator; using media::OmxEncoderConfigurator; using media::OmxInputBuffer; +using media::OmxOutputSink; using media::YuvFileReader; // This is the driver object to feed the decoder with data from a file. @@ -40,11 +42,11 @@ using media::YuvFileReader; // decoder. class TestApp { public: - TestApp(OmxConfigurator* configurator, FileReader* file_reader, - FileWriter* file_writer) + TestApp(OmxConfigurator* configurator, FileSink* file_sink, + FileReader* file_reader) : configurator_(configurator), file_reader_(file_reader), - file_writer_(file_writer), + file_sink_(file_sink), stopped_(false), error_(false) { } @@ -56,7 +58,7 @@ class TestApp { return false;; } - if (!file_writer_->Initialize()) { + if (!file_sink_->Initialize()) { LOG(ERROR) << "can't initialize output writer"; return false; } @@ -92,7 +94,7 @@ class TestApp { DCHECK_EQ(input_format.video_header.height, output_format.video_header.height); - file_writer_->UpdateSize(input_format.video_header.width, + file_sink_->UpdateSize(input_format.video_header.width, input_format.video_header.height); } @@ -107,7 +109,8 @@ class TestApp { FeedInputBuffer(); } - void ReadCompleteCallback(uint8* buffer, int size) { + void ReadCompleteCallback(int buffer, + FileSink::BufferUsedCallback* callback) { // This callback is received when the decoder has completed a decoding // task and given us some output data. The buffer is owned by the decoder. if (stopped_ || error_) @@ -117,7 +120,7 @@ class TestApp { first_sample_delivered_time_ = base::TimeTicks::HighResNow(); // If we are readding to the end, then stop. - if (!size) { + if (buffer == OmxCodec::kEosBuffer) { codec_->Stop(NewCallback(this, &TestApp::StopCallback)); return; } @@ -125,12 +128,11 @@ class TestApp { // Read one more from the decoder. codec_->Read(NewCallback(this, &TestApp::ReadCompleteCallback)); - if (file_writer_.get()) - file_writer_->Write(buffer, size); + if (file_sink_.get()) + file_sink_->BufferReady(buffer, callback); // could OMX IL return patial sample for decoder? frame_count_++; - bit_count_ += size << 3; } void FeedInputBuffer() { @@ -147,7 +149,7 @@ class TestApp { // Setup the |codec_| with the message loop of the current thread. Also // setup component name, codec format and callbacks. codec_ = new OmxCodec(&message_loop_); - codec_->Setup(configurator_.release()); + codec_->Setup(configurator_.get(), file_sink_.get()); codec_->SetErrorCallback(NewCallback(this, &TestApp::ErrorCallback)); codec_->SetFormatCallback(NewCallback(this, &TestApp::FormatCallback)); @@ -167,7 +169,6 @@ class TestApp { void StartProfiler() { start_time_ = base::TimeTicks::HighResNow(); frame_count_ = 0; - bit_count_ = 0; } void StopProfiler() { @@ -184,7 +185,6 @@ class TestApp { printf("\n<<< fps : %d >>>", static_cast<int>(fps)); printf("\n<<< initial delay used(us): %d >>>", static_cast<int>(delay.InMicroseconds())); - // printf("\n<<< bitrate>>> : %I64d\n", bit_count_ * 1000000 / micro_sec); printf("\n"); } @@ -192,7 +192,7 @@ class TestApp { MessageLoop message_loop_; scoped_ptr<OmxConfigurator> configurator_; scoped_ptr<FileReader> file_reader_; - scoped_ptr<FileWriter> file_writer_; + scoped_ptr<FileSink> file_sink_; // Internal states for execution. bool stopped_; @@ -202,7 +202,6 @@ class TestApp { base::TimeTicks start_time_; base::TimeTicks first_sample_delivered_time_; int frame_count_; - int bit_count_; }; static std::string GetStringSwitch(const char* name) { @@ -360,12 +359,11 @@ int main(int argc, char** argv) { else configurator = new OmxDecoderConfigurator(input, output); - // Create a file writer. - FileWriter* file_writer = - new FileWriter(output_filename, copy, enable_csc); + // Create a file sink. + FileSink* file_sink = new FileSink(output_filename, copy, enable_csc); // Create a test app object and initialize it. - TestApp test(configurator, file_reader, file_writer); + TestApp test(configurator, file_sink, file_reader); if (!test.Initialize()) { LOG(ERROR) << "can't initialize this application"; return -1; |