Index: content/common/gpu/media/omx_video_decode_accelerator.cc |
diff --git a/content/common/gpu/media/omx_video_decode_accelerator.cc b/content/common/gpu/media/omx_video_decode_accelerator.cc |
index 28614f2974e6b3e76b691c705dd925b8daccaa38..5d05668f582a66481c49d023354d067eee853eef 100644 |
--- a/content/common/gpu/media/omx_video_decode_accelerator.cc |
+++ b/content/common/gpu/media/omx_video_decode_accelerator.cc |
@@ -57,10 +57,10 @@ static bool AreOMXFunctionPointersInitialized() { |
} while (0) |
// OMX-specific version of RETURN_ON_FAILURE which compares with OMX_ErrorNone. |
-#define RETURN_ON_OMX_FAILURE(omx_result, log, error, ret_val) \ |
- RETURN_ON_FAILURE( \ |
- ((omx_result) == OMX_ErrorNone), \ |
- log << ", OMX result: " << std::hex << std::showbase << omx_result, \ |
+#define RETURN_ON_OMX_FAILURE(omx_result, log, error, ret_val) \ |
+ RETURN_ON_FAILURE( \ |
+ ((omx_result) == OMX_ErrorNone), \ |
+ log << ", OMX result: 0x" << std::hex << omx_result, \ |
error, ret_val) |
OmxVideoDecodeAccelerator::OmxVideoDecodeAccelerator( |
@@ -182,6 +182,7 @@ bool OmxVideoDecodeAccelerator::CreateComponent() { |
VIDEODECODERERROR_UNSUPPORTED, false); |
// Get the handle to the component. |
+ AddRef(); // To reflect passing |this| to OMX_GetHandle below. |
result = omx_gethandle( |
&component_handle_, reinterpret_cast<OMX_STRING>(component.get()), |
this, &omx_accelerator_callbacks); |
@@ -265,7 +266,7 @@ bool OmxVideoDecodeAccelerator::CreateComponent() { |
OMX_BUFFERHEADERTYPE* buffer; |
result = OMX_UseBuffer(component_handle_, &buffer, output_port_, |
NULL, 0, reinterpret_cast<OMX_U8*>(0x1)); |
- RETURN_ON_OMX_FAILURE(result, "OMX_UseBuffer failed with: " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_UseBuffer failed", |
VIDEODECODERERROR_INVALIDINPUT, false); |
buffer->pAppPrivate = NULL; |
buffer->nTimeStamp = -1; |
@@ -284,7 +285,7 @@ void OmxVideoDecodeAccelerator::Decode( |
RETURN_ON_FAILURE(current_state_change_ == NO_TRANSITION && |
(client_state_ == OMX_StateIdle || |
client_state_ == OMX_StateExecuting), |
- "Call to Decode() during invalid state or transition:" |
+ "Call to Decode() during invalid state or transition: " |
<< current_state_change_ << ", " << client_state_, |
VIDEODECODERERROR_UNSUPPORTED,); |
@@ -315,8 +316,7 @@ void OmxVideoDecodeAccelerator::Decode( |
// Give this buffer to OMX. |
OMX_ERRORTYPE result = OMX_EmptyThisBuffer(component_handle_, omx_buffer); |
- RETURN_ON_OMX_FAILURE(result, |
- "OMX_EmptyThisBuffer() failed with result " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_EmptyThisBuffer() failed", |
VIDEODECODERERROR_INVALIDINPUT,); |
input_buffers_at_component_++; |
@@ -365,8 +365,7 @@ void OmxVideoDecodeAccelerator::ReusePictureBuffer(int32 picture_buffer_id) { |
++output_buffers_at_component_; |
OMX_ERRORTYPE result = |
OMX_FillThisBuffer(component_handle_, output_picture.omx_buffer_header); |
- RETURN_ON_OMX_FAILURE(result, |
- "OMX_FillThisBuffer() failed with result " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_FillThisBuffer() failed", |
VIDEODECODERERROR_INVALIDINPUT,); |
} |
@@ -384,8 +383,7 @@ void OmxVideoDecodeAccelerator::Flush() { |
omx_buffer->nFlags |= OMX_BUFFERFLAG_EOS; |
omx_buffer->nTimeStamp = -2; |
OMX_ERRORTYPE result = OMX_EmptyThisBuffer(component_handle_, omx_buffer); |
- RETURN_ON_OMX_FAILURE(result, |
- "OMX_EmptyThisBuffer() failed with result " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_EmptyThisBuffer() failed", |
VIDEODECODERERROR_INVALIDINPUT,); |
input_buffers_at_component_++; |
} |
@@ -426,16 +424,33 @@ void OmxVideoDecodeAccelerator::Reset() { |
void OmxVideoDecodeAccelerator::Destroy() { |
DCHECK_EQ(message_loop_, MessageLoop::current()); |
+ if (current_state_change_ == ERRORING) |
+ return; |
DCHECK_EQ(current_state_change_, NO_TRANSITION); |
+ // If we were never initializeed there's no teardown to do. |
+ if (client_state_ == OMX_StateMax) |
+ return; |
+ // If we can already call OMX_FreeHandle, simply do so. |
+ if (client_state_ == OMX_StateInvalid || client_state_ == OMX_StateLoaded) { |
+ ShutdownComponent(); |
+ return; |
+ } |
DCHECK_EQ(client_state_, OMX_StateExecuting); |
current_state_change_ = DESTROYING; |
+ client_ = NULL; |
BeginTransitionToState(OMX_StateIdle); |
+ BusyLoopInDestroying(); |
} |
void OmxVideoDecodeAccelerator::BeginTransitionToState( |
OMX_STATETYPE new_state) { |
DCHECK_EQ(message_loop_, MessageLoop::current()); |
DCHECK_NE(current_state_change_, NO_TRANSITION); |
+ DCHECK_NE(current_state_change_, ERRORING); |
+ if (current_state_change_ == NO_TRANSITION || |
+ current_state_change_ == ERRORING) { |
+ return; |
+ } |
OMX_ERRORTYPE result = OMX_SendCommand( |
component_handle_, OMX_CommandStateSet, new_state, 0); |
RETURN_ON_OMX_FAILURE(result, "SendCommand(OMX_CommandStateSet) failed", |
@@ -461,13 +476,13 @@ void OmxVideoDecodeAccelerator::OnReachedExecutingInInitializing() { |
it != fake_output_buffers_.end(); ++it) { |
OMX_BUFFERHEADERTYPE* buffer = *it; |
OMX_ERRORTYPE result = OMX_FillThisBuffer(component_handle_, buffer); |
- RETURN_ON_OMX_FAILURE(result, |
- "OMX_FillThisBuffer() failed with: " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_FillThisBuffer()", |
VIDEODECODERERROR_INVALIDINPUT,); |
++output_buffers_at_component_; |
} |
- client_->NotifyInitializeDone(); |
+ if (client_) |
+ client_->NotifyInitializeDone(); |
} |
void OmxVideoDecodeAccelerator::OnReachedPauseInFlushing() { |
@@ -482,7 +497,8 @@ void OmxVideoDecodeAccelerator::OnReachedExecutingInFlushing() { |
DCHECK(saw_eos_during_flush_); |
saw_eos_during_flush_ = false; |
current_state_change_ = NO_TRANSITION; |
- client_->NotifyFlushDone(); |
+ if (client_) |
+ client_->NotifyFlushDone(); |
} |
void OmxVideoDecodeAccelerator::OnReachedPauseInResetting() { |
@@ -495,7 +511,25 @@ void OmxVideoDecodeAccelerator::OnReachedExecutingInResetting() { |
DCHECK_EQ(client_state_, OMX_StatePause); |
client_state_ = OMX_StateExecuting; |
current_state_change_ = NO_TRANSITION; |
- client_->NotifyResetDone(); |
+ if (client_) |
+ client_->NotifyResetDone(); |
+} |
+ |
+// Alert: HORROR ahead! OMX shutdown is an asynchronous dance but our clients |
+// enjoy the fire-and-forget nature of a synchronous Destroy() call that |
+// ensures no further callbacks are made. Since the interface between OMX |
+// callbacks and this class is a MessageLoop, we need to ensure the loop |
+// outlives the shutdown dance, even during process shutdown. We do this by |
+// repeatedly enqueuing a no-op task until shutdown is complete, since |
+// MessageLoop's shutdown drains pending tasks. |
+void OmxVideoDecodeAccelerator::BusyLoopInDestroying() { |
+ if (!component_handle_) return; |
+ // Can't use PostDelayedTask here because MessageLoop doesn't drain delayed |
+ // tasks. Instead we sleep for 5ms. Really. |
+ base::PlatformThread::Sleep(5); |
+ message_loop_->PostTask( |
+ FROM_HERE, NewRunnableMethod( |
+ this, &OmxVideoDecodeAccelerator::BusyLoopInDestroying)); |
} |
void OmxVideoDecodeAccelerator::OnReachedIdleInDestroying() { |
@@ -514,15 +548,8 @@ void OmxVideoDecodeAccelerator::OnReachedIdleInDestroying() { |
FreeInputBuffers(); |
if (!output_buffers_at_component_) |
FreeOutputBuffers(); |
-} |
-void OmxVideoDecodeAccelerator::ShutdownComponent() { |
- OMX_ERRORTYPE result = omx_free_handle(component_handle_); |
- if (result != OMX_ErrorNone) |
- LOG(ERROR) << "OMX_FreeHandle() error. Error code: " << result; |
- component_handle_ = NULL; |
- omx_deinit(); |
- client_state_ = OMX_StateMax; |
+ BusyLoopInDestroying(); |
} |
void OmxVideoDecodeAccelerator::OnReachedLoadedInDestroying() { |
@@ -530,7 +557,6 @@ void OmxVideoDecodeAccelerator::OnReachedLoadedInDestroying() { |
client_state_ = OMX_StateLoaded; |
current_state_change_ = NO_TRANSITION; |
ShutdownComponent(); |
- client_->NotifyDestroyDone(); |
} |
void OmxVideoDecodeAccelerator::OnReachedInvalidInErroring() { |
@@ -538,17 +564,31 @@ void OmxVideoDecodeAccelerator::OnReachedInvalidInErroring() { |
ShutdownComponent(); |
} |
+void OmxVideoDecodeAccelerator::ShutdownComponent() { |
+ OMX_ERRORTYPE result = omx_free_handle(component_handle_); |
+ if (result != OMX_ErrorNone) |
+ LOG(ERROR) << "OMX_FreeHandle() error. Error code: " << result; |
+ component_handle_ = NULL; |
+ client_state_ = OMX_StateMax; |
+ // This Release() call must happen *after* any access to |*this| because it |
+ // might result in |this| being deleted. |
+ Release(); // Since OMX no longer has |this| to call back to. |
+ omx_deinit(); |
+} |
+ |
void OmxVideoDecodeAccelerator::StopOnError( |
media::VideoDecodeAccelerator::Error error) { |
DCHECK_EQ(message_loop_, MessageLoop::current()); |
- current_state_change_ = ERRORING; |
- client_->NotifyError(error); |
+ if (client_) |
+ client_->NotifyError(error); |
+ client_ = NULL; |
if (client_state_ == OMX_StateInvalid || client_state_ == OMX_StateMax) |
return; |
BeginTransitionToState(OMX_StateInvalid); |
+ current_state_change_ = ERRORING; |
} |
bool OmxVideoDecodeAccelerator::AllocateInputBuffers() { |
@@ -589,7 +629,7 @@ bool OmxVideoDecodeAccelerator::AllocateOutputBuffers() { |
OMX_ERRORTYPE result = OMX_UseEGLImage( |
component_handle_, omx_buffer, output_port_, &gles_buffer, |
it->second.egl_image); |
- RETURN_ON_OMX_FAILURE(result, "OMX_UseEGLImage failed with: " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_UseEGLImage", |
VIDEODECODERERROR_MEMFAILURE, false); |
// Here we set a garbage bitstream buffer id, and then overwrite it before |
// passing to PictureReady. |
@@ -610,10 +650,9 @@ void OmxVideoDecodeAccelerator::FreeInputBuffers() { |
omx_buffer = free_input_buffers_.front(); |
free_input_buffers_.pop(); |
result = OMX_FreeBuffer(component_handle_, input_port_, omx_buffer); |
- RETURN_ON_OMX_FAILURE(result, "OMX_FreeBuffer failed with: " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_FreeBuffer", |
VIDEODECODERERROR_INVALIDINPUT,); |
} |
- VLOG(1) << "Input buffers freed."; |
} |
void OmxVideoDecodeAccelerator::FreeOutputBuffers() { |
@@ -627,11 +666,12 @@ void OmxVideoDecodeAccelerator::FreeOutputBuffers() { |
CHECK(omx_buffer); |
delete reinterpret_cast<media::Picture*>(omx_buffer->pAppPrivate); |
result = OMX_FreeBuffer(component_handle_, output_port_, omx_buffer); |
- RETURN_ON_OMX_FAILURE(result, "OMX_FreeBuffer failed with: " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_FreeBuffer", |
VIDEODECODERERROR_INVALIDINPUT,); |
texture2eglImage_translator.DestroyEglImage(egl_display_, |
it->second.egl_image); |
- client_->DismissPictureBuffer(it->first); |
+ if (client_) |
+ client_->DismissPictureBuffer(it->first); |
} |
pictures_.clear(); |
} |
@@ -643,7 +683,7 @@ void OmxVideoDecodeAccelerator::OnOutputPortDisabled() { |
port_format.nPortIndex = output_port_; |
OMX_ERRORTYPE result = OMX_GetParameter( |
component_handle_, OMX_IndexParamPortDefinition, &port_format); |
- RETURN_ON_OMX_FAILURE(result, "OMX_GetParameter failed with: " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_GetParameter", |
VIDEODECODERERROR_UNSUPPORTED,); |
DCHECK_EQ(port_format.nBufferCountMin, kNumPictureBuffers); |
@@ -655,10 +695,12 @@ void OmxVideoDecodeAccelerator::OnOutputPortDisabled() { |
// ProvidePictureBuffers() will trigger AssignGLESBuffers, which ultimately |
// assigns the textures to the component and re-enables the port. |
const OMX_VIDEO_PORTDEFINITIONTYPE& vformat = port_format.format.video; |
- client_->ProvidePictureBuffers( |
- kNumPictureBuffers, |
- gfx::Size(vformat.nFrameWidth, vformat.nFrameHeight), |
- PICTUREBUFFER_MEMORYTYPE_GL_TEXTURE); |
+ if (client_) { |
+ client_->ProvidePictureBuffers( |
+ kNumPictureBuffers, |
+ gfx::Size(vformat.nFrameWidth, vformat.nFrameHeight), |
+ PICTUREBUFFER_MEMORYTYPE_GL_TEXTURE); |
+ } |
} |
void OmxVideoDecodeAccelerator::OnOutputPortEnabled() { |
@@ -679,8 +721,7 @@ void OmxVideoDecodeAccelerator::OnOutputPortEnabled() { |
omx_buffer->nOutputPortIndex = output_port_; |
++output_buffers_at_component_; |
OMX_ERRORTYPE result = OMX_FillThisBuffer(component_handle_, omx_buffer); |
- RETURN_ON_OMX_FAILURE(result, |
- "OMX_FillThisBuffer() failed with result " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_FillThisBuffer() failed", |
VIDEODECODERERROR_INSUFFICIENT_BUFFERS,); |
} |
} |
@@ -695,7 +736,7 @@ void OmxVideoDecodeAccelerator::FillBufferDoneTask( |
CHECK_EQ(fake_output_buffers_.erase(buffer), 1U); |
OMX_ERRORTYPE result = |
OMX_FreeBuffer(component_handle_, output_port_, buffer); |
- RETURN_ON_OMX_FAILURE(result, "OMX_FreeBuffer failed with: " << result, |
+ RETURN_ON_OMX_FAILURE(result, "OMX_FreeBuffer failed", |
VIDEODECODERERROR_INVALIDINPUT,); |
return; |
} |
@@ -721,7 +762,8 @@ void OmxVideoDecodeAccelerator::FillBufferDoneTask( |
reinterpret_cast<media::Picture*>(buffer->pAppPrivate); |
// See Decode() for an explanation of this abuse of nTimeStamp. |
picture->set_bitstream_buffer_id(buffer->nTimeStamp); |
- client_->PictureReady(*picture); |
+ if (client_) |
+ client_->PictureReady(*picture); |
} |
void OmxVideoDecodeAccelerator::EmptyBufferDoneTask( |
@@ -739,7 +781,8 @@ void OmxVideoDecodeAccelerator::EmptyBufferDoneTask( |
reinterpret_cast<SharedMemoryAndId*>(buffer->pAppPrivate); |
DCHECK(input_buffer_details); |
buffer->pAppPrivate = NULL; |
- client_->NotifyEndOfBitstreamBuffer(input_buffer_details->second); |
+ if (client_) |
+ client_->NotifyEndOfBitstreamBuffer(input_buffer_details->second); |
delete input_buffer_details; |
} |
@@ -790,6 +833,14 @@ void OmxVideoDecodeAccelerator::DispatchStateReached(OMX_STATETYPE reached) { |
default: |
NOTREACHED() << "Unexpected state in DESTROYING: " << reached; |
} |
+ case ERRORING: |
+ switch (reached) { |
+ case OMX_StateInvalid: |
+ OnReachedInvalidInErroring(); |
+ return; |
+ default: |
+ NOTREACHED() << "Unexpected state in ERRORING: " << reached; |
+ } |
default: |
NOTREACHED() << "Unexpected state in " << current_state_change_ |
<< ": " << reached; |
@@ -801,23 +852,19 @@ void OmxVideoDecodeAccelerator::EventHandlerCompleteTask(OMX_EVENTTYPE event, |
OMX_U32 data2) { |
DCHECK_EQ(message_loop_, MessageLoop::current()); |
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<OMX_COMMANDTYPE>(data1); |
- switch (cmd) { |
+ case OMX_EventCmdComplete: |
+ switch (data1) { |
case OMX_CommandPortDisable: |
DCHECK_EQ(data2, output_port_); |
OnOutputPortDisabled(); |
- break; |
+ return; |
case OMX_CommandPortEnable: |
DCHECK_EQ(data2, output_port_); |
OnOutputPortEnabled(); |
- break; |
+ return; |
case OMX_CommandStateSet: |
DispatchStateReached(static_cast<OMX_STATETYPE>(data2)); |
- break; |
+ return; |
case OMX_CommandFlush: |
DCHECK(current_state_change_ == FLUSHING || |
current_state_change_ == RESETTING || |
@@ -828,16 +875,19 @@ void OmxVideoDecodeAccelerator::EventHandlerCompleteTask(OMX_EVENTTYPE event, |
OutputPortFlushDone(); |
else |
NOTREACHED() << "Unexpected port flushed: " << data2; |
- break; |
+ return; |
default: |
RETURN_ON_FAILURE(false, "Unknown command completed: " << data1, |
VIDEODECODERERROR_HARDWARE,); |
} |
- break; |
- } |
+ return; |
case OMX_EventError: |
- RETURN_ON_FAILURE(false, "EventError: " << data1, |
- VIDEODECODERERROR_HARDWARE,); |
+ if (current_state_change_ != DESTROYING && |
+ current_state_change_ != ERRORING) { |
+ RETURN_ON_FAILURE(false, "EventError: 0x" << std::hex << data1, |
+ VIDEODECODERERROR_HARDWARE,); |
+ } |
+ return; |
case OMX_EventPortSettingsChanged: |
if (data2 == OMX_IndexParamPortDefinition) { |
DCHECK_EQ(data1, output_port_); |
@@ -853,7 +903,7 @@ void OmxVideoDecodeAccelerator::EventHandlerCompleteTask(OMX_EVENTTYPE event, |
<< data1 << ", " << data2, |
VIDEODECODERERROR_HARDWARE,); |
} |
- break; |
+ return; |
case OMX_EventBufferFlag: |
if (data1 == output_port_) { |
DCHECK_EQ(current_state_change_, FLUSHING); |
@@ -864,7 +914,7 @@ void OmxVideoDecodeAccelerator::EventHandlerCompleteTask(OMX_EVENTTYPE event, |
<< data1 << ", " << data2, |
VIDEODECODERERROR_HARDWARE,); |
} |
- break; |
+ return; |
default: |
RETURN_ON_FAILURE(false, "Unexpected unhandled event: " << event, |
VIDEODECODERERROR_HARDWARE,); |
@@ -883,10 +933,9 @@ OMX_ERRORTYPE OmxVideoDecodeAccelerator::EventHandler(OMX_HANDLETYPE component, |
static_cast<OmxVideoDecodeAccelerator*>(priv_data); |
DCHECK_EQ(component, decoder->component_handle_); |
decoder->message_loop_->PostTask( |
- FROM_HERE, |
- NewRunnableMethod(decoder, |
- &OmxVideoDecodeAccelerator::EventHandlerCompleteTask, |
- event, data1, data2)); |
+ FROM_HERE, NewRunnableMethod( |
+ decoder, &OmxVideoDecodeAccelerator::EventHandlerCompleteTask, |
+ event, data1, data2)); |
return OMX_ErrorNone; |
} |
@@ -926,6 +975,10 @@ OMX_ERRORTYPE OmxVideoDecodeAccelerator::FillBufferCallback( |
bool OmxVideoDecodeAccelerator::CanFillBuffer() { |
DCHECK_EQ(message_loop_, MessageLoop::current()); |
+ if (current_state_change_ == DESTROYING || |
+ current_state_change_ == ERRORING) { |
+ return false; |
+ } |
return client_state_ == OMX_StateIdle || |
client_state_ == OMX_StateExecuting || |
client_state_ == OMX_StatePause; |
@@ -936,9 +989,7 @@ bool OmxVideoDecodeAccelerator::SendCommandToPort( |
DCHECK_EQ(message_loop_, MessageLoop::current()); |
OMX_ERRORTYPE result = OMX_SendCommand(component_handle_, |
cmd, port_index, 0); |
- RETURN_ON_OMX_FAILURE(result, "SendCommand() failed" << cmd << ":" << result, |
+ RETURN_ON_OMX_FAILURE(result, "SendCommand() failed" << cmd, |
VIDEODECODERERROR_INVALIDINPUT, false); |
return true; |
} |
- |
-DISABLE_RUNNABLE_METHOD_REFCOUNT(OmxVideoDecodeAccelerator); |