summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-17 19:03:59 +0000
committerhclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-17 19:03:59 +0000
commit19ffd67a4c32136d1d2abc07c97dcbb7ec5af216 (patch)
tree15ed2566ef839af4e218fc1d96880a9d1dc5ac44 /media
parent483a075709bb435ddf380aac050dc3b7a586d244 (diff)
downloadchromium_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.cc58
-rw-r--r--media/filters/omx_video_decode_engine.h30
-rw-r--r--media/media.gyp11
-rw-r--r--media/omx/mock_omx.cc139
-rw-r--r--media/omx/mock_omx.h94
-rw-r--r--media/omx/omx_codec.cc282
-rw-r--r--media/omx/omx_codec.h106
-rw-r--r--media/omx/omx_codec_unittest.cc490
-rw-r--r--media/omx/omx_output_sink.h144
-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.cc40
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;