// 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. // This class interacts with OmxCodec and the VideoDecoderImpl // in the media pipeline. // // THREADING SEMANTICS // // This class is created by OmxVideoDecoder and lives on the thread // that it lives. This class is given the message loop // for the above thread. The OMX callbacks are guaranteed to be // executed on the hosting message loop. Because of that there's no need // for locking anywhere. #include "media/video/omx_video_decode_engine.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/string_util.h" #include "media/base/buffers.h" namespace media { OmxVideoDecodeEngine::OmxVideoDecodeEngine() : width_(16), height_(16), message_loop_(NULL), input_buffer_count_(0), input_buffer_size_(0), input_port_(0), input_buffers_at_component_(0), input_pending_request_(0), input_queue_has_eos_(false), input_has_fed_eos_(false), input_port_flushed_(false), output_buffer_count_(0), output_buffer_size_(0), output_port_(0), output_buffers_at_component_(0), output_pending_request_(0), output_eos_(false), output_port_flushed_(false), il_state_(kIlNone), expected_il_state_(kIlNone), client_state_(kClientNotInitialized), component_handle_(NULL), need_free_input_buffers_(false), need_free_output_buffers_(false), flush_pending_(false), output_frames_allocated_(false), need_setup_output_port_(false) { // TODO(wjia): change uses_egl_image_ to runtime setup #if ENABLE_EGLIMAGE == 1 uses_egl_image_ = true; DLOG(INFO) << "Uses egl image for output"; #else uses_egl_image_ = false; DLOG(INFO) << "Uses system memory for output"; #endif } OmxVideoDecodeEngine::~OmxVideoDecodeEngine() { DCHECK(client_state_ == kClientNotInitialized || client_state_ == kClientStopped); DCHECK_EQ(il_state_, kIlNone); DCHECK_EQ(0u, input_buffers_.size()); DCHECK(free_input_buffers_.empty()); DCHECK(available_input_buffers_.empty()); DCHECK_EQ(0, input_buffers_at_component_); DCHECK_EQ(0, output_buffers_at_component_); DCHECK(output_frames_.empty()); } template static void ResetParamHeader(const OmxVideoDecodeEngine& dec, T* param) { memset(param, 0, sizeof(T)); param->nVersion.nVersion = dec.current_omx_spec_version(); param->nSize = sizeof(T); } void OmxVideoDecodeEngine::Initialize( MessageLoop* message_loop, VideoDecodeEngine::EventHandler* event_handler, VideoDecodeContext* context, const VideoCodecConfig& config) { DCHECK_EQ(message_loop, MessageLoop::current()); message_loop_ = message_loop; event_handler_ = event_handler; width_ = config.width; height_ = config.height; // TODO(wjia): Find the right way to determine the codec type. OmxConfigurator::MediaFormat input_format, output_format; memset(&input_format, 0, sizeof(input_format)); memset(&output_format, 0, sizeof(output_format)); input_format.codec = OmxConfigurator::kCodecH264; output_format.codec = OmxConfigurator::kCodecRaw; configurator_.reset( new OmxDecoderConfigurator(input_format, output_format)); // TODO(jiesun): We already ensure Initialize() is called in thread context, // We should try to merge the following function into this function. client_state_ = kClientInitializing; InitializeTask(); VideoCodecInfo info; // TODO(jiesun): ridiculous, we never fail initialization? info.success = true; info.provides_buffers = !uses_egl_image_; info.stream_info.surface_type = uses_egl_image_ ? VideoFrame::TYPE_GL_TEXTURE : VideoFrame::TYPE_SYSTEM_MEMORY; info.stream_info.surface_format = GetSurfaceFormat(); info.stream_info.surface_width = config.width; info.stream_info.surface_height = config.height; event_handler_->OnInitializeComplete(info); } // This method handles only input buffer, without coupling with output void OmxVideoDecodeEngine::ConsumeVideoSample(scoped_refptr buffer) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK(!free_input_buffers_.empty()); DCHECK_GT(input_pending_request_, 0); --input_pending_request_; if (!CanAcceptInput()) { FinishEmptyBuffer(buffer); return; } if (buffer->IsEndOfStream()) { DLOG(INFO) << "Input queue has EOS"; input_queue_has_eos_ = true; } OMX_BUFFERHEADERTYPE* omx_buffer = free_input_buffers_.front(); free_input_buffers_.pop(); // setup |omx_buffer|. omx_buffer->pBuffer = const_cast(buffer->GetData()); omx_buffer->nFilledLen = buffer->GetDataSize(); omx_buffer->nAllocLen = omx_buffer->nFilledLen; if (input_queue_has_eos_) omx_buffer->nFlags |= OMX_BUFFERFLAG_EOS; else omx_buffer->nFlags &= ~OMX_BUFFERFLAG_EOS; omx_buffer->nTimeStamp = buffer->GetTimestamp().InMicroseconds(); omx_buffer->pAppPrivate = buffer.get(); buffer->AddRef(); available_input_buffers_.push(omx_buffer); // Try to feed buffers into the decoder. EmptyBufferTask(); if (flush_pending_ && input_pending_request_ == 0) StartFlush(); } void OmxVideoDecodeEngine::Flush() { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(il_state_, kIlExecuting); if (il_state_ != kIlExecuting) { event_handler_->OnFlushComplete(); return; } client_state_ = kClientFlushing; expected_il_state_ = kIlPause; OnStateSetEventFunc = &OmxVideoDecodeEngine::PauseFromExecuting; TransitionToState(OMX_StatePause); } void OmxVideoDecodeEngine::PauseFromExecuting(OMX_STATETYPE state) { DCHECK_EQ(message_loop_, MessageLoop::current()); OnStateSetEventFunc = NULL; il_state_ = kIlPause; if (input_pending_request_ == 0) StartFlush(); else flush_pending_ = true; } void OmxVideoDecodeEngine::StartFlush() { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(input_pending_request_, 0); DLOG(INFO) << "StartFlush"; while (!available_input_buffers_.empty()) available_input_buffers_.pop(); flush_pending_ = false; // Flush input port first. OnFlushEventFunc = &OmxVideoDecodeEngine::PortFlushDone; OMX_ERRORTYPE omxresult; omxresult = OMX_SendCommand(component_handle_, OMX_CommandFlush, input_port_, 0); } bool OmxVideoDecodeEngine::InputPortFlushed() { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(client_state_, kClientFlushing); // Port flushed is defined by OpenMAX component had signal flush done and // We had all buffers returned from demuxer and OpenMAX component. int free_input_size = static_cast(free_input_buffers_.size()); return input_port_flushed_ && free_input_size == input_buffer_count_; } bool OmxVideoDecodeEngine::OutputPortFlushed() { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(client_state_, kClientFlushing); // Port flushed is defined by OpenMAX component had signal flush done and // We had all buffers returned from renderer and OpenMAX component. return output_port_flushed_ && output_pending_request_ == 0; } void OmxVideoDecodeEngine::ComponentFlushDone() { DCHECK_EQ(message_loop_, MessageLoop::current()); DLOG(INFO) << "Component had been flushed!"; if (input_port_flushed_ && output_port_flushed_) { event_handler_->OnFlushComplete(); input_port_flushed_ = false; output_port_flushed_ = false; } } void OmxVideoDecodeEngine::PortFlushDone(int port) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_NE(port, static_cast(OMX_ALL)); if (port == input_port_) { DLOG(INFO) << "Input Port had been flushed"; DCHECK_EQ(input_buffers_at_component_, 0); input_port_flushed_ = true; // Flush output port next. OMX_ERRORTYPE omxresult; omxresult = OMX_SendCommand(component_handle_, OMX_CommandFlush, output_port_, 0); return; } if (port == output_port_) { DLOG(INFO) << "Output Port had been flushed"; DCHECK_EQ(output_buffers_at_component_, 0); output_port_flushed_ = true; } if (kClientFlushing == client_state_ && InputPortFlushed() && OutputPortFlushed()) ComponentFlushDone(); } void OmxVideoDecodeEngine::Seek() { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK(client_state_ == kClientFlushing || // After a flush client_state_ == kClientInitializing); // After an initialize. if (client_state_ == kClientFlushing) { InitialReadBuffer(); OnStateSetEventFunc = &OmxVideoDecodeEngine::DoneSetStateExecuting; TransitionToState(OMX_StateExecuting); } event_handler_->OnSeekComplete(); } VideoFrame::Format OmxVideoDecodeEngine::GetSurfaceFormat() const { // TODO(jiesun): Both OmxHeaderType and EGLImage surface type could have // different surface formats. return uses_egl_image_ ? VideoFrame::RGBA : VideoFrame::YV12; } void OmxVideoDecodeEngine::Uninitialize() { DCHECK_EQ(message_loop_, MessageLoop::current()); if (client_state_ == kClientError) { OnStopDone(); return; } // TODO(wjia): add more state checking if (kClientRunning == client_state_ || kClientFlushing == client_state_) { client_state_ = kClientStopping; DeinitFromExecuting(OMX_StateExecuting); } // TODO(wjia): When FillThisBuffer() is added, engine state should be // kStopping here. engine state should be set to kStopped in OnStopDone(); // client_state_ = kClientStopping; } void OmxVideoDecodeEngine::FinishEmptyBuffer(scoped_refptr buffer) { DCHECK_EQ(message_loop_, MessageLoop::current()); if (!input_queue_has_eos_) { event_handler_->ProduceVideoSample(buffer); ++input_pending_request_; } } void OmxVideoDecodeEngine::FinishFillBuffer(OMX_BUFFERHEADERTYPE* buffer) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK(buffer); scoped_refptr frame; frame = static_cast(buffer->pAppPrivate); // We should not flush buffer to renderer during decoder flushing if decoder // provides the buffer allocator. if (kClientFlushing == client_state_ && !uses_egl_image_) return; frame->SetTimestamp(base::TimeDelta::FromMicroseconds(buffer->nTimeStamp)); frame->SetDuration(frame->GetTimestamp() - last_pts_); last_pts_ = frame->GetTimestamp(); event_handler_->ConsumeVideoFrame(frame); output_pending_request_--; } void OmxVideoDecodeEngine::OnStopDone() { DCHECK_EQ(message_loop_, MessageLoop::current()); event_handler_->OnUninitializeComplete(); } // Function sequence for initializing void OmxVideoDecodeEngine::InitializeTask() { DCHECK_EQ(il_state_, kIlNone); il_state_ = kIlNone; expected_il_state_ = kIlLoaded; output_port_state_ = kPortEnabled; if (!CreateComponent()) { StopOnError(); return; } il_state_ = kIlLoaded; // TODO(wjia): Disabling output port is to work around racing condition // due to bug in some vendor's driver. But it hits another bug. // So temporarily fall back to enabling output port. Still keep the code // disabling output port here. // No need to respond to this PortDisable event // OnPortDisableEventFunc = NULL; // ChangePort(OMX_CommandPortDisable, output_port_); // if (kClientError == client_state_) { // StopOnError(); // return; // } // output_port_state_ = kPortDisabled; // Transition component to Idle state OnStateSetEventFunc = &OmxVideoDecodeEngine::DoneSetStateIdle; if (!TransitionToState(OMX_StateIdle)) { StopOnError(); return; } expected_il_state_ = kIlIdle; if (!AllocateInputBuffers()) { LOG(ERROR) << "OMX_AllocateBuffer() Input buffer error"; client_state_ = kClientError; StopOnError(); return; } if (!AllocateOutputBuffers()) { LOG(ERROR) << "OMX_AllocateBuffer() Output buffer error"; client_state_ = kClientError; return; } } // Sequence of actions in this transition: // // 1. Initialize OMX (To be removed.) // 2. Map role name to component name. // 3. Get handle of the OMX component // 4. Get the port information. // 5. Set role for the component. // 6. Input/output ports media format configuration. // 7. Obtain the information about the input port. // 8. Obtain the information about the output port. bool OmxVideoDecodeEngine::CreateComponent() { DCHECK_EQ(message_loop_, MessageLoop::current()); static OMX_CALLBACKTYPE callback = { &OmxVideoDecodeEngine::EventHandler, &OmxVideoDecodeEngine::EmptyBufferCallback, &OmxVideoDecodeEngine::FillBufferCallback }; // 1. Initialize the OpenMAX Core. // TODO(hclam): move this out. OMX_ERRORTYPE omxresult = OMX_Init(); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "Failed to init OpenMAX core"; client_state_ = kClientError; return false; } // 2. Map role name to component name. std::string role_name = configurator_->GetRoleName(); OMX_U32 roles = 0; omxresult = OMX_GetComponentsOfRole( const_cast(role_name.c_str()), &roles, 0); if (omxresult != OMX_ErrorNone || roles == 0) { LOG(ERROR) << "Unsupported Role: " << role_name.c_str(); client_state_ = kClientError; return false; } const OMX_U32 kMaxRolePerComponent = 20; CHECK(roles < kMaxRolePerComponent); OMX_U8** component_names = new OMX_U8*[roles]; const int kMaxComponentNameLength = 256; for (size_t i = 0; i < roles; ++i) component_names[i] = new OMX_U8[kMaxComponentNameLength]; omxresult = OMX_GetComponentsOfRole( const_cast(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(component_names[0]); for (size_t i = 0; i < roles; ++i) delete [] component_names[i]; delete [] component_names; if (omxresult != OMX_ErrorNone || roles == 0) { LOG(ERROR) << "Unsupported Role: " << role_name.c_str(); client_state_ = kClientError; return false; } // 3. Get the handle to the component. After OMX_GetHandle(), // the component is in loaded state. OMX_STRING component = const_cast(component_name.c_str()); omxresult = OMX_GetHandle(&component_handle_, component, this, &callback); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "Failed to Load the component: " << component; client_state_ = kClientError; return false; } // 4. 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; ResetParamHeader(*this, &port_param); omxresult = OMX_GetParameter(component_handle_, OMX_IndexParamVideoInit, &port_param); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "Failed to get Port Param"; client_state_ = kClientError; return false; } input_port_ = port_param.nStartPortNumber; output_port_ = input_port_ + 1; // 5. Set role for the component because our component could // have multiple roles. OMX_PARAM_COMPONENTROLETYPE role_type; ResetParamHeader(*this, &role_type); base::strlcpy(reinterpret_cast(role_type.cRole), role_name.c_str(), OMX_MAX_STRINGNAME_SIZE); role_type.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0'; omxresult = OMX_SetParameter(component_handle_, OMX_IndexParamStandardComponentRole, &role_type); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "Failed to Set Role"; client_state_ = kClientError; return false; } // 6. Input/output ports media format configuration. if (!ConfigureIOPorts()) { LOG(ERROR) << "Media format configurations failed"; client_state_ = kClientError; return false; } // 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. OMX_PARAM_PORTDEFINITIONTYPE port_format; ResetParamHeader(*this, &port_format); port_format.nPortIndex = input_port_; omxresult = OMX_GetParameter(component_handle_, OMX_IndexParamPortDefinition, &port_format); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "GetParameter(OMX_IndexParamPortDefinition) failed"; client_state_ = kClientError; return false; } if (OMX_DirInput != port_format.eDir) { LOG(ERROR) << "Expected input port"; client_state_ = kClientError; return false; } input_buffer_count_ = port_format.nBufferCountActual; input_buffer_size_ = port_format.nBufferSize; // 8. Obtain the information about the output port. ResetParamHeader(*this, &port_format); port_format.nPortIndex = output_port_; omxresult = OMX_GetParameter(component_handle_, OMX_IndexParamPortDefinition, &port_format); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "GetParameter(OMX_IndexParamPortDefinition) failed"; client_state_ = kClientError; return false; } if (OMX_DirOutput != port_format.eDir) { LOG(ERROR) << "Expect Output Port"; client_state_ = kClientError; return false; } // TODO(wjia): use same buffer recycling for EGLImage and system memory. // Override buffer count when EGLImage is used. if (uses_egl_image_) { // TODO(wjia): remove hard-coded value port_format.nBufferCountActual = port_format.nBufferCountMin = output_buffer_count_ = 4; omxresult = OMX_SetParameter(component_handle_, OMX_IndexParamPortDefinition, &port_format); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "SetParameter(OMX_IndexParamPortDefinition) failed"; client_state_ = kClientError; return false; } } else { output_buffer_count_ = port_format.nBufferCountActual; } output_buffer_size_ = port_format.nBufferSize; return true; } // Event callback during initialization to handle DoneStateSet to idle void OmxVideoDecodeEngine::DoneSetStateIdle(OMX_STATETYPE state) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(client_state_, kClientInitializing); DCHECK_EQ(OMX_StateIdle, state); DLOG(INFO) << "OMX video decode engine is in Idle"; il_state_ = kIlIdle; // start reading bit stream InitialReadBuffer(); OnStateSetEventFunc = &OmxVideoDecodeEngine::DoneSetStateExecuting; if (!TransitionToState(OMX_StateExecuting)) { StopOnError(); return; } expected_il_state_ = kIlExecuting; } // Event callback during initialization to handle DoneStateSet to executing void OmxVideoDecodeEngine::DoneSetStateExecuting(OMX_STATETYPE state) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK(client_state_ == kClientInitializing || client_state_ == kClientFlushing); DCHECK_EQ(OMX_StateExecuting, state); DLOG(INFO) << "OMX video decode engine is in Executing"; il_state_ = kIlExecuting; client_state_ = kClientRunning; OnStateSetEventFunc = NULL; EmptyBufferTask(); InitialFillBuffer(); if (kClientError == client_state_) { StopOnError(); return; } } // Function for receiving output buffers. Hookup for buffer recycling // and outside allocator. void OmxVideoDecodeEngine::ProduceVideoFrame( scoped_refptr video_frame) { DCHECK(video_frame.get() && !video_frame->IsEndOfStream()); output_pending_request_++; if (!CanAcceptOutput()) { if (uses_egl_image_) { // return it to owner. output_pending_request_--; event_handler_->ConsumeVideoFrame(video_frame); } return; } OMX_BUFFERHEADERTYPE* omx_buffer = FindOmxBuffer(video_frame); if (omx_buffer) { if (kClientRunning == client_state_) { SendOutputBufferToComponent(omx_buffer); } else if (kClientFlushing == client_state_) { if (uses_egl_image_) { // return it to owner. output_pending_request_--; event_handler_->ConsumeVideoFrame(video_frame); } if (InputPortFlushed() && OutputPortFlushed()) ComponentFlushDone(); } } else { DCHECK(!output_frames_allocated_); DCHECK(uses_egl_image_); output_frames_.push_back(std::make_pair(video_frame, static_cast(NULL))); } DCHECK(static_cast(output_frames_.size()) <= output_buffer_count_); if ((!output_frames_allocated_) && static_cast(output_frames_.size()) == output_buffer_count_) { output_frames_allocated_ = true; if (need_setup_output_port_) { SetupOutputPort(); } } if (kClientError == client_state_) { StopOnError(); return; } } // Reconfigure port void OmxVideoDecodeEngine::OnPortSettingsChangedRun(int port, OMX_INDEXTYPE index) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(client_state_, kClientRunning); DCHECK_EQ(port, output_port_); // TODO(wjia): add buffer negotiation between decoder and renderer. if (uses_egl_image_) { DLOG(INFO) << "Port settings are changed"; return; } // TODO(wjia): remove this checking when all vendors observe same spec. if (index > OMX_IndexComponentStartUnused) { if (index != OMX_IndexParamPortDefinition) return; } OMX_PARAM_PORTDEFINITIONTYPE port_format; ResetParamHeader(*this, &port_format); port_format.nPortIndex = output_port_; OMX_ERRORTYPE omxresult; omxresult = OMX_GetParameter(component_handle_, OMX_IndexParamPortDefinition, &port_format); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "GetParameter(OMX_IndexParamPortDefinition) failed"; client_state_ = kClientError; StopOnError(); return; } if (OMX_DirOutput != port_format.eDir) { LOG(ERROR) << "Expected Output Port"; client_state_ = kClientError; StopOnError(); return; } // Update the output format. OmxConfigurator::MediaFormat output_format; output_format.video_header.height = port_format.format.video.nFrameHeight; output_format.video_header.width = port_format.format.video.nFrameWidth; output_format.video_header.stride = port_format.format.video.nStride; output_buffer_count_ = port_format.nBufferCountActual; output_buffer_size_ = port_format.nBufferSize; if (kPortEnabled == output_port_state_) { output_port_state_ = kPortDisabling; OnPortDisableEventFunc = &OmxVideoDecodeEngine::OnPortDisableEventRun; ChangePort(OMX_CommandPortDisable, output_port_); if (kClientError == client_state_) { StopOnError(); return; } FreeOutputBuffers(); } else { OnPortDisableEventRun(output_port_); } } // Post output port disabling void OmxVideoDecodeEngine::OnPortDisableEventRun(int port) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(client_state_, kClientRunning); DCHECK_EQ(port, output_port_); output_port_state_ = kPortDisabled; // make sure all eglimages are available before enabling output port if (output_frames_allocated_ || !uses_egl_image_) { SetupOutputPort(); if (kClientError == client_state_) { StopOnError(); return; } } else { need_setup_output_port_ = true; } } // Enable output port and allocate buffers correspondingly void OmxVideoDecodeEngine::SetupOutputPort() { DCHECK_EQ(message_loop_, MessageLoop::current()); need_setup_output_port_ = false; // Enable output port when necessary since the port could be waiting for // buffers, instead of port reconfiguration. if (kPortEnabled != output_port_state_) { output_port_state_ = kPortEnabling; OnPortEnableEventFunc = &OmxVideoDecodeEngine::OnPortEnableEventRun; ChangePort(OMX_CommandPortEnable, output_port_); if (kClientError == client_state_) { return; } } // TODO(wjia): add state checking // Update the ports in buffer if necessary if (!AllocateOutputBuffers()) { LOG(ERROR) << "OMX_AllocateBuffer() Output buffer error"; client_state_ = kClientError; return; } } // Post output port enabling void OmxVideoDecodeEngine::OnPortEnableEventRun(int port) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(port, output_port_); DCHECK_EQ(client_state_, kClientRunning); output_port_state_ = kPortEnabled; last_pts_ = base::TimeDelta::FromMilliseconds(0); OnPortEnableEventFunc = NULL; InitialFillBuffer(); if (kClientError == client_state_) { StopOnError(); return; } } void OmxVideoDecodeEngine::DeinitFromExecuting(OMX_STATETYPE state) { DCHECK_EQ(state, OMX_StateExecuting); DLOG(INFO) << "Deinit from Executing"; OnStateSetEventFunc = &OmxVideoDecodeEngine::DeinitFromIdle; TransitionToState(OMX_StateIdle); expected_il_state_ = kIlIdle; } void OmxVideoDecodeEngine::DeinitFromIdle(OMX_STATETYPE state) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(state, OMX_StateIdle); DLOG(INFO) << "Deinit from Idle"; il_state_ = kIlIdle; OnStateSetEventFunc = &OmxVideoDecodeEngine::DeinitFromLoaded; TransitionToState(OMX_StateLoaded); expected_il_state_ = kIlLoaded; if (!input_buffers_at_component_) FreeInputBuffers(); else need_free_input_buffers_ = true; if (!output_buffers_at_component_) FreeOutputBuffers(); else need_free_output_buffers_ = true; } void OmxVideoDecodeEngine::DeinitFromLoaded(OMX_STATETYPE state) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_EQ(state, OMX_StateLoaded); DLOG(INFO) << "Deinit from Loaded"; il_state_ = kIlLoaded; if (component_handle_) { OMX_ERRORTYPE result = OMX_FreeHandle(component_handle_); if (result != OMX_ErrorNone) LOG(ERROR) << "OMX_FreeHandle() error. Error code: " << result; component_handle_ = NULL; } il_state_ = expected_il_state_ = kIlNone; // kClientStopped is different from kClientNotInitialized. The former can't // accept output buffers, while the latter can. client_state_ = kClientStopped; OMX_Deinit(); OnStopDone(); } void OmxVideoDecodeEngine::StopOnError() { DCHECK_EQ(message_loop_, MessageLoop::current()); client_state_ = kClientStopping; if (kIlExecuting == expected_il_state_) { DeinitFromExecuting(OMX_StateExecuting); } else if (kIlIdle == expected_il_state_) { DeinitFromIdle(OMX_StateIdle); } else if (kIlLoaded == expected_il_state_) { DeinitFromLoaded(OMX_StateLoaded); } else if (kIlPause == expected_il_state_) { // TODO(jiesun): Make sure this works. DeinitFromExecuting(OMX_StateExecuting); } else { NOTREACHED(); } } // Call OMX_UseBuffer() to avoid buffer copying when // OMX_EmptyThisBuffer() is called bool OmxVideoDecodeEngine::AllocateInputBuffers() { DCHECK_EQ(message_loop_, MessageLoop::current()); uint8* data = new uint8[input_buffer_size_]; scoped_array data_deleter(data); for (int i = 0; i < input_buffer_count_; ++i) { OMX_BUFFERHEADERTYPE* buffer; OMX_ERRORTYPE error = OMX_UseBuffer(component_handle_, &buffer, input_port_, this, input_buffer_size_, data); if (error != OMX_ErrorNone) return false; buffer->nInputPortIndex = input_port_; buffer->nOffset = 0; buffer->nFlags = 0; input_buffers_.push_back(buffer); free_input_buffers_.push(buffer); } return true; } // This method handles EGLImage and internal buffer cases. Any external // allocation case is similar to EGLImage bool OmxVideoDecodeEngine::AllocateOutputBuffers() { DCHECK_EQ(message_loop_, MessageLoop::current()); if (uses_egl_image_ && !output_frames_allocated_) { DLOG(INFO) << "Output frames are not allocated yet"; need_setup_output_port_ = true; return true; } for (int i = 0; i < output_buffer_count_; ++i) { OMX_BUFFERHEADERTYPE* buffer; scoped_refptr video_frame; OMX_ERRORTYPE error; if (uses_egl_image_) { OutputFrame output_frame = output_frames_[i]; video_frame = output_frame.first; DCHECK(!output_frame.second); error = OMX_UseEGLImage(component_handle_, &buffer, output_port_, video_frame.get(), video_frame->private_buffer()); if (error != OMX_ErrorNone) return false; output_frames_[i].second = buffer; } else { error = OMX_AllocateBuffer(component_handle_, &buffer, output_port_, NULL, output_buffer_size_); if (error != OMX_ErrorNone) return false; video_frame = CreateOmxBufferVideoFrame(buffer); output_frames_.push_back(std::make_pair(video_frame, buffer)); buffer->pAppPrivate = video_frame.get(); } } return true; } scoped_refptr OmxVideoDecodeEngine::CreateOmxBufferVideoFrame( OMX_BUFFERHEADERTYPE* omx_buffer) { scoped_refptr video_frame; uint8* data[VideoFrame::kMaxPlanes]; int32 strides[VideoFrame::kMaxPlanes]; memset(data, 0, sizeof(data)); memset(strides, 0, sizeof(strides)); // TODO(jiesun): chroma format 4:2:0 only and 3 planes. data[0] = omx_buffer->pBuffer; data[1] = data[0] + width_ * height_; data[2] = data[1] + width_ * height_ / 4; strides[0] = width_; strides[1] = strides[2] = width_ >> 1; VideoFrame::CreateFrameExternal( VideoFrame::TYPE_SYSTEM_MEMORY, VideoFrame::YV12, width_, height_, 3, data, strides, StreamSample::kInvalidTimestamp, StreamSample::kInvalidTimestamp, omx_buffer, &video_frame); return video_frame; } void OmxVideoDecodeEngine::FreeInputBuffers() { DCHECK_EQ(message_loop_, MessageLoop::current()); // Empty available buffer queue. while (!free_input_buffers_.empty()) { free_input_buffers_.pop(); } while (!available_input_buffers_.empty()) { OMX_BUFFERHEADERTYPE* omx_buffer = available_input_buffers_.front(); available_input_buffers_.pop(); Buffer* stored_buffer = static_cast(omx_buffer->pAppPrivate); FinishEmptyBuffer(stored_buffer); stored_buffer->Release(); } // Calls to OMX to free buffers. for (size_t i = 0; i < input_buffers_.size(); ++i) OMX_FreeBuffer(component_handle_, input_port_, input_buffers_[i]); input_buffers_.clear(); need_free_input_buffers_ = false; } void OmxVideoDecodeEngine::FreeOutputBuffers() { DCHECK_EQ(message_loop_, MessageLoop::current()); // Calls to OMX to free buffers. for (size_t i = 0; i < output_frames_.size(); ++i) { OMX_BUFFERHEADERTYPE* omx_buffer = output_frames_[i].second; CHECK(omx_buffer); OMX_FreeBuffer(component_handle_, output_port_, omx_buffer); } output_frames_.clear(); output_frames_allocated_ = false; need_free_output_buffers_ = false; } bool OmxVideoDecodeEngine::ConfigureIOPorts() { OMX_PARAM_PORTDEFINITIONTYPE input_port_def, output_port_def; OMX_ERRORTYPE omxresult = OMX_ErrorNone; // Get default input port definition. ResetParamHeader(*this, &input_port_def); input_port_def.nPortIndex = input_port_; omxresult = OMX_GetParameter(component_handle_, OMX_IndexParamPortDefinition, &input_port_def); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "GetParameter(OMX_IndexParamPortDefinition) " << "for input port failed"; return false; } if (OMX_DirInput != input_port_def.eDir) { LOG(ERROR) << "Expected Input Port"; return false; } // Get default output port definition. ResetParamHeader(*this, &output_port_def); output_port_def.nPortIndex = output_port_; omxresult = OMX_GetParameter(component_handle_, OMX_IndexParamPortDefinition, &output_port_def); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "GetParameter(OMX_IndexParamPortDefinition) " << "for output port failed"; return false; } if (OMX_DirOutput != output_port_def.eDir) { LOG(ERROR) << "Expected Output Port"; return false; } return configurator_->ConfigureIOPorts( static_cast(component_handle_), &input_port_def, &output_port_def); } bool OmxVideoDecodeEngine::CanEmptyBuffer() { // We can call empty buffer while we are in executing and EOS has // not been sent return (il_state_ == kIlExecuting && !input_has_fed_eos_); } bool OmxVideoDecodeEngine::CanFillBuffer() { // Make sure component is in the executing state and end-of-stream // has not been reached. return (il_state_ == kIlExecuting && !output_eos_ && (output_port_state_ == kPortEnabled || output_port_state_ == kPortEnabling)); } bool OmxVideoDecodeEngine::CanAcceptInput() { // We can't take input buffer when in error state. return (kClientError != client_state_ && kClientStopping != client_state_ && kClientStopped != client_state_ && !input_queue_has_eos_); } bool OmxVideoDecodeEngine::CanAcceptOutput() { return (kClientError != client_state_ && kClientStopping != client_state_ && kClientStopped != client_state_ && output_port_state_ == kPortEnabled && !output_eos_); } // TODO(wjia): There are several things need to be done here: // 1. Merge this method into EmptyThisBuffer(); // 2. Get rid of the while loop, this is not needed because when we call // OMX_EmptyThisBuffer we assume we *always* have an input buffer. void OmxVideoDecodeEngine::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 (!available_input_buffers_.empty() && !input_has_fed_eos_) { OMX_BUFFERHEADERTYPE* omx_buffer = available_input_buffers_.front(); available_input_buffers_.pop(); input_has_fed_eos_ = omx_buffer->nFlags & OMX_BUFFERFLAG_EOS; if (input_has_fed_eos_) { DLOG(INFO) << "Input has fed EOS"; } // Give this buffer to OMX. input_buffers_at_component_++; OMX_ERRORTYPE ret = OMX_EmptyThisBuffer(component_handle_, omx_buffer); if (ret != OMX_ErrorNone) { LOG(ERROR) << "OMX_EmptyThisBuffer() failed with result " << ret; client_state_ = kClientError; return; } } } void OmxVideoDecodeEngine::InitialReadBuffer() { DCHECK_EQ(message_loop_, MessageLoop::current()); input_queue_has_eos_ = false; input_has_fed_eos_ = false; output_eos_ = false; DLOG(INFO) << "OmxVideoDecodeEngine::InitialReadBuffer"; for (size_t i = 0; i < free_input_buffers_.size(); i++) FinishEmptyBuffer(NULL); } void OmxVideoDecodeEngine::InitialFillBuffer() { DCHECK_EQ(message_loop_, MessageLoop::current()); // DCHECK(output_frames_allocated_); if (!CanFillBuffer()) return; DLOG(INFO) << "OmxVideoDecodeEngine::InitialFillBuffer"; // Ask the decoder to fill the output buffers. for (uint32 i = 0; i < output_frames_.size(); ++i) { OMX_BUFFERHEADERTYPE* omx_buffer = output_frames_[i].second; SendOutputBufferToComponent(omx_buffer); } } // helper functions // Send command to disable/enable port. void OmxVideoDecodeEngine::ChangePort(OMX_COMMANDTYPE cmd, int port_index) { DCHECK_EQ(message_loop_, MessageLoop::current()); OMX_ERRORTYPE omxresult = OMX_SendCommand(component_handle_, cmd, port_index, 0); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "SendCommand(OMX_CommandPortDisable) failed"; client_state_ = kClientError; return; } } // Find if omx_buffer exists corresponding to video_frame OMX_BUFFERHEADERTYPE* OmxVideoDecodeEngine::FindOmxBuffer( scoped_refptr video_frame) { for (size_t i = 0; i < output_frames_.size(); ++i) { if (video_frame == output_frames_[i].first) return output_frames_[i].second; } return NULL; } OMX_STATETYPE OmxVideoDecodeEngine::GetComponentState() { OMX_STATETYPE eState; OMX_ERRORTYPE eError; eError = OMX_GetState(component_handle_, &eState); if (OMX_ErrorNone != eError) { LOG(ERROR) << "OMX_GetState failed"; StopOnError(); } return eState; } // send one output buffer to component void OmxVideoDecodeEngine::SendOutputBufferToComponent( OMX_BUFFERHEADERTYPE *omx_buffer) { DCHECK_EQ(message_loop_, MessageLoop::current()); if (!CanFillBuffer()) return; // clear EOS flag. omx_buffer->nFlags &= ~OMX_BUFFERFLAG_EOS; omx_buffer->nOutputPortIndex = output_port_; output_buffers_at_component_++; OMX_ERRORTYPE ret = OMX_FillThisBuffer(component_handle_, omx_buffer); if (OMX_ErrorNone != ret) { LOG(ERROR) << "OMX_FillThisBuffer() failed with result " << ret; client_state_ = kClientError; return; } } // Send state transition command to component. bool OmxVideoDecodeEngine::TransitionToState(OMX_STATETYPE new_state) { DCHECK_EQ(message_loop_, MessageLoop::current()); OMX_ERRORTYPE omxresult = OMX_SendCommand(component_handle_, OMX_CommandStateSet, new_state, 0); if (omxresult != OMX_ErrorNone) { LOG(ERROR) << "SendCommand(OMX_CommandStateSet) failed"; client_state_ = kClientError; return false; } return true; } void OmxVideoDecodeEngine::EmptyBufferDoneTask(OMX_BUFFERHEADERTYPE* buffer) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_GT(input_buffers_at_component_, 0); Buffer* stored_buffer = static_cast(buffer->pAppPrivate); buffer->pAppPrivate = NULL; if (client_state_ != kClientFlushing) FinishEmptyBuffer(stored_buffer); stored_buffer->Release(); // Enqueue the available buffer because the decoder has consumed it. free_input_buffers_.push(buffer); input_buffers_at_component_--; if (need_free_input_buffers_ && !input_buffers_at_component_) { FreeInputBuffers(); return; } // Try to feed more data into the decoder. EmptyBufferTask(); if (client_state_ == kClientFlushing && InputPortFlushed() && OutputPortFlushed()) ComponentFlushDone(); } void OmxVideoDecodeEngine::FillBufferDoneTask(OMX_BUFFERHEADERTYPE* buffer) { DCHECK_EQ(message_loop_, MessageLoop::current()); DCHECK_GT(output_buffers_at_component_, 0); output_buffers_at_component_--; if (need_free_output_buffers_ && !output_buffers_at_component_) { FreeOutputBuffers(); return; } if (!CanAcceptOutput()) { if (uses_egl_image_) { scoped_refptr frame; frame = static_cast(buffer->pAppPrivate); event_handler_->ConsumeVideoFrame(frame); output_pending_request_--; } return; } // This buffer is received with decoded frame. Enqueue it and make it // ready to be consumed by reads. if (buffer->nFlags & OMX_BUFFERFLAG_EOS) { output_eos_ = true; DLOG(INFO) << "Output has EOS"; } FinishFillBuffer(buffer); if (buffer->nFlags & OMX_BUFFERFLAG_EOS) { // Singal end of stream. scoped_refptr frame; VideoFrame::CreateEmptyFrame(&frame); event_handler_->ConsumeVideoFrame(frame); } if (client_state_ == kClientFlushing && InputPortFlushed() && OutputPortFlushed()) ComponentFlushDone(); } void OmxVideoDecodeEngine::EventHandlerCompleteTask(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) { 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(data1); if (cmd == OMX_CommandPortDisable) { if (OnPortDisableEventFunc) (this->*OnPortDisableEventFunc)(static_cast(data2)); } else if (cmd == OMX_CommandPortEnable) { if (OnPortEnableEventFunc) (this->*OnPortEnableEventFunc)(static_cast(data2)); } else if (cmd == OMX_CommandStateSet) { (this->*OnStateSetEventFunc)(static_cast(data2)); } else if (cmd == OMX_CommandFlush) { (this->*OnFlushEventFunc)(data2); } else { LOG(ERROR) << "Unknown command completed\n" << data1; } break; } case OMX_EventError: if (OMX_ErrorInvalidState == (OMX_ERRORTYPE)data1) { // TODO(hclam): what to do here? } StopOnError(); break; case OMX_EventPortSettingsChanged: // TODO(wjia): remove this hack when all vendors observe same spec. if (data1 < OMX_IndexComponentStartUnused) OnPortSettingsChangedRun(static_cast(data1), static_cast(data2)); else OnPortSettingsChangedRun(static_cast(data2), static_cast(data1)); break; default: LOG(ERROR) << "Warning - Unknown event received\n"; break; } } // static OMX_ERRORTYPE OmxVideoDecodeEngine::EventHandler(OMX_HANDLETYPE component, OMX_PTR priv_data, OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2, OMX_PTR event_data) { OmxVideoDecodeEngine* decoder = static_cast(priv_data); DCHECK_EQ(component, decoder->component_handle_); decoder->message_loop_->PostTask(FROM_HERE, NewRunnableMethod(decoder, &OmxVideoDecodeEngine::EventHandlerCompleteTask, event, data1, data2)); return OMX_ErrorNone; } // static OMX_ERRORTYPE OmxVideoDecodeEngine::EmptyBufferCallback( OMX_HANDLETYPE component, OMX_PTR priv_data, OMX_BUFFERHEADERTYPE* buffer) { OmxVideoDecodeEngine* decoder = static_cast(priv_data); DCHECK_EQ(component, decoder->component_handle_); decoder->message_loop_->PostTask(FROM_HERE, NewRunnableMethod(decoder, &OmxVideoDecodeEngine::EmptyBufferDoneTask, buffer)); return OMX_ErrorNone; } // static OMX_ERRORTYPE OmxVideoDecodeEngine::FillBufferCallback( OMX_HANDLETYPE component, OMX_PTR priv_data, OMX_BUFFERHEADERTYPE* buffer) { OmxVideoDecodeEngine* decoder = static_cast(priv_data); DCHECK_EQ(component, decoder->component_handle_); decoder->message_loop_->PostTask(FROM_HERE, NewRunnableMethod(decoder, &OmxVideoDecodeEngine::FillBufferDoneTask, buffer)); return OMX_ErrorNone; } } // namespace media // Disable refcounting for this object because this object only lives // on the video decoder thread and there's no need to refcount it. DISABLE_RUNNABLE_METHOD_REFCOUNT(media::OmxVideoDecodeEngine);