| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "starboard/raspi/shared/open_max/open_max_component.h" |
| |
| #include <algorithm> |
| |
| #include "starboard/configuration.h" |
| #include "starboard/once.h" |
| #include "starboard/thread.h" |
| |
| namespace starboard { |
| namespace raspi { |
| namespace shared { |
| namespace open_max { |
| |
| namespace { |
| |
| const int kInvalidPort = -1; |
| |
| OMX_INDEXTYPE kPortTypes[] = {OMX_IndexParamAudioInit, OMX_IndexParamVideoInit, |
| OMX_IndexParamImageInit, OMX_IndexParamOtherInit}; |
| |
| SbOnceControl s_open_max_initialization_once = SB_ONCE_INITIALIZER; |
| |
| void DoInitializeOpenMax() { |
| OMX_ERRORTYPE error = OMX_Init(); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| |
| void InitializeOpenMax() { |
| bool initialized = |
| SbOnce(&s_open_max_initialization_once, DoInitializeOpenMax); |
| SB_DCHECK(initialized); |
| } |
| |
| } // namespace |
| |
| OpenMaxComponent::OpenMaxComponent(const char* name) |
| : condition_variable_(mutex_), |
| handle_(NULL), |
| input_port_(kInvalidPort), |
| output_port_(kInvalidPort), |
| output_setting_changed_(false), |
| output_port_enabled_(false) { |
| InitializeOpenMax(); |
| |
| OMX_CALLBACKTYPE callbacks; |
| callbacks.EventHandler = OpenMaxComponent::EventHandler; |
| callbacks.EmptyBufferDone = OpenMaxComponent::EmptyBufferDone; |
| callbacks.FillBufferDone = OpenMaxComponent::FillBufferDone; |
| |
| OMX_ERRORTYPE error = |
| OMX_GetHandle(&handle_, const_cast<char*>(name), this, &callbacks); |
| SB_DCHECK(error == OMX_ErrorNone); |
| |
| for (size_t i = 0; i < SB_ARRAY_SIZE(kPortTypes); ++i) { |
| OMX_PORT_PARAM_TYPE port; |
| port.nSize = sizeof(OMX_PORT_PARAM_TYPE); |
| port.nVersion.nVersion = OMX_VERSION; |
| |
| error = OMX_GetParameter(handle_, kPortTypes[i], &port); |
| if (error == OMX_ErrorNone && port.nPorts == 2) { |
| input_port_ = port.nStartPortNumber; |
| output_port_ = input_port_ + 1; |
| SendCommandAndWaitForCompletion(OMX_CommandPortDisable, input_port_); |
| SendCommandAndWaitForCompletion(OMX_CommandPortDisable, output_port_); |
| break; |
| } |
| } |
| SB_CHECK(input_port_ != kInvalidPort); |
| SB_CHECK(output_port_ != kInvalidPort); |
| SB_DLOG(INFO) << "Opened \"" << name << "\" with port " << input_port_ |
| << " and " << output_port_; |
| |
| SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateIdle); |
| } |
| |
| OpenMaxComponent::~OpenMaxComponent() { |
| if (!handle_) { |
| return; |
| } |
| SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateIdle); |
| |
| SendCommandAndWaitForCompletion(OMX_CommandFlush, input_port_); |
| SendCommandAndWaitForCompletion(OMX_CommandFlush, output_port_); |
| |
| SendCommand(OMX_CommandPortDisable, input_port_); |
| for (size_t i = 0; i < input_buffers_.size(); ++i) { |
| OMX_ERRORTYPE error = |
| OMX_FreeBuffer(handle_, input_port_, input_buffers_[i]); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| WaitForCommandCompletion(); |
| |
| SendCommand(OMX_CommandPortDisable, output_port_); |
| for (size_t i = 0; i < output_buffers_.size(); ++i) { |
| OMX_ERRORTYPE error = |
| OMX_FreeBuffer(handle_, output_port_, output_buffers_[i]); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| output_buffers_.clear(); |
| WaitForCommandCompletion(); |
| |
| SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateLoaded); |
| OMX_FreeHandle(handle_); |
| } |
| |
| void OpenMaxComponent::Start() { |
| EnableInputPortAndAllocateBuffers(); |
| SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateExecuting); |
| } |
| |
| void OpenMaxComponent::Flush() { |
| SendCommandAndWaitForCompletion(OMX_CommandFlush, input_port_); |
| SendCommandAndWaitForCompletion(OMX_CommandFlush, output_port_); |
| } |
| |
| void OpenMaxComponent::WriteData(const void* data, |
| size_t size, |
| SbTime timestamp) { |
| size_t offset = 0; |
| |
| while (offset != size) { |
| OMX_BUFFERHEADERTYPE* buffer_header = GetUnusedInputBuffer(); |
| |
| int size_to_append = std::min(size - offset, buffer_header->nAllocLen); |
| buffer_header->nOffset = 0; |
| buffer_header->nFilledLen = size_to_append; |
| buffer_header->nFlags = 0; |
| |
| buffer_header->nTimeStamp.nLowPart = timestamp; |
| buffer_header->nTimeStamp.nHighPart = timestamp >> 32; |
| |
| memcpy(buffer_header->pBuffer, (const char*)data + offset, size_to_append); |
| offset += size_to_append; |
| |
| OMX_ERRORTYPE error = OMX_EmptyThisBuffer(handle_, buffer_header); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| } |
| |
| void OpenMaxComponent::WriteEOS() { |
| OMX_BUFFERHEADERTYPE* buffer_header = GetUnusedInputBuffer(); |
| |
| buffer_header->nOffset = 0; |
| buffer_header->nFilledLen = 0; |
| buffer_header->nFlags = OMX_BUFFERFLAG_EOS; |
| |
| OMX_ERRORTYPE error = OMX_EmptyThisBuffer(handle_, buffer_header); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| |
| OMX_BUFFERHEADERTYPE* OpenMaxComponent::PeekNextOutputBuffer() { |
| { |
| ScopedLock scoped_lock(mutex_); |
| |
| if (!output_setting_changed_) { |
| return NULL; |
| } |
| } |
| |
| if (!output_port_enabled_) { |
| EnableOutputPortAndAllocateBuffer(); |
| } |
| |
| ScopedLock scoped_lock(mutex_); |
| return filled_output_buffers_.empty() ? NULL : filled_output_buffers_.front(); |
| } |
| |
| void OpenMaxComponent::DropNextOutputBuffer() { |
| OMX_BUFFERHEADERTYPE* buffer = NULL; |
| { |
| ScopedLock scoped_lock(mutex_); |
| SB_DCHECK(!filled_output_buffers_.empty()); |
| buffer = filled_output_buffers_.front(); |
| filled_output_buffers_.pop(); |
| } |
| buffer->nFilledLen = 0; |
| OMX_ERRORTYPE error = OMX_FillThisBuffer(handle_, buffer); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| |
| void OpenMaxComponent::SendCommand(OMX_COMMANDTYPE command, int param) { |
| OMX_ERRORTYPE error = OMX_SendCommand(handle_, command, param, NULL); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| |
| void OpenMaxComponent::WaitForCommandCompletion() { |
| for (;;) { |
| ScopedLock scoped_lock(mutex_); |
| for (EventDescriptions::iterator iter = event_descriptions_.begin(); |
| iter != event_descriptions_.end(); ++iter) { |
| if (iter->event == OMX_EventCmdComplete) { |
| event_descriptions_.erase(iter); |
| return; |
| } |
| // Special case for OMX_CommandStateSet. |
| if (iter->event == OMX_EventError && iter->data1 == OMX_ErrorSameState) { |
| event_descriptions_.erase(iter); |
| return; |
| } |
| } |
| condition_variable_.Wait(); |
| } |
| } |
| |
| void OpenMaxComponent::SendCommandAndWaitForCompletion(OMX_COMMANDTYPE command, |
| int param) { |
| SendCommand(command, param); |
| WaitForCommandCompletion(); |
| } |
| |
| void OpenMaxComponent::EnableInputPortAndAllocateBuffers() { |
| SB_DCHECK(input_buffers_.empty()); |
| |
| OMXParamPortDefinition port_definition; |
| GetInputPortParam(&port_definition); |
| if (OnEnableInputPort(&port_definition)) { |
| SetPortParam(port_definition); |
| } |
| |
| SendCommand(OMX_CommandPortEnable, input_port_); |
| |
| for (int i = 0; i < port_definition.nBufferCountActual; ++i) { |
| OMX_BUFFERHEADERTYPE* buffer; |
| OMX_ERRORTYPE error = OMX_AllocateBuffer(handle_, &buffer, input_port_, |
| NULL, port_definition.nBufferSize); |
| SB_DCHECK(error == OMX_ErrorNone); |
| input_buffers_.push_back(buffer); |
| unused_input_buffers_.push(buffer); |
| } |
| |
| WaitForCommandCompletion(); |
| } |
| |
| void OpenMaxComponent::EnableOutputPortAndAllocateBuffer() { |
| SB_DCHECK(!output_port_enabled_); |
| |
| GetOutputPortParam(&output_port_definition_); |
| if (OnEnableOutputPort(&output_port_definition_)) { |
| SetPortParam(output_port_definition_); |
| } |
| |
| SendCommand(OMX_CommandPortEnable, output_port_); |
| |
| output_buffers_.reserve(output_port_definition_.nBufferCountActual); |
| for (int i = 0; i < output_port_definition_.nBufferCountActual; ++i) { |
| OMX_BUFFERHEADERTYPE* buffer; |
| OMX_ERRORTYPE error = |
| OMX_AllocateBuffer(handle_, &buffer, output_port_, NULL, |
| output_port_definition_.nBufferSize); |
| SB_DCHECK(error == OMX_ErrorNone); |
| output_buffers_.push_back(buffer); |
| } |
| |
| WaitForCommandCompletion(); |
| |
| output_port_enabled_ = true; |
| |
| for (size_t i = 0; i < output_buffers_.size(); ++i) { |
| output_buffers_[i]->nFilledLen = 0; |
| OMX_ERRORTYPE error = OMX_FillThisBuffer(handle_, output_buffers_[i]); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| } |
| |
| OMX_BUFFERHEADERTYPE* OpenMaxComponent::GetUnusedInputBuffer() { |
| for (;;) { |
| { |
| ScopedLock scoped_lock(mutex_); |
| if (!unused_input_buffers_.empty()) { |
| OMX_BUFFERHEADERTYPE* buffer_header = unused_input_buffers_.front(); |
| unused_input_buffers_.pop(); |
| return buffer_header; |
| } |
| } |
| SbThreadSleep(kSbTimeMillisecond); |
| } |
| SB_NOTREACHED(); |
| return NULL; |
| } |
| |
| OMX_ERRORTYPE OpenMaxComponent::OnEvent(OMX_EVENTTYPE event, |
| OMX_U32 data1, |
| OMX_U32 data2, |
| OMX_PTR event_data) { |
| if (event == OMX_EventError && data1 != OMX_ErrorSameState) { |
| SB_NOTREACHED() << "OMX_EventError received with " << std::hex << data1 |
| << " " << data2; |
| return OMX_ErrorNone; |
| } |
| |
| ScopedLock scoped_lock(mutex_); |
| if (event == OMX_EventPortSettingsChanged && data1 == output_port_) { |
| output_setting_changed_ = true; |
| return OMX_ErrorNone; |
| } |
| EventDescription event_desc; |
| event_desc.event = event; |
| event_desc.data1 = data1; |
| event_desc.data2 = data2; |
| event_desc.event_data = event_data; |
| event_descriptions_.push_back(event_desc); |
| condition_variable_.Signal(); |
| |
| return OMX_ErrorNone; |
| } |
| |
| OMX_ERRORTYPE OpenMaxComponent::OnEmptyBufferDone( |
| OMX_BUFFERHEADERTYPE* buffer) { |
| ScopedLock scoped_lock(mutex_); |
| unused_input_buffers_.push(buffer); |
| } |
| |
| void OpenMaxComponent::OnFillBufferDone(OMX_BUFFERHEADERTYPE* buffer) { |
| ScopedLock scoped_lock(mutex_); |
| filled_output_buffers_.push(buffer); |
| } |
| |
| // static |
| OMX_ERRORTYPE OpenMaxComponent::EventHandler(OMX_HANDLETYPE handle, |
| OMX_PTR app_data, |
| OMX_EVENTTYPE event, |
| OMX_U32 data1, |
| OMX_U32 data2, |
| OMX_PTR event_data) { |
| SB_DCHECK(app_data != NULL); |
| OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data); |
| SB_DCHECK(handle == component->handle_); |
| |
| return component->OnEvent(event, data1, data2, event_data); |
| } |
| |
| // static |
| OMX_ERRORTYPE OpenMaxComponent::EmptyBufferDone(OMX_HANDLETYPE handle, |
| OMX_PTR app_data, |
| OMX_BUFFERHEADERTYPE* buffer) { |
| SB_DCHECK(app_data != NULL); |
| OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data); |
| SB_DCHECK(handle == component->handle_); |
| |
| return component->OnEmptyBufferDone(buffer); |
| } |
| |
| // static |
| OMX_ERRORTYPE OpenMaxComponent::FillBufferDone(OMX_HANDLETYPE handle, |
| OMX_PTR app_data, |
| OMX_BUFFERHEADERTYPE* buffer) { |
| SB_DCHECK(app_data != NULL); |
| OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data); |
| SB_DCHECK(handle == component->handle_); |
| |
| component->OnFillBufferDone(buffer); |
| |
| return OMX_ErrorNone; |
| } |
| |
| } // namespace open_max |
| } // namespace shared |
| } // namespace raspi |
| } // namespace starboard |