Index: content/common/gpu/media/vt_video_decode_accelerator.cc |
diff --git a/content/common/gpu/media/vt_video_decode_accelerator.cc b/content/common/gpu/media/vt_video_decode_accelerator.cc |
index 8fb0b834bbfc0649e20ebf486f5a40964ea7035b..47bf742ca7ec813e2f9d9aeaae7ef2184bfd771b 100644 |
--- a/content/common/gpu/media/vt_video_decode_accelerator.cc |
+++ b/content/common/gpu/media/vt_video_decode_accelerator.cc |
@@ -7,6 +7,7 @@ |
#include <OpenGL/gl.h> |
#include "base/bind.h" |
+#include "base/callback_helpers.h" |
#include "base/command_line.h" |
#include "base/sys_byteorder.h" |
#include "base/thread_task_runner_handle.h" |
@@ -21,6 +22,12 @@ using content_common_gpu_media::InitializeStubs; |
using content_common_gpu_media::IsVtInitialized; |
using content_common_gpu_media::StubPathMap; |
+#define NOTIFY_STATUS(name, status) \ |
+ do { \ |
+ LOG(ERROR) << name << " failed with status " << status; \ |
+ NotifyError(PLATFORM_FAILURE); \ |
+ } while (0) |
+ |
namespace content { |
// Size of NALU length headers in AVCC/MPEG-4 format (can be 1, 2, or 4). |
@@ -69,6 +76,7 @@ VTVideoDecodeAccelerator::PendingAction::~PendingAction() { |
VTVideoDecodeAccelerator::VTVideoDecodeAccelerator(CGLContextObj cgl_context) |
: cgl_context_(cgl_context), |
client_(NULL), |
+ has_error_(false), |
format_(NULL), |
session_(NULL), |
gpu_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
@@ -115,21 +123,26 @@ bool VTVideoDecodeAccelerator::Initialize( |
return true; |
} |
-// TODO(sandersd): Proper error reporting instead of CHECKs. |
-void VTVideoDecodeAccelerator::ConfigureDecoder( |
+bool VTVideoDecodeAccelerator::ConfigureDecoder( |
const std::vector<const uint8_t*>& nalu_data_ptrs, |
const std::vector<size_t>& nalu_data_sizes) { |
DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
+ |
// Construct a new format description from the parameter sets. |
// TODO(sandersd): Replace this with custom code to support OS X < 10.9. |
format_.reset(); |
- CHECK(!CMVideoFormatDescriptionCreateFromH264ParameterSets( |
+ OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets( |
kCFAllocatorDefault, |
nalu_data_ptrs.size(), // parameter_set_count |
&nalu_data_ptrs.front(), // ¶meter_set_pointers |
&nalu_data_sizes.front(), // ¶meter_set_sizes |
kNALUHeaderLength, // nal_unit_header_length |
- format_.InitializeInto())); |
+ format_.InitializeInto()); |
+ if (status) { |
+ NOTIFY_STATUS("CMVideoFormatDescriptionCreateFromH264ParameterSets()", |
+ status); |
+ return false; |
+ } |
CMVideoDimensions coded_dimensions = |
CMVideoFormatDescriptionGetDimensions(format_); |
@@ -170,13 +183,17 @@ void VTVideoDecodeAccelerator::ConfigureDecoder( |
// TODO(sandersd): Check if the session is already compatible. |
session_.reset(); |
- CHECK(!VTDecompressionSessionCreate( |
+ status = VTDecompressionSessionCreate( |
kCFAllocatorDefault, |
format_, // video_format_description |
decoder_config, // video_decoder_specification |
image_config, // destination_image_buffer_attributes |
&callback_, // output_callback |
- session_.InitializeInto())); |
+ session_.InitializeInto()); |
+ if (status) { |
+ NOTIFY_STATUS("VTDecompressionSessionCreate()", status); |
+ return false; |
+ } |
// If the size has changed, trigger a request for new picture buffers. |
// TODO(sandersd): Move to SendPictures(), and use this just as a hint for an |
@@ -189,26 +206,45 @@ void VTVideoDecodeAccelerator::ConfigureDecoder( |
weak_this_factory_.GetWeakPtr(), |
coded_size_));; |
} |
+ |
+ return true; |
} |
void VTVideoDecodeAccelerator::Decode(const media::BitstreamBuffer& bitstream) { |
DCHECK(CalledOnValidThread()); |
- CHECK_GE(bitstream.id(), 0) << "Negative bitstream_id"; |
+ // Not actually a requirement of the VDA API, but we're lazy and use negative |
+ // values as flags internally. Revisit that if this actually happens. |
+ if (bitstream.id() < 0) { |
+ LOG(ERROR) << "Negative bitstream ID"; |
+ NotifyError(INVALID_ARGUMENT); |
+ client_->NotifyEndOfBitstreamBuffer(bitstream.id()); |
+ return; |
+ } |
pending_bitstream_ids_.push(bitstream.id()); |
decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
&VTVideoDecodeAccelerator::DecodeTask, base::Unretained(this), |
bitstream)); |
} |
-// TODO(sandersd): Proper error reporting instead of CHECKs. |
void VTVideoDecodeAccelerator::DecodeTask( |
- const media::BitstreamBuffer bitstream) { |
+ const media::BitstreamBuffer& bitstream) { |
DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
+ // Once we have a bitstream buffer, we must either decode it or drop it. |
+ // This construct ensures that the buffer is always dropped unless we call |
+ // drop_bitstream.Release(). |
+ base::ScopedClosureRunner drop_bitstream(base::Bind( |
+ &VTVideoDecodeAccelerator::DropBitstream, base::Unretained(this), |
+ bitstream.id())); |
+ |
// Map the bitstream buffer. |
base::SharedMemory memory(bitstream.handle(), true); |
size_t size = bitstream.size(); |
- CHECK(memory.Map(size)); |
+ if (!memory.Map(size)) { |
+ LOG(ERROR) << "Failed to map bitstream buffer"; |
+ NotifyError(PLATFORM_FAILURE); |
+ return; |
+ } |
const uint8_t* buf = static_cast<uint8_t*>(memory.memory()); |
// NALUs are stored with Annex B format in the bitstream buffer (start codes), |
@@ -227,7 +263,11 @@ void VTVideoDecodeAccelerator::DecodeTask( |
media::H264Parser::Result result = parser_.AdvanceToNextNALU(&nalu); |
if (result == media::H264Parser::kEOStream) |
break; |
- CHECK_EQ(result, media::H264Parser::kOk); |
+ if (result != media::H264Parser::kOk) { |
+ LOG(ERROR) << "Failed to find H.264 NALU"; |
+ NotifyError(PLATFORM_FAILURE); |
+ return; |
+ } |
// TODO(sandersd): Check that these are only at the start. |
if (nalu.nal_unit_type == media::H264NALU::kSPS || |
nalu.nal_unit_type == media::H264NALU::kPPS || |
@@ -243,23 +283,21 @@ void VTVideoDecodeAccelerator::DecodeTask( |
// 2. Initialize VideoToolbox. |
// TODO(sandersd): Reinitialize when there are new parameter sets. |
- if (!session_) |
- ConfigureDecoder(config_nalu_data_ptrs, config_nalu_data_sizes); |
+ if (!session_) { |
+ // If configuring fails, ConfigureDecoder() already called NotifyError(). |
+ if (!ConfigureDecoder(config_nalu_data_ptrs, config_nalu_data_sizes)) |
+ return; |
+ } |
// If there are no non-configuration units, immediately return an empty |
// (ie. dropped) frame. It is an error to create a MemoryBlock with zero |
// size. |
- if (!data_size) { |
- gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
- &VTVideoDecodeAccelerator::OutputTask, |
- weak_this_factory_.GetWeakPtr(), |
- DecodedFrame(bitstream.id(), NULL))); |
+ if (!data_size) |
return; |
- } |
// 3. Allocate a memory-backed CMBlockBuffer for the translated data. |
base::ScopedCFTypeRef<CMBlockBufferRef> data; |
- CHECK(!CMBlockBufferCreateWithMemoryBlock( |
+ OSStatus status = CMBlockBufferCreateWithMemoryBlock( |
kCFAllocatorDefault, |
NULL, // &memory_block |
data_size, // block_length |
@@ -268,23 +306,35 @@ void VTVideoDecodeAccelerator::DecodeTask( |
0, // offset_to_data |
data_size, // data_length |
0, // flags |
- data.InitializeInto())); |
+ data.InitializeInto()); |
+ if (status) { |
+ NOTIFY_STATUS("CMBlockBufferCreateWithMemoryBlock()", status); |
+ return; |
+ } |
// 4. Copy NALU data, inserting length headers. |
size_t offset = 0; |
for (size_t i = 0; i < nalus.size(); i++) { |
media::H264NALU& nalu = nalus[i]; |
uint32_t header = base::HostToNet32(static_cast<uint32_t>(nalu.size)); |
- CHECK(!CMBlockBufferReplaceDataBytes( |
- &header, data, offset, kNALUHeaderLength)); |
+ status = CMBlockBufferReplaceDataBytes( |
+ &header, data, offset, kNALUHeaderLength); |
+ if (status) { |
+ NOTIFY_STATUS("CMBlockBufferReplaceDataBytes()", status); |
+ return; |
+ } |
offset += kNALUHeaderLength; |
- CHECK(!CMBlockBufferReplaceDataBytes(nalu.data, data, offset, nalu.size)); |
+ status = CMBlockBufferReplaceDataBytes(nalu.data, data, offset, nalu.size); |
+ if (status) { |
+ NOTIFY_STATUS("CMBlockBufferReplaceDataBytes()", status); |
+ return; |
+ } |
offset += nalu.size; |
} |
// 5. Package the data for VideoToolbox and request decoding. |
base::ScopedCFTypeRef<CMSampleBufferRef> frame; |
- CHECK(!CMSampleBufferCreate( |
+ status = CMSampleBufferCreate( |
kCFAllocatorDefault, |
data, // data_buffer |
true, // data_ready |
@@ -296,7 +346,11 @@ void VTVideoDecodeAccelerator::DecodeTask( |
NULL, // &sample_timing_array |
0, // num_sample_size_entries |
NULL, // &sample_size_array |
- frame.InitializeInto())); |
+ frame.InitializeInto()); |
+ if (status) { |
+ NOTIFY_STATUS("CMSampleBufferCreate()", status); |
+ return; |
+ } |
// Asynchronous Decompression allows for parallel submission of frames |
// (without it, DecodeFrame() does not return until the frame has been |
@@ -306,23 +360,37 @@ void VTVideoDecodeAccelerator::DecodeTask( |
kVTDecodeFrame_EnableAsynchronousDecompression; |
intptr_t bitstream_id = bitstream.id(); |
- CHECK(!VTDecompressionSessionDecodeFrame( |
+ status = VTDecompressionSessionDecodeFrame( |
session_, |
frame, // sample_buffer |
decode_flags, // decode_flags |
reinterpret_cast<void*>(bitstream_id), // source_frame_refcon |
- NULL)); // &info_flags_out |
+ NULL); // &info_flags_out |
+ if (status) { |
+ NOTIFY_STATUS("VTDecompressionSessionDecodeFrame()", status); |
+ return; |
+ } |
+ |
+ // Now that the bitstream is decoding, don't drop it. |
+ (void)drop_bitstream.Release(); |
} |
// This method may be called on any VideoToolbox thread. |
-// TODO(sandersd): Proper error reporting instead of CHECKs. |
void VTVideoDecodeAccelerator::Output( |
int32_t bitstream_id, |
OSStatus status, |
CVImageBufferRef image_buffer) { |
- CHECK(!status); |
- CHECK_EQ(CFGetTypeID(image_buffer), CVPixelBufferGetTypeID()); |
- CFRetain(image_buffer); |
+ if (status) { |
+ // TODO(sandersd): Handle dropped frames. |
+ NOTIFY_STATUS("Decoding", status); |
+ image_buffer = NULL; |
+ } else if (CFGetTypeID(image_buffer) != CVPixelBufferGetTypeID()) { |
+ LOG(ERROR) << "Decoded frame is not a CVPixelBuffer"; |
+ NotifyError(PLATFORM_FAILURE); |
+ image_buffer = NULL; |
+ } else { |
+ CFRetain(image_buffer); |
+ } |
gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
&VTVideoDecodeAccelerator::OutputTask, |
weak_this_factory_.GetWeakPtr(), |
@@ -348,7 +416,7 @@ void VTVideoDecodeAccelerator::AssignPictureBuffers( |
DCHECK(CalledOnValidThread()); |
for (size_t i = 0; i < pictures.size(); i++) { |
- CHECK(!texture_ids_.count(pictures[i].id())); |
+ DCHECK(!texture_ids_.count(pictures[i].id())); |
available_picture_ids_.push(pictures[i].id()); |
texture_ids_[pictures[i].id()] = pictures[i].texture_id(); |
} |
@@ -399,7 +467,8 @@ void VTVideoDecodeAccelerator::ProcessDecodedFrames() { |
while (!decoded_frames_.empty()) { |
if (pending_actions_.empty()) { |
// No pending actions; send frames normally. |
- SendPictures(pending_bitstream_ids_.back()); |
+ if (!has_error_) |
+ SendPictures(pending_bitstream_ids_.back()); |
return; |
} |
@@ -408,11 +477,15 @@ void VTVideoDecodeAccelerator::ProcessDecodedFrames() { |
switch (pending_actions_.front().action) { |
case ACTION_FLUSH: |
// Send frames normally. |
+ if (has_error_) |
+ return; |
last_sent_bitstream_id = SendPictures(next_action_bitstream_id); |
break; |
case ACTION_RESET: |
// Drop decoded frames. |
+ if (has_error_) |
+ return; |
while (!decoded_frames_.empty() && |
last_sent_bitstream_id != next_action_bitstream_id) { |
last_sent_bitstream_id = decoded_frames_.front().bitstream_id; |
@@ -454,22 +527,28 @@ int32_t VTVideoDecodeAccelerator::SendPictures(int32_t up_to_bitstream_id) { |
DCHECK(CalledOnValidThread()); |
DCHECK(!decoded_frames_.empty()); |
+ // TODO(sandersd): Store the actual last sent bitstream ID? |
+ int32_t last_sent_bitstream_id = -1; |
+ |
if (available_picture_ids_.empty()) |
- return -1; |
+ return last_sent_bitstream_id; |
gfx::ScopedCGLSetCurrentContext scoped_set_current_context(cgl_context_); |
glEnable(GL_TEXTURE_RECTANGLE_ARB); |
- |
- int32_t last_sent_bitstream_id = -1; |
while (!available_picture_ids_.empty() && |
!decoded_frames_.empty() && |
- last_sent_bitstream_id != up_to_bitstream_id) { |
- DecodedFrame frame = decoded_frames_.front(); |
- decoded_frames_.pop(); |
+ last_sent_bitstream_id != up_to_bitstream_id && |
+ !has_error_) { |
+ // We don't pop |frame| until it is consumed, which won't happen if an |
+ // error occurs. Conveniently, this also removes some refcounting. |
+ const DecodedFrame& frame = decoded_frames_.front(); |
DCHECK_EQ(pending_bitstream_ids_.front(), frame.bitstream_id); |
- pending_bitstream_ids_.pop(); |
+ |
+ // Likewise, |picture_id| won't be popped if |image_buffer| is NULL or an |
+ // error occurs. |
+ // TODO(sandersd): Don't block waiting for a |picture_id| when |
+ // |image_buffer| is NULL. |
int32_t picture_id = available_picture_ids_.front(); |
- available_picture_ids_.pop(); |
CVImageBufferRef image_buffer = frame.image_buffer.get(); |
if (image_buffer) { |
@@ -478,7 +557,7 @@ int32_t VTVideoDecodeAccelerator::SendPictures(int32_t up_to_bitstream_id) { |
// TODO(sandersd): Find out why this sometimes fails due to no GL context. |
gfx::ScopedTextureBinder |
texture_binder(GL_TEXTURE_RECTANGLE_ARB, texture_ids_[picture_id]); |
- CHECK(!CGLTexImageIOSurface2D( |
+ CGLError status = CGLTexImageIOSurface2D( |
cgl_context_, // ctx |
GL_TEXTURE_RECTANGLE_ARB, // target |
GL_RGB, // internal_format |
@@ -487,24 +566,32 @@ int32_t VTVideoDecodeAccelerator::SendPictures(int32_t up_to_bitstream_id) { |
GL_YCBCR_422_APPLE, // format |
GL_UNSIGNED_SHORT_8_8_APPLE, // type |
surface, // io_surface |
- 0)); // plane |
+ 0); // plane |
+ if (status != kCGLNoError) { |
+ NOTIFY_STATUS("CGLTexImageIOSurface2D()", status); |
+ break; |
+ } |
picture_bindings_[picture_id] = frame.image_buffer; |
client_->PictureReady(media::Picture( |
picture_id, frame.bitstream_id, gfx::Rect(texture_size_))); |
+ available_picture_ids_.pop(); |
} |
client_->NotifyEndOfBitstreamBuffer(frame.bitstream_id); |
last_sent_bitstream_id = frame.bitstream_id; |
+ decoded_frames_.pop(); |
+ pending_bitstream_ids_.pop(); |
} |
- |
glDisable(GL_TEXTURE_RECTANGLE_ARB); |
return last_sent_bitstream_id; |
} |
void VTVideoDecodeAccelerator::FlushTask() { |
DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
- CHECK(!VTDecompressionSessionFinishDelayedFrames(session_)); |
+ OSStatus status = VTDecompressionSessionFinishDelayedFrames(session_); |
+ if (status) |
+ NOTIFY_STATUS("VTDecompressionSessionFinishDelayedFrames()", status); |
} |
void VTVideoDecodeAccelerator::QueueAction(Action action) { |
@@ -525,6 +612,26 @@ void VTVideoDecodeAccelerator::QueueAction(Action action) { |
} |
} |
+void VTVideoDecodeAccelerator::NotifyError(Error error) { |
+ if (!CalledOnValidThread()) { |
+ gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
+ &VTVideoDecodeAccelerator::NotifyError, |
+ weak_this_factory_.GetWeakPtr(), |
+ error)); |
+ return; |
+ } |
+ has_error_ = true; |
+ client_->NotifyError(error); |
+} |
+ |
+void VTVideoDecodeAccelerator::DropBitstream(int32_t bitstream_id) { |
+ DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
+ gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
+ &VTVideoDecodeAccelerator::OutputTask, |
+ weak_this_factory_.GetWeakPtr(), |
+ DecodedFrame(bitstream_id, NULL))); |
+} |
+ |
void VTVideoDecodeAccelerator::Flush() { |
DCHECK(CalledOnValidThread()); |
QueueAction(ACTION_FLUSH); |