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/omx | |
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/omx')
-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 |
6 files changed, 1160 insertions, 95 deletions
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_ |