| // Copyright 2016 The Cobalt Authors. 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 { |
| |
| OpenMaxComponent::OpenMaxComponent(const char* name) |
| : OpenMaxComponentBase(name), |
| output_setting_changed_(false), |
| outstanding_output_buffers_(0), |
| output_component_(NULL) {} |
| |
| void OpenMaxComponent::SetOutputComponent(OpenMaxComponent* component) { |
| SB_DCHECK(component != NULL); |
| SB_DCHECK(component->input_buffers_.empty()); |
| SB_DCHECK(output_component_ == NULL); |
| SB_DCHECK(output_buffers_.empty()); |
| output_component_ = component; |
| } |
| |
| void OpenMaxComponent::CloseTunnel() { |
| OMX_ERRORTYPE error; |
| |
| Flush(); |
| OpenMaxComponent* prev = this; |
| OpenMaxComponent* current = output_component_; |
| while (current != NULL) { |
| current->Flush(); |
| |
| prev->SendCommand(OMX_CommandPortDisable, prev->output_port_); |
| current->SendCommand(OMX_CommandPortDisable, current->input_port_); |
| prev->WaitForCommandCompletion(); |
| current->WaitForCommandCompletion(); |
| |
| error = OMX_SetupTunnel(prev->handle_, prev->output_port_, NULL, 0); |
| SB_DCHECK(error == OMX_ErrorNone) |
| << "OMX_SetupTunnel " << prev->output_port_ |
| << " to 0 failed with error " << std::hex << error; |
| error = OMX_SetupTunnel(current->handle_, current->input_port_, NULL, 0); |
| SB_DCHECK(error == OMX_ErrorNone) |
| << "OMX_SetupTunnel " << current->input_port_ |
| << " to 0 failed with error " << std::hex << error; |
| |
| prev->output_component_ = NULL; |
| prev = current; |
| current = current->output_component_; |
| } |
| } |
| |
| void OpenMaxComponent::Start() { |
| SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateIdle); |
| EnableInputPortAndAllocateBuffers(); |
| SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateExecuting); |
| } |
| |
| void OpenMaxComponent::Flush() { |
| DisableOutputPort(); |
| SendCommandAndWaitForCompletion(OMX_CommandFlush, input_port_); |
| SendCommandAndWaitForCompletion(OMX_CommandFlush, output_port_); |
| } |
| |
| int OpenMaxComponent::WriteData(const void* data, int size, DataType type, |
| SbTime timestamp) { |
| int offset = 0; |
| |
| while (offset < size) { |
| OMX_BUFFERHEADERTYPE* buffer_header = GetUnusedInputBuffer(); |
| if (buffer_header == NULL) { |
| return offset; |
| } |
| |
| int size_to_append = size - offset; |
| if (size_to_append > buffer_header->nAllocLen) { |
| size_to_append = buffer_header->nAllocLen; |
| buffer_header->nFlags = 0; |
| } else if (type == kDataEOS) { |
| buffer_header->nFlags = OMX_BUFFERFLAG_EOS; |
| } else { |
| buffer_header->nFlags = 0; |
| } |
| buffer_header->nOffset = 0; |
| buffer_header->nFilledLen = size_to_append; |
| |
| buffer_header->nTimeStamp.nLowPart = timestamp; |
| buffer_header->nTimeStamp.nHighPart = timestamp >> 32; |
| |
| memcpy(buffer_header->pBuffer, reinterpret_cast<const char*>(data) + offset, |
| size_to_append); |
| offset += size_to_append; |
| |
| OMX_ERRORTYPE error = OMX_EmptyThisBuffer(handle_, buffer_header); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| |
| return offset; |
| } |
| |
| bool OpenMaxComponent::WriteEOS() { |
| OMX_BUFFERHEADERTYPE* buffer_header; |
| if ((buffer_header = GetUnusedInputBuffer()) == NULL) { |
| return false; |
| } |
| |
| 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); |
| return true; |
| } |
| |
| OMX_BUFFERHEADERTYPE* OpenMaxComponent::GetOutputBuffer() { |
| bool enable_output_port = false; |
| |
| { |
| mutex_.Acquire(); |
| if (!output_setting_changed_ && output_buffers_.empty()) { |
| mutex_.Release(); |
| if (output_component_ == NULL) { |
| return NULL; |
| } else { |
| return output_component_->GetOutputBuffer(); |
| } |
| } |
| |
| enable_output_port = output_setting_changed_ && |
| filled_output_buffers_.empty() && |
| outstanding_output_buffers_ == 0; |
| mutex_.Release(); |
| } |
| |
| if (enable_output_port) { |
| EnableOutputPortAndAllocateBuffer(); |
| output_setting_changed_ = false; |
| } |
| |
| ScopedLock scoped_lock(mutex_); |
| if (filled_output_buffers_.empty()) { |
| return NULL; |
| } |
| |
| SB_DCHECK(output_component_ == NULL); |
| OMX_BUFFERHEADERTYPE* buffer = filled_output_buffers_.front(); |
| filled_output_buffers_.pop(); |
| ++outstanding_output_buffers_; |
| return buffer; |
| } |
| |
| void OpenMaxComponent::DropOutputBuffer(OMX_BUFFERHEADERTYPE* buffer) { |
| if (output_component_ != NULL) { |
| output_component_->DropOutputBuffer(buffer); |
| return; |
| } |
| |
| { |
| ScopedLock scoped_lock(mutex_); |
| if (output_buffers_.empty()) { |
| SB_DCHECK(outstanding_output_buffers_ == 0); |
| return; |
| } |
| SB_DCHECK(outstanding_output_buffers_ > 0); |
| --outstanding_output_buffers_; |
| |
| if (output_setting_changed_) { |
| return; |
| } |
| } |
| buffer->nFilledLen = 0; |
| OMX_ERRORTYPE error = OMX_FillThisBuffer(handle_, buffer); |
| SB_DCHECK(error == OMX_ErrorNone); |
| } |
| |
| 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(); |
| |
| DisableOutputPort(); |
| |
| SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateLoaded); |
| } |
| |
| void OpenMaxComponent::OnErrorEvent(OMX_U32 data1, |
| OMX_U32 data2, |
| OMX_PTR event_data) { |
| SB_NOTREACHED() << "OMX_EventError received with " << std::hex << data1 |
| << " " << data2; |
| } |
| |
| OMX_BUFFERHEADERTYPE* OpenMaxComponent::AllocateBuffer(int port, |
| int index, |
| OMX_U32 size) { |
| OMX_BUFFERHEADERTYPE* buffer; |
| OMX_ERRORTYPE error = OMX_AllocateBuffer(handle_, &buffer, port, NULL, size); |
| SB_DCHECK(error == OMX_ErrorNone); |
| return buffer; |
| } |
| |
| void OpenMaxComponent::DisableOutputPort() { |
| if (!output_buffers_.empty()) { |
| SendCommandAndWaitForCompletion(OMX_CommandFlush, output_port_); |
| |
| 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(); |
| while (!filled_output_buffers_.empty()) { |
| filled_output_buffers_.pop(); |
| } |
| } |
| outstanding_output_buffers_ = 0; |
| } |
| |
| 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 = |
| AllocateBuffer(input_port_, i, port_definition.nBufferSize); |
| input_buffers_.push_back(buffer); |
| free_input_buffers_.push(buffer); |
| } |
| |
| WaitForCommandCompletion(); |
| } |
| |
| void OpenMaxComponent::EnableOutputPortAndAllocateBuffer() { |
| DisableOutputPort(); |
| |
| OMXParamPortDefinition output_port_definition; |
| |
| GetOutputPortParam(&output_port_definition); |
| if (OnEnableOutputPort(&output_port_definition)) { |
| SetPortParam(output_port_definition); |
| } |
| |
| if (output_component_ != NULL) { |
| EnableOutputTunnelling(output_port_definition); |
| return; |
| } |
| |
| // Finish Start() for tunnel components. |
| if (input_buffers_.empty()) { |
| // Call OnEnableOutputPort() again with the final port settings. |
| // This is only meant to notify the component of the settings -- any |
| // changes should have been made on the first call. |
| GetOutputPortParam(&output_port_definition); |
| if (OnEnableOutputPort(&output_port_definition)) { |
| SB_NOTREACHED() << "Tunnel port parameters cannot be changed now"; |
| } |
| SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateExecuting); |
| } |
| |
| 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 = |
| AllocateBuffer(output_port_, i, output_port_definition.nBufferSize); |
| output_buffers_.push_back(buffer); |
| } |
| |
| WaitForCommandCompletion(); |
| |
| 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); |
| } |
| } |
| |
| void OpenMaxComponent::EnableOutputTunnelling( |
| const OMXParamPortDefinition& port_definition) { |
| // Setup tunnelling to output component. Set the output component's |
| // input port to look exactly like our output. |
| OMXParamPortDefinition input_port = port_definition; |
| input_port.nPortIndex = output_component_->input_port_; |
| output_component_->OnEnableInputPort(&input_port); |
| output_component_->SetPortParam(input_port); |
| |
| OMX_ERRORTYPE error = |
| OMX_SetupTunnel(handle_, output_port_, output_component_->handle_, |
| output_component_->input_port_); |
| SB_DCHECK(error == OMX_ErrorNone) << "OMX_SetupTunnel " << output_port_ |
| << " to " << output_component_->input_port_ |
| << " failed with error " << std::hex |
| << error; |
| |
| // Enable the tunnel. This takes place of output_component_->Start(), but |
| // the component will still need to be put into the executing state when |
| // its output port is enabled. |
| SendCommand(OMX_CommandPortEnable, output_port_); |
| output_component_->SendCommand(OMX_CommandPortEnable, |
| output_component_->input_port_); |
| output_component_->WaitForCommandCompletion(); |
| output_component_->SendCommandAndWaitForCompletion(OMX_CommandStateSet, |
| OMX_StateIdle); |
| WaitForCommandCompletion(); |
| } |
| |
| OMX_BUFFERHEADERTYPE* OpenMaxComponent::GetUnusedInputBuffer() { |
| ScopedLock scoped_lock(mutex_); |
| if (!free_input_buffers_.empty()) { |
| OMX_BUFFERHEADERTYPE* buffer_header = free_input_buffers_.front(); |
| free_input_buffers_.pop(); |
| return buffer_header; |
| } |
| return NULL; |
| } |
| |
| void OpenMaxComponent::OnOutputSettingChanged() { |
| ScopedLock scoped_lock(mutex_); |
| output_setting_changed_ = true; |
| } |
| |
| OMX_ERRORTYPE OpenMaxComponent::OnEmptyBufferDone( |
| OMX_BUFFERHEADERTYPE* buffer) { |
| ScopedLock scoped_lock(mutex_); |
| free_input_buffers_.push(buffer); |
| return OMX_ErrorNone; |
| } |
| |
| void OpenMaxComponent::OnFillBufferDone(OMX_BUFFERHEADERTYPE* buffer) { |
| ScopedLock scoped_lock(mutex_); |
| filled_output_buffers_.push(buffer); |
| } |
| |
| } // namespace open_max |
| } // namespace shared |
| } // namespace raspi |
| } // namespace starboard |