| 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() {
|
|
|