summaryrefslogtreecommitdiffstats
path: root/media/omx/omx_codec.cc
diff options
context:
space:
mode:
Diffstat (limited to 'media/omx/omx_codec.cc')
-rw-r--r--media/omx/omx_codec.cc1103
1 files changed, 1103 insertions, 0 deletions
diff --git a/media/omx/omx_codec.cc b/media/omx/omx_codec.cc
new file mode 100644
index 0000000..6c42b3c
--- /dev/null
+++ b/media/omx/omx_codec.cc
@@ -0,0 +1,1103 @@
+// Copyright (c) 2009 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 "base/logging.h"
+#include "base/message_loop.h"
+#include "base/stl_util-inl.h"
+#include "media/omx/input_buffer.h"
+#include "media/omx/omx_codec.h"
+
+namespace media {
+
+template <typename T>
+static void ResetPortHeader(const OmxCodec& dec, T* param) {
+ memset(param, 0, sizeof(T));
+ param->nVersion.nVersion = dec.current_omx_spec_version();
+ param->nSize = sizeof(T);
+}
+
+OmxCodec::OmxCodec(MessageLoop* message_loop)
+ : input_buffer_count_(0),
+ input_buffer_size_(0),
+ input_port_(0),
+ input_eos_(false),
+ output_buffer_count_(0),
+ output_buffer_size_(0),
+ output_port_(0),
+ output_eos_(false),
+ decoder_handle_(NULL),
+ state_(kEmpty),
+ next_state_(kEmpty),
+ codec_(kCodecNone),
+ message_loop_(message_loop) {
+}
+
+OmxCodec::~OmxCodec() {
+ DCHECK(state_ == kError || state_ == kEmpty);
+ DCHECK_EQ(0u, input_buffers_.size());
+ DCHECK_EQ(0u, output_buffers_.size());
+ DCHECK(available_input_buffers_.empty());
+ DCHECK(available_output_buffers_.empty());
+ DCHECK(input_queue_.empty());
+ DCHECK(output_queue_.empty());
+}
+
+void OmxCodec::Setup(const char* component, Codec codec) {
+ DCHECK_EQ(kEmpty, state_);
+ component_ = component;
+ codec_ = codec;
+}
+
+void OmxCodec::SetErrorCallback(Callback* callback) {
+ DCHECK_EQ(kEmpty, state_);
+ error_callback_.reset(callback);
+}
+
+void OmxCodec::Start() {
+ DCHECK_NE(kCodecNone, codec_);
+
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &OmxCodec::StartTask));
+}
+
+void OmxCodec::Stop(Callback* callback) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &OmxCodec::StopTask, callback));
+}
+
+void OmxCodec::Read(ReadCallback* callback) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &OmxCodec::ReadTask, callback));
+}
+
+void OmxCodec::Feed(InputBuffer* buffer, FeedCallback* callback) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &OmxCodec::FeedTask, buffer, callback));
+}
+
+void OmxCodec::Flush(Callback* callback) {
+ // TODO(hclam): implement.
+}
+
+OmxCodec::State OmxCodec::GetState() const {
+ return state_;
+}
+
+void OmxCodec::SetState(State state) {
+ state_ = state;
+}
+
+OmxCodec::State OmxCodec::GetNextState() const {
+ return next_state_;
+}
+
+void OmxCodec::SetNextState(State state) {
+ next_state_ = state;
+}
+
+void OmxCodec::StartTask() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ StateTransitionTask(kLoaded);
+}
+
+void OmxCodec::StopTask(Callback* callback) {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ stop_callback_.reset(callback);
+
+ if (GetState() == kError) {
+ DoneStop();
+ return;
+ }
+
+ FreeInputQueue();
+ FreeOutputQueue();
+
+ if (GetState() == kExecuting)
+ StateTransitionTask(kIdle);
+ // TODO(hclam): The following two transitions may not be correct.
+ else if (GetState() == kPortSettingDisable)
+ StateTransitionTask(kIdle);
+ else if (GetState() == kPortSettingEnable)
+ StateTransitionTask(kIdle);
+ else if (GetState() == kIdle)
+ StateTransitionTask(kLoaded);
+ else if (GetState() == kLoaded)
+ StateTransitionTask(kEmpty);
+}
+
+void OmxCodec::ReadTask(ReadCallback* callback) {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ // Don't accept read request on error state.
+ if (!CanAcceptOutput()) {
+ callback->RunWithParams(MakeTuple(static_cast<uint8*>(NULL), 0));
+ delete callback;
+ return;
+ }
+
+ // Queue this request.
+ output_queue_.push(callback);
+
+ // Make our best effort to serve the request and read
+ // from the decoder.
+ FillBufferTask();
+}
+
+void OmxCodec::FeedTask(InputBuffer* buffer, FeedCallback* callback) {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (!CanAcceptInput()) {
+ callback->Run(buffer);
+ delete callback;
+ return;
+ }
+
+ // Queue this input buffer.
+ input_queue_.push(std::make_pair(buffer, callback));
+
+ // Try to feed buffers into the decoder.
+ EmptyBufferTask();
+}
+
+// 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.
+bool OmxCodec::AllocateInputBuffers() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ for(int i = 0; i < input_buffer_count_; ++i) {
+ OMX_BUFFERHEADERTYPE* buffer;
+ OMX_ERRORTYPE error =
+ OMX_AllocateBuffer(decoder_handle_, &buffer, input_port_,
+ NULL, input_buffer_size_);
+ if (error != OMX_ErrorNone)
+ return false;
+ input_buffers_.push_back(buffer);
+ available_input_buffers_.push(buffer);
+ }
+ 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.
+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(decoder_handle_, &buffer, output_port_,
+ NULL, output_buffer_size_);
+ if (error != OMX_ErrorNone)
+ return false;
+ output_buffers_.push_back(buffer);
+ }
+ return true;
+}
+
+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)
+ OMX_FreeBuffer(decoder_handle_, input_port_, input_buffers_[i]);
+ input_buffers_.clear();
+
+ // Empty available buffer queue.
+ while (!available_input_buffers_.empty()) {
+ available_input_buffers_.pop();
+ }
+}
+
+void OmxCodec::FreeOutputBuffers() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ // Calls to OMX to free buffers.
+ for(size_t i = 0; i < output_buffers_.size(); ++i)
+ OMX_FreeBuffer(decoder_handle_, output_port_, output_buffers_[i]);
+ output_buffers_.clear();
+
+ // Empty available buffer queue.
+ while (!available_output_buffers_.empty()) {
+ available_output_buffers_.pop();
+ }
+}
+
+void OmxCodec::FreeInputQueue() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ while (!input_queue_.empty()) {
+ InputBuffer* buffer = input_queue_.front().first;
+ FeedCallback* callback = input_queue_.front().second;
+ callback->Run(buffer);
+ delete callback;
+ input_queue_.pop();
+ }
+}
+
+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;
+ output_queue_.pop();
+ }
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Initialize OMX (To be removed.)
+// 2. Get handle of the OMX component
+// 3. Get parameters about I/O ports.
+// 4. Device specific configurations.
+// 5. General configuration of input port.
+// 6. General configuration of output port.
+// 7. Get Parameters about input port.
+// 8. Get Parameters about output port.
+// 9. Codec specific configurations.
+void OmxCodec::Transition_EmptyToLoaded() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kEmpty, GetState());
+
+ OMX_CALLBACKTYPE callback = { &EventHandler,
+ &EmptyBufferCallback,
+ &FillBufferCallback };
+
+ // 1. Initialize the OpenMAX Core.
+ // TODO(hclam): move this out.
+ OMX_ERRORTYPE omxresult = OMX_Init();
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - Failed to Init OpenMAX core";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // 2. Get the handle to the component. After OMX_GetHandle(),
+ // the component is in loaded state.
+ // TODO(hclam): We should have a list of componant names instead.
+ OMX_STRING component = const_cast<OMX_STRING>(component_);
+ OMX_HANDLETYPE handle = reinterpret_cast<OMX_HANDLETYPE>(decoder_handle_);
+ omxresult = OMX_GetHandle(&handle, component, this, &callback);
+ decoder_handle_ = reinterpret_cast<OMX_COMPONENTTYPE*>(handle);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Failed to Load the component: " << component;
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // 3. Get the port information. This will obtain information about the
+ // number of ports and index of the first port.
+ OMX_PORT_PARAM_TYPE port_param;
+ ResetPortHeader(*this, &port_param);
+ omxresult = OMX_GetParameter(decoder_handle_, OMX_IndexParamVideoInit,
+ &port_param);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "ERROR - Failed to get Port Param";
+ StateTransitionTask(kError);
+ return;
+ }
+ input_port_ = port_param.nStartPortNumber;
+ output_port_ = input_port_ + 1;
+
+ // 4. Device specific configurations.
+ if (!DeviceSpecificConfig()) {
+ LOG(ERROR) << "Error - device specific configurations failed";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // 5. Configure the input port.
+ // Query the decoder input port's minimum buffer requirements.
+ // Note that port_param.nStartPortNumber defines the index of the
+ // input port.
+ OMX_PARAM_PORTDEFINITIONTYPE port_format;
+ ResetPortHeader(*this, &port_format);
+ port_format.nPortIndex = input_port_;
+ omxresult = OMX_GetParameter(decoder_handle_,
+ OMX_IndexParamPortDefinition,
+ &port_format);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - GetParameter failed";
+ StateTransitionTask(kError);
+ return;
+ }
+ if(OMX_DirInput != port_format.eDir) {
+ LOG(ERROR) << "Error - Expect Input Port";
+ StateTransitionTask(kError);
+ return;
+ }
+ if (codec_ == kCodecH264)
+ port_format.format.video.eCompressionFormat = OMX_VIDEO_CodingAVC;
+ else if (codec_ == kCodecMpeg4)
+ port_format.format.video.eCompressionFormat = OMX_VIDEO_CodingMPEG4;
+ else if (codec_ == kCodecH263)
+ port_format.format.video.eCompressionFormat = OMX_VIDEO_CodingH263;
+ else if (codec_ == kCodecVc1)
+ port_format.format.video.eCompressionFormat = OMX_VIDEO_CodingWMV;
+ else
+ LOG(ERROR) << "Error: Unsupported codec " << codec_;
+ // Assume QCIF.
+ port_format.format.video.nFrameWidth = 176;
+ port_format.format.video.nFrameHeight = 144;
+ omxresult = OMX_SetParameter(decoder_handle_,
+ OMX_IndexParamPortDefinition,
+ &port_format);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SetParameter failed";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // 6. Configure the output port.
+ // There's nothing to be done here.
+
+ // 7. Obtain the information about the input port.
+ // This will have the new mini buffer count in port_format.nBufferCountMin.
+ // Save this value to input_buf_count.
+ port_format.nPortIndex = port_param.nStartPortNumber;
+ omxresult = OMX_GetParameter(decoder_handle_,
+ OMX_IndexParamPortDefinition,
+ &port_format);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - GetParameter failed";
+ StateTransitionTask(kError);
+ return;
+ }
+ if (OMX_DirInput != port_format.eDir) {
+ LOG(ERROR) << "Error - Expect input port";
+ StateTransitionTask(kError);
+ return;
+ }
+ input_buffer_count_ = port_format.nBufferCountMin;
+ input_buffer_size_ = port_format.nBufferSize;
+
+ // 8. Obtain the information about the output port.
+ ResetPortHeader(*this, &port_format);
+ port_format.nPortIndex = output_port_;
+ omxresult = OMX_GetParameter(decoder_handle_,
+ OMX_IndexParamPortDefinition,
+ &port_format);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - GetParameter failed";
+ StateTransitionTask(kError);
+ return;
+ }
+ if (OMX_DirOutput != port_format.eDir) {
+ LOG(ERROR) << "Error - Expect Output Port";
+ StateTransitionTask(kError);
+ return;
+ }
+ output_buffer_count_ = port_format.nBufferCountMin;
+ output_buffer_size_ = port_format.nBufferSize;
+
+ // 9. Codec specific configurations.
+ // This sets the NAL length size. 0 means we are using a 3 byte start code.
+ // Other values specifies number of bytes of the NAL length.
+ if (codec_ == kCodecH264) {
+ OMX_VIDEO_CONFIG_NALSIZE naluSize;
+ naluSize.nNaluBytes = 0;
+ omxresult = OMX_SetConfig(decoder_handle_,
+ OMX_IndexConfigVideoNalSize, (OMX_PTR)&naluSize);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SetConfig failed";
+ StateTransitionTask(kError);
+ return;
+ }
+ }
+
+ // After we have done all the configurations, we are considered loaded.
+ DoneStateTransitionTask();
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Send command to Idle state.
+// 2. Allocate buffers for input port.
+// 3. Allocate buffers for output port.
+void OmxCodec::Transition_LoadedToIdle() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kLoaded, GetState());
+
+ // 1. Sets decoder to idle state.
+ OMX_ERRORTYPE omxresult = OMX_SendCommand(decoder_handle_,
+ OMX_CommandStateSet,
+ OMX_StateIdle, 0);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SendCommand failed";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // 2. Allocate buffer for the input port.
+ if (!AllocateInputBuffers()) {
+ LOG(ERROR) << "Error - OMX_AllocateBuffer Input buffer error";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // 3. Allocate buffer for the output port.
+ if (!AllocateOutputBuffers()) {
+ LOG(ERROR) << "Error - OMX_AllocateBuffer Output buffer error";
+ StateTransitionTask(kError);
+ return;
+ }
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Send command to Executing state.
+void OmxCodec::Transition_IdleToExecuting() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kIdle, GetState());
+
+ // Transist to executing state.
+ OMX_ERRORTYPE omxresult = OMX_SendCommand(decoder_handle_,
+ OMX_CommandStateSet,
+ OMX_StateExecuting, 0);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SendCommand failed";
+ StateTransitionTask(kError);
+ return;
+ }
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Send command to disable output port.
+// 2. Free buffers of the output port.
+void OmxCodec::Transition_ExecutingToDisable() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kExecuting, GetState());
+
+ // Send DISABLE command.
+ OMX_ERRORTYPE omxresult = OMX_SendCommand(decoder_handle_,
+ OMX_CommandPortDisable,
+ output_port_, 0);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SendCommand failed";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // Free output Buffer.
+ FreeOutputBuffers();
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Send command to enable output port.
+// 2. Get parameter of the output port.
+// 3. Allocate buffers for the output port.
+void OmxCodec::Transition_DisableToEnable() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kPortSettingDisable, GetState());
+
+ // Send Enable command.
+ OMX_ERRORTYPE omxresult = OMX_SendCommand(decoder_handle_,
+ OMX_CommandPortEnable,
+ output_port_, 0);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SendCommand failed";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // AllocateBuffers.
+ OMX_PARAM_PORTDEFINITIONTYPE port_format;
+ ResetPortHeader(*this, &port_format);
+ port_format.nPortIndex = output_port_;
+ omxresult = OMX_GetParameter(decoder_handle_, OMX_IndexParamPortDefinition,
+ &port_format);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - GetParameter failed";
+ StateTransitionTask(kError);
+ return;
+ }
+ if (OMX_DirOutput != port_format.eDir) {
+ LOG(ERROR) << "Error - Expect Output Port";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ // Update the ports in buffer.
+ output_buffer_count_ = port_format.nBufferCountActual;
+ output_buffer_size_ = port_format.nBufferSize;
+ if (!AllocateOutputBuffers()) {
+ LOG(ERROR) << "Error - OMX_AllocateBuffer Output buffer error";
+ StateTransitionTask(kError);
+ return;
+ }
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Send command to Idle state.
+void OmxCodec::Transition_DisableToIdle() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kPortSettingDisable, GetState());
+
+ OMX_ERRORTYPE omxresult = OMX_SendCommand(decoder_handle_,
+ OMX_CommandStateSet,
+ OMX_StateIdle, 0);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SendCommand failed";
+ StateTransitionTask(kError);
+ return;
+ }
+}
+
+// Sequence of actions in this transition:
+//
+// This transition does nothing.
+void OmxCodec::Transition_EnableToExecuting() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kPortSettingEnable, GetState());
+
+ // This transition is fake, nothing to do here.
+ DoneStateTransitionTask();
+}
+
+void OmxCodec::Transition_EnableToIdle() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kPortSettingEnable, GetState());
+
+ OMX_ERRORTYPE omxresult = OMX_SendCommand(decoder_handle_,
+ OMX_CommandStateSet,
+ OMX_StateIdle, 0);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SendCommand failed";
+ StateTransitionTask(kError);
+ return;
+ }
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Send command to Idle state.
+void OmxCodec::Transition_ExecutingToIdle() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kExecuting, GetState());
+
+ OMX_ERRORTYPE omxresult = OMX_SendCommand(decoder_handle_,
+ OMX_CommandStateSet,
+ OMX_StateIdle, 0);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SendCommand failed";
+ StateTransitionTask(kError);
+ return;
+ }
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Send command to Loaded state
+// 2. Free input buffers
+// 2. Free output buffers
+void OmxCodec::Transition_IdleToLoaded() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kIdle, GetState());
+
+ OMX_ERRORTYPE omxresult = OMX_SendCommand(decoder_handle_,
+ OMX_CommandStateSet,
+ OMX_StateLoaded, 0);
+ if (omxresult != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - SendCommand failed";
+ StateTransitionTask(kError);
+ return;
+ }
+
+ FreeInputBuffers();
+ FreeOutputBuffers();
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Free decoder handle
+// 2. Uninitialize OMX (TODO(hclam): Remove this.)
+void OmxCodec::Transition_LoadedToEmpty() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_EQ(kLoaded, GetState());
+
+ // Free the decoder handle.
+ OMX_ERRORTYPE result = OMX_FreeHandle(decoder_handle_);
+ if (result != OMX_ErrorNone) {
+ LOG(ERROR) << "Error - Terminate: OMX_FreeHandle error. Error code: " << result;
+ }
+ decoder_handle_ = NULL;
+
+ // Deinit OpenMAX
+ // TODO(hclam): move this out.
+ OMX_Deinit();
+
+ DoneStateTransitionTask();
+}
+
+// Sequence of actions in this transition:
+//
+// 1. Disable input port
+// 2. Disable output port
+// 3. Free input buffer
+// 4. Free output buffer
+// 5. Free decoder handle
+// 6. Uninitialize OMX (TODO(hclam): Remove this.)
+void OmxCodec::Transition_Error() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+ DCHECK_NE(kError, GetState());
+
+ State old_state = GetState();
+ SetState(kError);
+
+ // If we are going to error state in the following states, we need to
+ // send a command to disable ports for us to free buffers.
+ if (old_state == kExecuting || old_state == kIdle ||
+ old_state == kPortSettingEnable || old_state == kPortSettingDisable) {
+ DCHECK(decoder_handle_);
+ OMX_SendCommand(decoder_handle_, OMX_CommandPortDisable, input_port_, 0);
+ OMX_SendCommand(decoder_handle_, OMX_CommandPortDisable, output_port_, 0);
+ }
+
+ // Free input and output buffers.
+ FreeInputBuffers();
+ FreeOutputBuffers();
+
+ // Free input and output queues.
+ FreeInputQueue();
+ FreeOutputQueue();
+
+ // Free decoder handle.
+ if (decoder_handle_) {
+ OMX_ERRORTYPE result = OMX_FreeHandle(decoder_handle_);
+ if (result != OMX_ErrorNone)
+ LOG(ERROR) << "Error - OMX_FreeHandle error. Error code: " << result;
+ decoder_handle_ = NULL;
+ }
+
+ // Deinit OpenMAX.
+ OMX_Deinit();
+
+ DoneStateTransitionTask();
+}
+
+void OmxCodec::PostStateTransitionTask(State new_state) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &OmxCodec::StateTransitionTask, new_state));
+}
+
+void OmxCodec::StateTransitionTask(State new_state) {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (GetState() == kError)
+ return;
+
+ // Save the next state.
+ SetNextState(new_state);
+
+ // The following list defines all the possible state transitions
+ // for this object:
+ //
+ // TRANSITIONS
+ // 1. Empty -> Loaded
+ // 2. Loaded -> Idle
+ // 3. Idle -> Executing
+ // 4. Executing -> Disable
+ // 5. Executing -> Idle
+ // 6. Disable -> Enable
+ // 7. Disable -> Idle
+ // 8. Enable -> Executing
+ // 9. Enable -> Idle
+ // 10. Idle -> Loaded
+ // 11. Loaded -> Empty (TODO(hclam): To stopped instead.)
+ // 12. *ANYTHING* -> Error
+ if (GetState() == kEmpty && new_state == kLoaded)
+ Transition_EmptyToLoaded();
+ else if (GetState() == kLoaded && new_state == kIdle)
+ Transition_LoadedToIdle();
+ else if (GetState() == kIdle && new_state == kExecuting)
+ Transition_IdleToExecuting();
+ else if (GetState() == kExecuting && new_state == kPortSettingDisable)
+ Transition_ExecutingToDisable();
+ else if (GetState() == kPortSettingDisable && new_state == kPortSettingEnable)
+ Transition_DisableToEnable();
+ else if (GetState() == kPortSettingDisable && new_state == kIdle)
+ Transition_DisableToIdle();
+ else if (GetState() == kPortSettingEnable && new_state == kExecuting)
+ Transition_EnableToExecuting();
+ else if (GetState() == kPortSettingEnable && new_state == kIdle)
+ Transition_EnableToIdle();
+ else if (GetState() == kExecuting && new_state == kIdle)
+ Transition_ExecutingToIdle();
+ else if (GetState() == kIdle && new_state == kLoaded)
+ Transition_IdleToLoaded();
+ else if (GetState() == kLoaded && new_state == kEmpty)
+ Transition_LoadedToEmpty();
+ else if (new_state == kError)
+ Transition_Error();
+}
+
+void OmxCodec::PostDoneStateTransitionTask() {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this, &OmxCodec::DoneStateTransitionTask));
+}
+
+void OmxCodec::DoneStateTransitionTask() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (GetState() == kError) {
+ ReportError();
+ return;
+ }
+
+ // Save the current state and completes the transition.
+ State old_state = GetState();
+ SetState(GetNextState());
+
+ // The following list is to perform a state transition automatically
+ // based on the last transition done:
+ //
+ // LAST TRANSITION NEXT TRANSITION
+ //
+ // 1. Empty -> Loaded Laoded -> Idle
+ // 2. Loaded -> Idle Idle -> Executing
+ // 3. Idle -> Executing
+ //
+ // Because of the above reoute, once we kick start the transition
+ // from empty to loaded, this method will automatically route it
+ // executing eventually.
+ //
+ // The following sequence is for transition to the stopped state.
+ //
+ // LAST TRANSITION NEXT TRANSITION
+ //
+ // 4. Executing -> Idle Idle -> Loaded
+ // 5. Idle -> Loaded Loaded -> Empty
+ // TODO(hclam): should go to Stopped instead of Empty.
+ //
+ // During dynamic port seeting, the route of state transition is:
+ //
+ // LAST TRANSITION NEXT TRANSITION
+ //
+ // 6. Executing -> Disable Disable -> Enable
+ // 7. Disable -> Enable Enable -> Executing
+ if (old_state == kEmpty && GetState() == kLoaded)
+ StateTransitionTask(kIdle);
+ else if (old_state == kLoaded && GetState() == kIdle)
+ StateTransitionTask(kExecuting);
+ else if (old_state == kIdle && GetState() == kExecuting) {
+ // TODO(hclam): It is a little too late to issue read requests.
+ // This seems to introduce some latencies.
+ InitialEmptyBuffer();
+ InitialFillBuffer();
+ }
+ else if (old_state == kExecuting && GetState() == kPortSettingDisable)
+ StateTransitionTask(kPortSettingEnable);
+ else if (old_state == kPortSettingDisable && GetState() == kPortSettingEnable)
+ StateTransitionTask(kExecuting);
+ else if (old_state == kPortSettingEnable && GetState() == kExecuting) {
+ InitialFillBuffer();
+ }
+ else if (old_state == kPortSettingDisable && GetState() == kIdle)
+ StateTransitionTask(kLoaded);
+ else if (old_state == kPortSettingEnable && GetState() == kIdle)
+ StateTransitionTask(kLoaded);
+ else if (old_state == kExecuting && GetState() == kIdle)
+ StateTransitionTask(kLoaded);
+ else if (old_state == kIdle && GetState() == kLoaded)
+ StateTransitionTask(kEmpty);
+ else if (old_state == kLoaded && GetState() == kEmpty)
+ DoneStop();
+ else {
+ NOTREACHED() << "Invalid state transition";
+ }
+}
+
+void OmxCodec::DoneStop() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (!stop_callback_.get())
+ return;
+ stop_callback_->Run();
+ stop_callback_.reset();
+}
+
+void OmxCodec::ReportError() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (!error_callback_.get())
+ return;
+ error_callback_->Run();
+ error_callback_.reset();
+}
+
+bool OmxCodec::CanEmptyBuffer() {
+ // We can call empty buffer while we are in executing or enabling / disabling
+ // the output port.
+ return (GetState() == kExecuting || GetState() == kPortSettingDisable ||
+ GetState() == kPortSettingEnable) &&
+ (GetNextState() == kExecuting || GetNextState() == kPortSettingDisable ||
+ GetNextState() == kPortSettingEnable);
+}
+
+bool OmxCodec::CanFillBuffer() {
+ // Make sure that we are staying in the executing state.
+ return GetState() == kExecuting && GetState() == GetNextState();
+}
+
+bool OmxCodec::CanAcceptInput() {
+ // We can't take input buffer when in error state.
+ // TODO(hclam): Reject when in stopped state.
+ return GetState() != kError;
+}
+
+bool OmxCodec::CanAcceptOutput() {
+ // Don't output request when in error state.
+ // TODO(hclam): Reject when in stopped state.
+ return GetState() != kError;
+}
+
+void OmxCodec::EmptyBufferCompleteTask(OMX_BUFFERHEADERTYPE* buffer) {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (!CanEmptyBuffer())
+ return;
+
+ // Enqueue the available buffer beacuse the decoder has consumed it.
+ available_input_buffers_.push(buffer);
+
+ // Try to feed more data into the decoder.
+ EmptyBufferTask();
+}
+
+void OmxCodec::EmptyBufferTask() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (!CanEmptyBuffer())
+ return;
+
+ // Loop for all available input data and input buffer for the
+ // decoder. When input has reached EOS we need to stop.
+ while (!input_queue_.empty() &&
+ !available_input_buffers_.empty() &&
+ !input_eos_) {
+ InputBuffer* buffer = input_queue_.front().first;
+ FeedCallback* callback = input_queue_.front().second;
+ OMX_BUFFERHEADERTYPE* omx_buffer = available_input_buffers_.front();
+ available_input_buffers_.pop();
+
+ // Read into |omx_buffer|.
+ input_eos_ = buffer->IsEndOfStream();
+ int filled = buffer->Read(omx_buffer->pBuffer, input_buffer_size_);
+ if (buffer->Used()) {
+ input_queue_.pop();
+ callback->Run(buffer);
+ delete callback;
+ }
+
+ omx_buffer->nInputPortIndex = input_port_;
+ omx_buffer->nOffset = 0;
+ omx_buffer->nFlags = 0;
+ omx_buffer->nFilledLen = filled;
+ omx_buffer->pAppPrivate = this;
+ omx_buffer->nFlags |= input_eos_ ? OMX_BUFFERFLAG_EOS : 0;
+
+ // Give this buffer to OMX.
+ OMX_ERRORTYPE ret = OMX_EmptyThisBuffer(decoder_handle_, omx_buffer);
+ if (ret != OMX_ErrorNone) {
+ LOG(ERROR) << "ERROR - OMX_EmptyThisBuffer failed with result " << ret;
+ StateTransitionTask(kError);
+ return;
+ }
+ }
+}
+
+void OmxCodec::FillBufferCompleteTask(OMX_BUFFERHEADERTYPE* buffer) {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (!CanFillBuffer())
+ return;
+
+ // Enqueue the decoded buffer.
+ available_output_buffers_.push(buffer);
+
+ // Fulfill read requests and read more from decoder.
+ FillBufferTask();
+}
+
+void OmxCodec::FillBufferTask() {
+ 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_) {
+ 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.
+ callback->RunWithParams(
+ MakeTuple(omx_buffer->pBuffer,
+ static_cast<int>(omx_buffer->nFilledLen)));
+ delete callback;
+
+ if (omx_buffer->nFlags & OMX_BUFFERFLAG_EOS)
+ output_eos_ = true;
+
+ omx_buffer->nOutputPortIndex = output_port_;
+ omx_buffer->pAppPrivate = this;
+ omx_buffer->nFlags &= ~OMX_BUFFERFLAG_EOS;
+ OMX_ERRORTYPE ret = OMX_FillThisBuffer(decoder_handle_, omx_buffer);
+ if (OMX_ErrorNone != ret) {
+ LOG(ERROR) << "Error - OMX_FillThisBuffer failed with result " << ret;
+ StateTransitionTask(kError);
+ return;
+ }
+ }
+}
+
+void OmxCodec::InitialEmptyBuffer() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ if (!CanEmptyBuffer())
+ return;
+
+ // Use EmptyBuffer() to use available input buffers to feed decoder.
+ EmptyBufferTask();
+}
+
+void OmxCodec::InitialFillBuffer() {
+ DCHECK_EQ(message_loop_, MessageLoop::current());
+
+ 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_ERRORTYPE ret = OMX_FillThisBuffer(decoder_handle_, omx_buffer);
+
+ if (OMX_ErrorNone != ret) {
+ LOG(ERROR) << "Error - OMX_FillThisBuffer failed with result " << ret;
+ StateTransitionTask(kError);
+ return;
+ }
+ }
+}
+
+void OmxCodec::EventHandlerInternal(OMX_HANDLETYPE component,
+ OMX_EVENTTYPE event,
+ OMX_U32 data1,
+ OMX_U32 data2,
+ OMX_PTR event_data) {
+ switch(event) {
+ case OMX_EventCmdComplete: {
+ // If the last command was successful, we have completed
+ // a state transition. So notify that we have done it
+ // accordingly.
+ OMX_COMMANDTYPE cmd = static_cast<OMX_COMMANDTYPE>(data1);
+ if (cmd == OMX_CommandPortEnable) {
+ PostDoneStateTransitionTask();
+ } else if (cmd == OMX_CommandPortDisable) {
+ PostDoneStateTransitionTask();
+ } else if (cmd == OMX_CommandStateSet) {
+ PostDoneStateTransitionTask();
+ } else {
+ LOG(ERROR) << "Unknown command completed\n";
+ }
+ break;
+ }
+ case OMX_EventError:
+ if (OMX_ErrorInvalidState == (OMX_ERRORTYPE)data1) {
+ // TODO(hclam): what to do here?
+ }
+ PostStateTransitionTask(kError);
+ break;
+ case OMX_EventPortSettingsChanged:
+ PostStateTransitionTask(kPortSettingDisable);
+ break;
+ default:
+ LOG(ERROR) << "Warning - Unknown event received\n";
+ break;
+ }
+}
+
+void OmxCodec::EmptyBufferCallbackInternal(
+ OMX_HANDLETYPE component,
+ OMX_BUFFERHEADERTYPE* buffer) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &OmxCodec::EmptyBufferCompleteTask, buffer));
+}
+
+void OmxCodec::FillBufferCallbackInternal(
+ OMX_HANDLETYPE component,
+ OMX_BUFFERHEADERTYPE* buffer) {
+ message_loop_->PostTask(
+ FROM_HERE,
+ NewRunnableMethod(this,
+ &OmxCodec::FillBufferCompleteTask, buffer));
+}
+
+// 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) {
+ OmxCodec* decoder = static_cast<OmxCodec*>(priv_data);
+ decoder->EventHandlerInternal(component, event, data1, data2, event_data);
+ return OMX_ErrorNone;
+}
+
+// static
+OMX_ERRORTYPE OmxCodec::EmptyBufferCallback(
+ OMX_HANDLETYPE component,
+ OMX_PTR priv_data,
+ OMX_BUFFERHEADERTYPE* buffer) {
+ OmxCodec* decoder = static_cast<OmxCodec*>(priv_data);
+ decoder->EmptyBufferCallbackInternal(component, buffer);
+ return OMX_ErrorNone;
+}
+
+// static
+OMX_ERRORTYPE OmxCodec::FillBufferCallback(
+ OMX_HANDLETYPE component,
+ OMX_PTR priv_data,
+ OMX_BUFFERHEADERTYPE* buffer) {
+ OmxCodec* decoder = static_cast<OmxCodec*>(priv_data);
+ decoder->FillBufferCallbackInternal(component, buffer);
+ return OMX_ErrorNone;
+}
+
+} // namespace media