Chromium Code Reviews| 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..c83e2ef90c0589b9957a78f1f793a9db2e7fcda1 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,7 @@ 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. |
| + pending_bitstream_ids_.push(bitstream.id()); |
| decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
| &VTVideoDecodeAccelerator::DecodeTask, base::Unretained(this), |
| bitstream)); |
| @@ -236,6 +245,14 @@ void VTVideoDecodeAccelerator::DecodeTask( |
| if (!session_) |
| ConfigureDecoder(config_nalu_data_ptrs, config_nalu_data_sizes); |
| + if (!data_size) { |
|
Pawel Osciak
2014/09/25 01:27:29
Could you explain? If there is no data we want to
sandersd (OOO until July 31)
2014/09/25 19:18:52
Done. (And yes.)
|
| + 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 +328,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 +349,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 +362,173 @@ 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) { |
|
Pawel Osciak
2014/09/25 01:27:29
Wouldn't you want to do <= instead of == here?
sandersd (OOO until July 31)
2014/09/25 19:18:53
No, CompleteActions() is called every time a pendi
Pawel Osciak
2014/09/26 04:25:07
Ah, lack of doc for the method confused me.
And I
sandersd (OOO until July 31)
2014/09/26 04:56:41
Acknowledged.
|
| + CompleteAction(pending_actions_.front().action); |
| + pending_actions_.pop(); |
| + } |
| +} |
| + |
| +void VTVideoDecodeAccelerator::ProcessDecodedFrames() { |
| + DCHECK(CalledOnValidThread()); |
| + |
| + while (true) { |
|
Pawel Osciak
2014/09/25 01:27:29
Maybe just while (!decoded_frames_.empty()) instea
sandersd (OOO until July 31)
2014/09/25 19:18:52
Done.
|
| + if (decoded_frames_.empty()) |
| + return; |
| + |
| + if (pending_actions_.empty()) { |
| + // No pending actions; send frames normally. |
| + if (!available_picture_ids_.empty()) |
| + SendPictures(pending_bitstream_ids_.back()); |
| + return; |
| + } else if (pending_actions_.front().action == ACTION_FLUSH) { |
|
Pawel Osciak
2014/09/25 01:27:28
I think it could be more readable if you:
if (pen
sandersd (OOO until July 31)
2014/09/25 19:18:52
I like this, thanks!
|
| + // Flushing; send frames normally, then complete any actions. |
| + if (!available_picture_ids_.empty()) { |
|
Pawel Osciak
2014/09/25 01:27:28
You make this check in SendPictures anyway. I'd re
sandersd (OOO until July 31)
2014/09/25 19:18:52
This check served a few purposes:
- It avoids ne
|
| + int32_t last_sent_bitstream_id = |
| + SendPictures(pending_actions_.front().bitstream_id); |
| + CompleteActions(last_sent_bitstream_id); |
| + // Loop again, as there may be work to do for a new pending action. |
| + continue; |
| + } |
| + return; |
|
Pawel Osciak
2014/09/25 01:27:29
I'd just remove continue and return and let the ex
sandersd (OOO until July 31)
2014/09/25 19:18:53
Acknowledged.
Pawel Osciak
2014/09/26 04:25:07
But not implemented?
sandersd (OOO until July 31)
2014/09/26 04:56:41
I thought the switch version made this clear enoug
Pawel Osciak
2014/09/26 05:20:30
My point is, continue is the default action. So yo
sandersd (OOO until July 31)
2014/09/26 06:56:02
Done.
|
| + } else if (pending_actions_.front().action == ACTION_RESET) { |
| + // Reseting; drop a decoded frame and then complete any actions. |
|
Pawel Osciak
2014/09/25 01:27:28
One frame? Not drop frames until the id for the ac
sandersd (OOO until July 31)
2014/09/25 19:18:53
It's the same, but I've switched it around.
|
| + int32_t bitstream_id = decoded_frames_.front().bitstream_id; |
|
Pawel Osciak
2014/09/25 01:27:28
Why not do things until action's bitstream id? Thi
sandersd (OOO until July 31)
2014/09/25 19:18:53
Done.
|
| + decoded_frames_.pop(); |
| + DCHECK_EQ(pending_bitstream_ids_.front(), bitstream_id); |
| + pending_bitstream_ids_.pop(); |
| + client_->NotifyEndOfBitstreamBuffer(bitstream_id); |
| + CompleteActions(bitstream_id); |
| + // Loop again, as there may be more frames to drop or work to do for a new |
| + // pending action. |
| + continue; |
|
Pawel Osciak
2014/09/25 01:27:29
Not needed.
sandersd (OOO until July 31)
2014/09/25 19:18:52
Acknowledged.
Pawel Osciak
2014/09/26 04:25:07
But not removed?
sandersd (OOO until July 31)
2014/09/26 04:56:41
See above.
|
| + } else { |
| + // Destroying; drop a decoded frame, then destroy if ready. |
|
Pawel Osciak
2014/09/25 01:27:28
Why only one not up to action id? That would be si
sandersd (OOO until July 31)
2014/09/25 19:18:52
Done.
|
| + DCHECK_EQ(pending_actions_.front().action, ACTION_DESTROY); |
| + int32_t bitstream_id = decoded_frames_.front().bitstream_id; |
| + decoded_frames_.pop(); |
| + if (pending_actions_.front().bitstream_id == bitstream_id) { |
| + CompleteAction(ACTION_DESTROY); |
| + return; |
| + } |
| + // Loop again, as there may be more frames to drop. |
| + continue; |
| + } |
| + |
| + NOTREACHED(); |
|
Pawel Osciak
2014/09/25 01:27:29
This would go to default: clause.
sandersd (OOO until July 31)
2014/09/25 19:18:52
Not quite, I want to be sure that every path at th
Pawel Osciak
2014/09/26 04:25:07
You can do that by removing continues and putting
sandersd (OOO until July 31)
2014/09/26 04:56:41
I don't believe that is the same. The compiler can
Pawel Osciak
2014/09/26 05:20:30
That's exactly the point I'm trying to make. Nobod
sandersd (OOO until July 31)
2014/09/26 06:56:02
Done.
|
| + } |
| +} |
| + |
| +int32_t VTVideoDecodeAccelerator::SendPictures(int32_t up_to_bitstream_id) { |
| + DCHECK(CalledOnValidThread()); |
| + DCHECK(!decoded_frames_.empty()); |
| + DCHECK(!available_picture_ids_.empty()); |
| + int32_t last_sent_bitstream_id = -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(); |
| 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) { |
|
Pawel Osciak
2014/09/25 01:27:28
What happens if there is no image_buffer? Don't we
sandersd (OOO until July 31)
2014/09/25 19:18:52
This happens in if !data_size, or if the decoder d
|
| + IOSurfaceRef surface = CVPixelBufferGetIOSurface(image_buffer); |
| + |
| + // TODO(sandersd): Find out why this somtimes fails due to no GL context. |
|
Pawel Osciak
2014/09/25 01:27:28
s/somtimes/sometimes/
How do you know it failed?
sandersd (OOO until July 31)
2014/09/25 19:18:53
It fails when the CGLTexImageIOSurface2D() check f
Pawel Osciak
2014/09/26 04:25:07
Well, I'd really like to express my strong prefere
sandersd (OOO until July 31)
2014/09/26 04:56:41
Understood, replacing all the CHECKs is the next i
|
| + 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; |
| + if (frame.bitstream_id == up_to_bitstream_id) |
|
Pawel Osciak
2014/09/25 01:27:29
Make this a part of while() condition?
sandersd (OOO until July 31)
2014/09/25 19:18:53
Done.
|
| + break; |
| } |
| 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()) { |
| + CompleteAction(action); |
|
Pawel Osciak
2014/09/25 01:27:29
Could you just let ProcessDecodedFrames() to this
sandersd (OOO until July 31)
2014/09/25 19:18:52
ProcessDecodedFrames() doesn't currently handle an
|
| + } else { |
| + pending_actions_.push(PendingAction(action, pending_bitstream_ids_.back())); |
| + ProcessDecodedFrames(); |
| + if (!pending_actions_.empty() && !pending_bitstream_ids_.empty()) { |
| + decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
| + &VTVideoDecodeAccelerator::FlushTask, base::Unretained(this))); |
|
Pawel Osciak
2014/09/25 01:27:28
This requires an explanation...
sandersd (OOO until July 31)
2014/09/25 19:18:53
I think the condition was just wrong, as there wil
|
| + } |
| + } |
| } |
| 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()); |
|
Pawel Osciak
2014/09/25 01:27:29
If you stopped the decoder thread here, you wouldn
sandersd (OOO until July 31)
2014/09/25 19:18:52
This is indeed possible, as long as the FlushTask
Pawel Osciak
2014/09/26 04:25:07
I don't understand the part about FlushTask... Que
sandersd (OOO until July 31)
2014/09/26 04:56:41
Queued to the decoder thread message loop, before
Pawel Osciak
2014/09/26 05:20:30
If you decoder_thread_.Stop() from Destroy(), it w
sandersd (OOO until July 31)
2014/09/26 06:56:02
This makes sense, but I'd still rather hold off. I
Pawel Osciak
2014/09/26 15:30:49
The idea was to do away with ACTION_DESTROY. It wo
sandersd (OOO until July 31)
2014/09/26 17:28:51
Acknowledged.
|
| - // 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()); |
|
Pawel Osciak
2014/09/25 01:27:29
You shouldn't have to do this on Destroy().
sandersd (OOO until July 31)
2014/09/25 19:18:53
It seems I do, GpuVideoDecoder DCHECKS that there
Pawel Osciak
2014/09/26 04:25:07
Acknowledged.
|
| + pending_bitstream_ids_.pop(); |
| + } |
| + QueueAction(ACTION_DESTROY); |
|
Pawel Osciak
2014/09/25 01:27:29
Do we actually need this? You dropped everything a
sandersd (OOO until July 31)
2014/09/25 19:18:52
We tell the client we are done with the buffers, b
Pawel Osciak
2014/09/26 04:25:07
How do you guarantee that during that client_ will
sandersd (OOO until July 31)
2014/09/26 04:56:41
During destroy, ProcessDecodedFrames() specificall
Pawel Osciak
2014/09/26 05:20:30
Could you tell me how this is achieved please?
sandersd (OOO until July 31)
2014/09/26 06:56:02
Because there is a queued ACTION_DESTROY, it alway
|
| } |
| bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { |