Chromium Code Reviews| Index: content/common/gpu/media/mac_video_decode_accelerator.mm |
| diff --git a/content/common/gpu/media/mac_video_decode_accelerator.mm b/content/common/gpu/media/mac_video_decode_accelerator.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..8d572c8af1cd7954fa4051b09f9edd8733f13c72 |
| --- /dev/null |
| +++ b/content/common/gpu/media/mac_video_decode_accelerator.mm |
| @@ -0,0 +1,310 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/common/gpu/media/mac_video_decode_accelerator.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/file_path.h" |
| +#import "base/mac/foundation_util.h" |
| +#import "base/memory/ref_counted_memory.h" |
| +#import "base/message_loop.h" |
| +#include "base/location.h" |
| +#include "base/native_library.h" |
| +#include "ui/surface/io_surface_support_mac.h" |
| +#include "ui/gfx/video_decode_acceleration_support_mac.h" |
| + |
| +namespace { |
| + |
| +enum { kNumPictureBuffers = 4 }; |
| + |
| +class ScopedContextSetter { |
| + public: |
| + ScopedContextSetter(CGLContextObj context) |
| + : old_context_(NULL), |
| + did_succeed_(false) { |
| + old_context_ = CGLGetCurrentContext(); |
| + did_succeed_ = CGLSetCurrentContext(context) == kCGLNoError; |
| + } |
| + |
| + ~ScopedContextSetter() { |
| + if (did_succeed_) |
| + CGLSetCurrentContext(old_context_); |
| + } |
| + |
| + bool did_succeed() const { |
| + return did_succeed_; |
| + } |
| + |
| + private: |
| + CGLContextObj old_context_; |
| + bool did_succeed_; |
| +}; |
| + |
| +} // namespace |
| + |
| +static bool BindImageToTexture(CGLContextObj context, |
| + CVImageBufferRef image, |
| + uint32 texture_id) { |
| + ScopedContextSetter scoped_context_setter(context); |
| + if (!scoped_context_setter.did_succeed()) { |
| + DLOG(ERROR) << "Unable to set OpenGL context."; |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
s/DLOG(ERROR)/DVLOG(1)/
here and everywhere below
sail
2012/05/29 03:45:01
Done.
|
| + return false; |
| + } |
| + |
| + IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); |
| + DCHECK(io_surface_support); |
| + |
| + CFTypeRef io_surface = |
| + io_surface_support->CVPixelBufferGetIOSurface(image); |
| + if (!io_surface) { |
| + DLOG(ERROR) << "Unable to get IOSurface for CVPixelBuffer."; |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
I'm afraid I wasn't clear before. The problem wit
sail
2012/05/29 03:45:01
Done.
To mark the MacVDA instance unusable I'm ju
sail
2012/05/29 23:22:57
Actually, I changed this to check for a client_ in
|
| + return false; |
| + } |
| + |
| + glEnable(GL_TEXTURE_RECTANGLE_ARB); |
| + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_id); |
| + if (io_surface_support->CGLTexImageIOSurface2D( |
| + context, |
| + GL_TEXTURE_RECTANGLE_ARB, |
| + GL_RGB, |
| + io_surface_support->IOSurfaceGetWidth(io_surface), |
| + io_surface_support->IOSurfaceGetHeight(io_surface), |
| + GL_YCBCR_422_APPLE, |
| + GL_UNSIGNED_SHORT_8_8_APPLE, |
| + io_surface, |
| + 0) != kCGLNoError) { |
| + DLOG(ERROR) << "Failed to bind image to texture."; |
| + return false; |
| + } |
| + return glGetError() == GL_NO_ERROR; |
| +} |
| + |
| +MacVideoDecodeAccelerator::MacVideoDecodeAccelerator( |
| + media::VideoDecodeAccelerator::Client* client) |
| + : client_(client), |
| + cgl_context_(NULL), |
| + nalu_len_field_size_(0), |
| + did_request_pictures_(false) { |
| +} |
| + |
| +void MacVideoDecodeAccelerator::SetGLContext(void* gl_context) { |
| + DCHECK(CalledOnValidThread()); |
| + cgl_context_ = static_cast<CGLContextObj>(gl_context); |
| +} |
| + |
| +bool MacVideoDecodeAccelerator::SetConfigInfo( |
| + uint32_t frame_width, |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
This config info includes width & height, and the
sail
2012/05/29 03:45:01
So taking a step back.
All the Mac video API clie
Ami GONE FROM CHROMIUM
2012/05/30 00:29:58
Presumably you need to request different textures,
sail
2012/05/30 20:13:46
Sounds good. I've filed bug 130352 to track this.
|
| + uint32_t frame_height, |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
Is your plan to land this first and then immediate
sail
2012/05/29 03:45:01
I'm planning to land the various CLs one after ano
|
| + const std::vector<uint8_t>& avc_data) { |
| + DCHECK(CalledOnValidThread()); |
| + frame_size_ = gfx::Size(frame_width, frame_height); |
| + nalu_len_field_size_ = (avc_data[4] & 0x03) + 1; |
| + |
| + DCHECK(!vda_support_.get()); |
| + vda_support_ = new gfx::VideoDecodeAccelerationSupport(); |
| + return vda_support_->Create(frame_size_.width(), frame_size_.height(), |
| + kCVPixelFormatType_422YpCbCr8, &avc_data.front(), avc_data.size()) == |
| + gfx::VideoDecodeAccelerationSupport::SUCCESS; |
| +} |
| + |
| +bool MacVideoDecodeAccelerator::Initialize(media::VideoCodecProfile profile) { |
| + DCHECK(CalledOnValidThread()); |
| + |
| + IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); |
| + if (!io_surface_support) |
| + return false; |
| + |
| + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( |
| + &MacVideoDecodeAccelerator::NotifyInitializeDone, this)); |
| + return true; |
| +} |
| + |
| +void MacVideoDecodeAccelerator::Decode( |
| + const media::BitstreamBuffer& bitstream_buffer) { |
| + DCHECK(CalledOnValidThread()); |
| + base::SharedMemory memory(bitstream_buffer.handle(), true); |
| + if (!memory.Map(bitstream_buffer.size())) { |
| + LOG(ERROR) << "Failed to SharedMemory::Map()."; |
| + if (client_) |
| + client_->NotifyError(UNREADABLE_INPUT); |
| + return; |
| + } |
| + |
| + size_t buffer_size = bitstream_buffer.size(); |
| + if (buffer_size < nalu_len_field_size_ + 1) { |
| + LOG(ERROR) << "Bitstream contains invalid data."; |
| + if (client_) |
| + client_->NotifyError(INVALID_ARGUMENT); |
| + return; |
| + } |
| + |
| + // The decoder can only handle slice types 1-5. |
| + const uint8_t* buffer = static_cast<const uint8_t*>(memory.memory()); |
| + uint8_t nalu_type = buffer[nalu_len_field_size_] & 0x1f; |
| + if (nalu_type < 1 || nalu_type > 5) { |
| + if (client_) |
| + client_->NotifyEndOfBitstreamBuffer(bitstream_buffer.id()); |
| + return; |
| + } |
| + |
| + // Keep a ref counted copy of the buffer. |
| + std::vector<uint8_t> vbuffer(buffer, buffer + buffer_size); |
| + scoped_refptr<base::RefCountedBytes> bytes( |
| + base::RefCountedBytes::TakeVector(&vbuffer)); |
| + |
| + // Store the buffer size at the beginning of the buffer as the decoder |
| + // expects. |
| + size_t frame_buffer_size = buffer_size - nalu_len_field_size_; |
| + DCHECK(nalu_len_field_size_ <= 4); |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
DCHECK_LE
sail
2012/05/29 03:45:01
Done.
|
| + uint64_t max_frame_buffer_size = (1llu << (nalu_len_field_size_ * 8)) - 1; |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
s/uint64/int64/
s/llu/LL/
sail
2012/05/29 03:45:01
Done.
|
| + if (frame_buffer_size > max_frame_buffer_size) { |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
FWIW, writing this as
if (frame_buffer_size >> (n
sail
2012/05/29 03:45:01
Unfortunately that overflows when nalu_len_field_s
Ami GONE FROM CHROMIUM
2012/05/30 00:29:58
WDYM "overflows"?
sail
2012/05/30 20:13:46
Sorry, not sure what the correct term is. If I do:
|
| + LOG(ERROR) << "Bitstream is too large."; |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
s/is/buffer is/
sail
2012/05/29 03:45:01
Done.
|
| + if (client_) |
| + client_->NotifyError(INVALID_ARGUMENT); |
| + return; |
| + } |
| + for (size_t i = 0; i < nalu_len_field_size_; ++i) { |
| + size_t shift = nalu_len_field_size_ * 8 - (i + 1) * 8; |
| + bytes->data()[i] = (frame_buffer_size >> shift) & 0xff; |
| + } |
| + |
| + if (vda_support_) { |
| + vda_support_->Decode(bytes->front(), bytes->size(), |
| + base::Bind(&MacVideoDecodeAccelerator::OnFrameReady, |
| + this, bitstream_buffer.id(), bytes)); |
| + } |
| + |
| + if (!did_request_pictures_) { |
| + did_request_pictures_ = true; |
| + if (client_) |
| + client_->ProvidePictureBuffers(kNumPictureBuffers, frame_size_); |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
Again needs to be out-of-line to avoid re-entrancy
sail
2012/05/29 03:45:01
Done.
|
| + } |
| +} |
| + |
| +void MacVideoDecodeAccelerator::AssignPictureBuffers( |
| + const std::vector<media::PictureBuffer>& buffers) { |
| + DCHECK(CalledOnValidThread()); |
| + available_pictures_.insert( |
| + available_pictures_.end(), buffers.begin(), buffers.end()); |
| + SendImages(); |
| +} |
| + |
| +void MacVideoDecodeAccelerator::ReusePictureBuffer(int32 picture_buffer_id) { |
| + DCHECK(CalledOnValidThread()); |
| + std::map<int32, UsedPictureInfo>::iterator it = |
| + used_pictures_.find(picture_buffer_id); |
| + if (it == used_pictures_.end()) { |
| + LOG(ERROR) << "Missing picture buffer id: " << picture_buffer_id; |
| + if (client_) |
| + client_->NotifyError(INVALID_ARGUMENT); |
| + return; |
| + } |
| + UsedPictureInfo info = it->second; |
| + used_pictures_.erase(it); |
| + available_pictures_.push_back(info.picture_buffer); |
| + SendImages(); |
| +} |
| + |
| +void MacVideoDecodeAccelerator::Flush() { |
| + DCHECK(CalledOnValidThread()); |
| + if (vda_support_) |
| + vda_support_->Flush(true); |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
I'm surprised this can be done synchronously.
In
sail
2012/05/29 03:45:01
Even though the flush is synchronous the decoded i
|
| + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( |
| + &MacVideoDecodeAccelerator::NotifyFlushDone, this)); |
| +} |
| + |
| +void MacVideoDecodeAccelerator::Reset() { |
| + DCHECK(CalledOnValidThread()); |
| + if (vda_support_) |
| + vda_support_->Flush(false); |
| + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( |
| + &MacVideoDecodeAccelerator::NotifyResetDone, this)); |
| +} |
| + |
| +void MacVideoDecodeAccelerator::Destroy() { |
| + DCHECK(CalledOnValidThread()); |
| + if (vda_support_) { |
| + vda_support_->Destroy(); |
| + vda_support_ = NULL; |
| + } |
| + client_ = NULL; |
| +} |
| + |
| +MacVideoDecodeAccelerator::~MacVideoDecodeAccelerator() { |
| + DCHECK(CalledOnValidThread()); |
| + Destroy(); |
| +} |
| + |
| +void MacVideoDecodeAccelerator::OnFrameReady( |
| + int32 bitstream_buffer_id, |
| + scoped_refptr<base::RefCountedBytes> bytes, |
| + CVImageBufferRef image, |
| + int status) { |
| + DCHECK(CalledOnValidThread()); |
| + if (image) { |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
Is this failing not a cause for notifying the clie
sail
2012/05/29 03:45:01
The image can be NULL if Reset() is called.
I adde
|
| + DecodedImageInfo info; |
| + info.image.reset(image, base::mac::RETAIN); |
| + info.bitstream_buffer_id = bitstream_buffer_id; |
| + decoded_images_.push_back(info); |
| + SendImages(); |
| + } |
| + if (client_) |
| + client_->NotifyEndOfBitstreamBuffer(bitstream_buffer_id); |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
Add
// TODO(sail): this assumes Decode() is handed
sail
2012/05/29 03:45:01
Done.
|
| +} |
| + |
| +void MacVideoDecodeAccelerator::SendImages() { |
| + if (!client_) |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
In this case do you not want to clear the queues?
sail
2012/05/29 03:45:01
I added code to clear the decoded image queue in D
|
| + return; |
| + |
| + while (available_pictures_.size() && decoded_images_.size()) { |
| + DecodedImageInfo info = decoded_images_.front(); |
| + decoded_images_.pop_front(); |
| + media::PictureBuffer picture_buffer = available_pictures_.front(); |
| + available_pictures_.pop_front(); |
| + |
| + if (!BindImageToTexture(cgl_context_, info.image, |
| + picture_buffer.texture_id())) { |
| + LOG(ERROR) << "Error binding image to texture."; |
| + client_->NotifyError(PLATFORM_FAILURE); |
| + return; |
| + } |
| + |
| + used_pictures_.insert(std::pair<int, UsedPictureInfo>( |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
what's the problem if you replace
std::pair<int, U
sail
2012/05/29 03:45:01
Done.
|
| + picture_buffer.id(), UsedPictureInfo(picture_buffer, info.image))); |
| + client_->PictureReady( |
| + media::Picture(picture_buffer.id(), info.bitstream_buffer_id)); |
| + } |
| +} |
| + |
| +void MacVideoDecodeAccelerator::NotifyInitializeDone() { |
| + if (client_) |
| + client_->NotifyInitializeDone(); |
| +} |
| + |
| +void MacVideoDecodeAccelerator::NotifyFlushDone() { |
| + if (client_) |
| + client_->NotifyFlushDone(); |
|
Ami GONE FROM CHROMIUM
2012/05/24 21:30:30
In that case in what sense is the flush "done"???
sail
2012/05/29 03:45:01
So it looks like there are a few solutions:
#1
Ami GONE FROM CHROMIUM
2012/05/30 00:29:58
This is the only option that's compatible with the
sail
2012/05/30 20:13:46
Sounds good. I've filed bug 130357 to track this w
|
| +} |
| + |
| +void MacVideoDecodeAccelerator::NotifyResetDone() { |
| + decoded_images_.clear(); |
| + if (client_) |
| + client_->NotifyResetDone(); |
| +} |
| + |
| +MacVideoDecodeAccelerator::UsedPictureInfo::UsedPictureInfo( |
| + const media::PictureBuffer& pic, |
| + const base::mac::ScopedCFTypeRef<CVImageBufferRef>& image) |
| + : picture_buffer(pic), |
| + image(image, base::mac::RETAIN) { |
| +} |
| + |
| +MacVideoDecodeAccelerator::UsedPictureInfo::~UsedPictureInfo() { |
| +} |
| + |
| +MacVideoDecodeAccelerator::DecodedImageInfo::DecodedImageInfo() { |
| +} |
| + |
| +MacVideoDecodeAccelerator::DecodedImageInfo::~DecodedImageInfo() { |
| +} |