blob: eb0582572b5d345d6fed123eb6a524c54db91544 [file] [log] [blame]
// 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, size_t minimum_output_size)
: condition_variable_(mutex_),
minimum_output_size_(minimum_output_size),
handle_(NULL),
input_port_(kInvalidPort),
output_port_(kInvalidPort),
output_setting_changed_(false),
output_buffer_(NULL),
output_buffer_filled_(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 (BufferHeaders::iterator iter = unused_buffers_.begin();
iter != unused_buffers_.end(); ++iter) {
OMX_ERRORTYPE error = OMX_FreeBuffer(handle_, input_port_, *iter);
SB_DCHECK(error == OMX_ErrorNone);
}
WaitForCommandCompletion();
SendCommand(OMX_CommandPortDisable, output_port_);
if (output_buffer_) {
OMX_ERRORTYPE error = OMX_FreeBuffer(handle_, output_port_, output_buffer_);
SB_DCHECK(error == OMX_ErrorNone);
}
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);
}
bool OpenMaxComponent::ReadVideoFrame(VideoFrame* frame) {
{
ScopedLock scoped_lock(mutex_);
if (output_buffer_ && !output_buffer_filled_) {
return false;
}
if (!output_setting_changed_) {
return false;
}
}
SB_DCHECK(output_setting_changed_);
if (!output_buffer_) {
GetOutputPortParam(&output_port_definition_);
SB_DCHECK(output_port_definition_.format.video.eColorFormat ==
OMX_COLOR_FormatYUV420PackedPlanar);
EnableOutputPortAndAllocateBuffer();
return false;
}
if (output_buffer_->nFlags & OMX_BUFFERFLAG_EOS) {
*frame = VideoFrame();
return true;
}
SbMediaTime timestamp =
((output_buffer_->nTimeStamp.nHighPart * 0x100000000ull) +
output_buffer_->nTimeStamp.nLowPart) *
kSbMediaTimeSecond / kSbTimeSecond;
int width = output_port_definition_.format.video.nFrameWidth;
int height = output_port_definition_.format.video.nSliceHeight;
int pitch = output_port_definition_.format.video.nStride;
*frame = VideoFrame::CreateYV12Frame(
width, height, pitch, timestamp, output_buffer_->pBuffer,
output_buffer_->pBuffer + pitch * height,
output_buffer_->pBuffer + pitch * height * 5 / 4);
output_buffer_filled_ = false;
output_buffer_->nFilledLen = 0;
OMX_ERRORTYPE error = OMX_FillThisBuffer(handle_, output_buffer_);
SB_DCHECK(error == OMX_ErrorNone);
return true;
}
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(unused_buffers_.empty());
OMXParamPortDefinition port_definition;
GetInputPortParam(&port_definition);
SendCommand(OMX_CommandPortEnable, input_port_);
unused_buffers_.resize(port_definition.nBufferCountActual);
for (int i = 0; i != port_definition.nBufferCountActual; ++i) {
OMX_ERRORTYPE error =
OMX_AllocateBuffer(handle_, &unused_buffers_[i], input_port_, NULL,
port_definition.nBufferSize);
SB_DCHECK(error == OMX_ErrorNone);
}
WaitForCommandCompletion();
}
void OpenMaxComponent::EnableOutputPortAndAllocateBuffer() {
if (output_buffer_ != NULL) {
return;
}
SendCommand(OMX_CommandPortEnable, output_port_);
OMX_ERRORTYPE error = OMX_AllocateBuffer(
handle_, &output_buffer_, output_port_, NULL,
std::max(output_port_definition_.nBufferSize, minimum_output_size_));
SB_DCHECK(error == OMX_ErrorNone);
WaitForCommandCompletion();
error = OMX_FillThisBuffer(handle_, output_buffer_);
SB_DCHECK(error == OMX_ErrorNone);
}
OMX_BUFFERHEADERTYPE* OpenMaxComponent::GetUnusedInputBuffer() {
for (;;) {
ScopedLock scoped_lock(mutex_);
if (!unused_buffers_.empty()) {
OMX_BUFFERHEADERTYPE* buffer_header = unused_buffers_.back();
unused_buffers_.pop_back();
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_buffers_.push_back(buffer);
}
void OpenMaxComponent::OnFillBufferDone(OMX_BUFFERHEADERTYPE* buffer) {
ScopedLock scoped_lock(mutex_);
SB_DCHECK(!output_buffer_filled_);
output_buffer_filled_ = true;
}
// 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