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 37d96b7bbeae0a38c85a10a1afc77002a3076800..8fb0b834bbfc0649e20ebf486f5a40964ea7035b 100644 |
--- a/content/common/gpu/media/vt_video_decode_accelerator.cc |
+++ b/content/common/gpu/media/vt_video_decode_accelerator.cc |
@@ -40,7 +40,6 @@ static void OutputThunk( |
CVImageBufferRef image_buffer, |
CMTime presentation_time_stamp, |
CMTime presentation_duration) { |
- // TODO(sandersd): Implement flush-before-delete to guarantee validity. |
VTVideoDecodeAccelerator* vda = |
reinterpret_cast<VTVideoDecodeAccelerator*>(decompression_output_refcon); |
int32_t bitstream_id = reinterpret_cast<intptr_t>(source_frame_refcon); |
@@ -57,6 +56,16 @@ VTVideoDecodeAccelerator::DecodedFrame::DecodedFrame( |
VTVideoDecodeAccelerator::DecodedFrame::~DecodedFrame() { |
} |
+VTVideoDecodeAccelerator::PendingAction::PendingAction( |
+ Action action, |
+ int32_t bitstream_id) |
+ : action(action), |
+ bitstream_id(bitstream_id) { |
+} |
+ |
+VTVideoDecodeAccelerator::PendingAction::~PendingAction() { |
+} |
+ |
VTVideoDecodeAccelerator::VTVideoDecodeAccelerator(CGLContextObj cgl_context) |
: cgl_context_(cgl_context), |
client_(NULL), |
@@ -160,7 +169,6 @@ void VTVideoDecodeAccelerator::ConfigureDecoder( |
image_config, kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); |
// TODO(sandersd): Check if the session is already compatible. |
- // TODO(sandersd): Flush. |
session_.reset(); |
CHECK(!VTDecompressionSessionCreate( |
kCFAllocatorDefault, |
@@ -171,6 +179,8 @@ void VTVideoDecodeAccelerator::ConfigureDecoder( |
session_.InitializeInto())); |
// 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 |
+ // upcoming size change. |
gfx::Size new_coded_size(coded_dimensions.width, coded_dimensions.height); |
if (coded_size_ != new_coded_size) { |
coded_size_ = new_coded_size; |
@@ -183,8 +193,8 @@ void VTVideoDecodeAccelerator::ConfigureDecoder( |
void VTVideoDecodeAccelerator::Decode(const media::BitstreamBuffer& bitstream) { |
DCHECK(CalledOnValidThread()); |
- // TODO(sandersd): Test what happens if bitstream buffers are passed to VT out |
- // of order. |
+ CHECK_GE(bitstream.id(), 0) << "Negative bitstream_id"; |
+ pending_bitstream_ids_.push(bitstream.id()); |
decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
&VTVideoDecodeAccelerator::DecodeTask, base::Unretained(this), |
bitstream)); |
@@ -236,6 +246,17 @@ void VTVideoDecodeAccelerator::DecodeTask( |
if (!session_) |
ConfigureDecoder(config_nalu_data_ptrs, config_nalu_data_sizes); |
+ // 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))); |
+ return; |
+ } |
+ |
// 3. Allocate a memory-backed CMBlockBuffer for the translated data. |
base::ScopedCFTypeRef<CMBlockBufferRef> data; |
CHECK(!CMBlockBufferCreateWithMemoryBlock( |
@@ -311,7 +332,7 @@ void VTVideoDecodeAccelerator::Output( |
void VTVideoDecodeAccelerator::OutputTask(DecodedFrame frame) { |
DCHECK(CalledOnValidThread()); |
decoded_frames_.push(frame); |
- SendPictures(); |
+ ProcessDecodedFrames(); |
} |
void VTVideoDecodeAccelerator::SizeChangedTask(gfx::Size coded_size) { |
@@ -332,10 +353,11 @@ void VTVideoDecodeAccelerator::AssignPictureBuffers( |
texture_ids_[pictures[i].id()] = pictures[i].texture_id(); |
} |
- // Pictures are not marked as uncleared until this method returns. They will |
- // become broken if they are used before that happens. |
+ // Pictures are not marked as uncleared until after this method returns, and |
+ // they will be broken if they are used before that happens. So, schedule |
+ // future work after that happens. |
gpu_task_runner_->PostTask(FROM_HERE, base::Bind( |
- &VTVideoDecodeAccelerator::SendPictures, |
+ &VTVideoDecodeAccelerator::ProcessDecodedFrames, |
weak_this_factory_.GetWeakPtr())); |
} |
@@ -344,61 +366,186 @@ void VTVideoDecodeAccelerator::ReusePictureBuffer(int32_t picture_id) { |
DCHECK_EQ(CFGetRetainCount(picture_bindings_[picture_id]), 1); |
picture_bindings_.erase(picture_id); |
available_picture_ids_.push(picture_id); |
- SendPictures(); |
+ ProcessDecodedFrames(); |
} |
-// TODO(sandersd): Proper error reporting instead of CHECKs. |
-void VTVideoDecodeAccelerator::SendPictures() { |
+void VTVideoDecodeAccelerator::CompleteAction(Action action) { |
DCHECK(CalledOnValidThread()); |
- if (available_picture_ids_.empty() || decoded_frames_.empty()) |
- return; |
+ switch (action) { |
+ case ACTION_FLUSH: |
+ client_->NotifyFlushDone(); |
+ break; |
+ case ACTION_RESET: |
+ client_->NotifyResetDone(); |
+ break; |
+ case ACTION_DESTROY: |
+ delete this; |
+ break; |
+ } |
+} |
+ |
+void VTVideoDecodeAccelerator::CompleteActions(int32_t bitstream_id) { |
+ DCHECK(CalledOnValidThread()); |
+ while (!pending_actions_.empty() && |
+ pending_actions_.front().bitstream_id == bitstream_id) { |
+ CompleteAction(pending_actions_.front().action); |
+ pending_actions_.pop(); |
+ } |
+} |
+ |
+void VTVideoDecodeAccelerator::ProcessDecodedFrames() { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ while (!decoded_frames_.empty()) { |
+ if (pending_actions_.empty()) { |
+ // No pending actions; send frames normally. |
+ SendPictures(pending_bitstream_ids_.back()); |
+ return; |
+ } |
+ |
+ int32_t next_action_bitstream_id = pending_actions_.front().bitstream_id; |
+ int32_t last_sent_bitstream_id = -1; |
+ switch (pending_actions_.front().action) { |
+ case ACTION_FLUSH: |
+ // Send frames normally. |
+ last_sent_bitstream_id = SendPictures(next_action_bitstream_id); |
+ break; |
+ |
+ case ACTION_RESET: |
+ // Drop decoded frames. |
+ while (!decoded_frames_.empty() && |
+ last_sent_bitstream_id != next_action_bitstream_id) { |
+ last_sent_bitstream_id = decoded_frames_.front().bitstream_id; |
+ decoded_frames_.pop(); |
+ DCHECK_EQ(pending_bitstream_ids_.front(), last_sent_bitstream_id); |
+ pending_bitstream_ids_.pop(); |
+ client_->NotifyEndOfBitstreamBuffer(last_sent_bitstream_id); |
+ } |
+ break; |
+ |
+ case ACTION_DESTROY: |
+ // Drop decoded frames, without bookkeeping. |
+ while (!decoded_frames_.empty()) { |
+ last_sent_bitstream_id = decoded_frames_.front().bitstream_id; |
+ decoded_frames_.pop(); |
+ } |
+ |
+ // Handle completing the action specially, as it is important not to |
+ // access |this| after calling CompleteAction(). |
+ if (last_sent_bitstream_id == next_action_bitstream_id) |
+ CompleteAction(ACTION_DESTROY); |
+ |
+ // Either |this| was deleted or no more progress can be made. |
+ return; |
+ } |
+ |
+ // If we ran out of buffers (or pictures), no more progress can be made |
+ // until more frames are decoded. |
+ if (last_sent_bitstream_id != next_action_bitstream_id) |
+ return; |
+ |
+ // Complete all actions pending for this |bitstream_id|, then loop to see |
+ // if progress can be made on the next action. |
+ CompleteActions(next_action_bitstream_id); |
+ } |
+} |
+ |
+int32_t VTVideoDecodeAccelerator::SendPictures(int32_t up_to_bitstream_id) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK(!decoded_frames_.empty()); |
+ |
+ if (available_picture_ids_.empty()) |
+ return -1; |
gfx::ScopedCGLSetCurrentContext scoped_set_current_context(cgl_context_); |
glEnable(GL_TEXTURE_RECTANGLE_ARB); |
- while (!available_picture_ids_.empty() && !decoded_frames_.empty()) { |
- int32_t picture_id = available_picture_ids_.front(); |
- available_picture_ids_.pop(); |
+ 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(); |
- IOSurfaceRef surface = CVPixelBufferGetIOSurface(frame.image_buffer); |
- |
- gfx::ScopedTextureBinder |
- texture_binder(GL_TEXTURE_RECTANGLE_ARB, texture_ids_[picture_id]); |
- CHECK(!CGLTexImageIOSurface2D( |
- cgl_context_, // ctx |
- GL_TEXTURE_RECTANGLE_ARB, // target |
- GL_RGB, // internal_format |
- texture_size_.width(), // width |
- texture_size_.height(), // height |
- GL_YCBCR_422_APPLE, // format |
- GL_UNSIGNED_SHORT_8_8_APPLE, // type |
- surface, // io_surface |
- 0)); // plane |
- |
- picture_bindings_[picture_id] = frame.image_buffer; |
- client_->PictureReady(media::Picture( |
- picture_id, frame.bitstream_id, gfx::Rect(texture_size_))); |
+ DCHECK_EQ(pending_bitstream_ids_.front(), frame.bitstream_id); |
+ pending_bitstream_ids_.pop(); |
+ int32_t picture_id = available_picture_ids_.front(); |
+ available_picture_ids_.pop(); |
+ |
+ CVImageBufferRef image_buffer = frame.image_buffer.get(); |
+ if (image_buffer) { |
+ IOSurfaceRef surface = CVPixelBufferGetIOSurface(image_buffer); |
+ |
+ // 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( |
+ cgl_context_, // ctx |
+ GL_TEXTURE_RECTANGLE_ARB, // target |
+ GL_RGB, // internal_format |
+ texture_size_.width(), // width |
+ texture_size_.height(), // height |
+ GL_YCBCR_422_APPLE, // format |
+ GL_UNSIGNED_SHORT_8_8_APPLE, // type |
+ surface, // io_surface |
+ 0)); // plane |
+ |
+ picture_bindings_[picture_id] = frame.image_buffer; |
+ client_->PictureReady(media::Picture( |
+ picture_id, frame.bitstream_id, gfx::Rect(texture_size_))); |
+ } |
+ |
client_->NotifyEndOfBitstreamBuffer(frame.bitstream_id); |
+ last_sent_bitstream_id = frame.bitstream_id; |
} |
glDisable(GL_TEXTURE_RECTANGLE_ARB); |
+ return last_sent_bitstream_id; |
+} |
+ |
+void VTVideoDecodeAccelerator::FlushTask() { |
+ DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
+ CHECK(!VTDecompressionSessionFinishDelayedFrames(session_)); |
+} |
+ |
+void VTVideoDecodeAccelerator::QueueAction(Action action) { |
+ DCHECK(CalledOnValidThread()); |
+ if (pending_bitstream_ids_.empty()) { |
+ // If there are no pending frames, all actions complete immediately. |
+ CompleteAction(action); |
+ } else { |
+ // Otherwise, queue the action. |
+ pending_actions_.push(PendingAction(action, pending_bitstream_ids_.back())); |
+ |
+ // Request a flush to make sure the action will eventually complete. |
+ decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
+ &VTVideoDecodeAccelerator::FlushTask, base::Unretained(this))); |
+ |
+ // See if we can make progress now that there is a new pending action. |
+ ProcessDecodedFrames(); |
+ } |
} |
void VTVideoDecodeAccelerator::Flush() { |
DCHECK(CalledOnValidThread()); |
- // TODO(sandersd): Trigger flush, sending frames. |
+ QueueAction(ACTION_FLUSH); |
} |
void VTVideoDecodeAccelerator::Reset() { |
DCHECK(CalledOnValidThread()); |
- // TODO(sandersd): Trigger flush, discarding frames. |
+ QueueAction(ACTION_RESET); |
} |
void VTVideoDecodeAccelerator::Destroy() { |
DCHECK(CalledOnValidThread()); |
- // TODO(sandersd): Trigger flush, discarding frames, and wait for them. |
- delete this; |
+ // Drop any other pending actions. |
+ while (!pending_actions_.empty()) |
+ pending_actions_.pop(); |
+ // Return all bitstream buffers. |
+ while (!pending_bitstream_ids_.empty()) { |
+ client_->NotifyEndOfBitstreamBuffer(pending_bitstream_ids_.front()); |
+ pending_bitstream_ids_.pop(); |
+ } |
+ QueueAction(ACTION_DESTROY); |
} |
bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { |