blob: 94c03daf4db12743554bac2a5c5af8d757a7d684 [file] [log] [blame]
// 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