summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
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;