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 59251d20465c7249396029833cf575007046efbd..7745e6dfb999aacc259f50033d856d2aded7d28a 100644 |
--- a/content/common/gpu/media/vt_video_decode_accelerator.cc |
+++ b/content/common/gpu/media/vt_video_decode_accelerator.cc |
@@ -2,15 +2,51 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
+#include <CoreVideo/CoreVideo.h> |
+#include <OpenGL/CGLIOSurface.h> |
+ |
+#include "base/bind.h" |
+#include "base/thread_task_runner_handle.h" |
#include "content/common/gpu/media/vt_video_decode_accelerator.h" |
+#include "media/filters/h264_parser.h" |
+ |
+using content_common_gpu_media::kModuleVt; |
+using content_common_gpu_media::InitializeStubs; |
+using content_common_gpu_media::IsVtInitialized; |
+using content_common_gpu_media::StubPathMap; |
namespace content { |
+// Size of length headers prepended to NALUs in MPEG-4 framing. (1, 2, or 4.) |
+static const int kNALUHeaderLength = 4; |
+ |
+// Route decoded frame callbacks back into the VTVideoDecodeAccelerator. |
+static void OutputThunk( |
+ void* decompression_output_refcon, |
+ void* source_frame_refcon, |
+ OSStatus status, |
+ VTDecodeInfoFlags info_flags, |
+ CVImageBufferRef image_buffer, |
+ CMTime presentation_time_stamp, |
+ CMTime presentation_duration) { |
+ VTVideoDecodeAccelerator* vda = |
+ reinterpret_cast<VTVideoDecodeAccelerator*>(decompression_output_refcon); |
+ int32_t* bitstream_id_ptr = reinterpret_cast<int32_t*>(source_frame_refcon); |
+ int32_t bitstream_id = *bitstream_id_ptr; |
+ delete bitstream_id_ptr; |
+ CFRetain(image_buffer); |
+ vda->Output(bitstream_id, status, info_flags, image_buffer); |
+} |
+ |
VTVideoDecodeAccelerator::VTVideoDecodeAccelerator(CGLContextObj cgl_context) |
- : loop_proxy_(base::MessageLoopProxy::current()), |
- cgl_context_(cgl_context), |
+ : cgl_context_(cgl_context), |
client_(NULL), |
+ decoder_thread_("VTDecoderThread"), |
+ format_(NULL), |
+ session_(NULL), |
weak_this_factory_(this) { |
+ callback_.decompressionOutputCallback = OutputThunk; |
+ callback_.decompressionOutputRefCon = this; |
} |
VTVideoDecodeAccelerator::~VTVideoDecodeAccelerator() { |
@@ -20,19 +56,154 @@ bool VTVideoDecodeAccelerator::Initialize( |
media::VideoCodecProfile profile, |
Client* client) { |
DCHECK(CalledOnValidThread()); |
- DVLOG(2) << __FUNCTION__; |
client_ = client; |
// Only H.264 is supported. |
if (profile < media::H264PROFILE_MIN || profile > media::H264PROFILE_MAX) |
return false; |
- // Prevent anyone from using VTVideoDecoder for now. http://crbug.com/133828 |
- return false; |
+ // TODO(sandersd): Move VideoToolbox library loading to sandbox startup; |
+ // until then, --no-sandbox is required. |
+ if (!IsVtInitialized()) { |
+ StubPathMap paths; |
+ // CoreVideo is also required, but the loader stops after the first |
+ // path is loaded. Instead we rely on the transitive dependency from |
+ // VideoToolbox to CoreVideo. |
+ // TODO(sandersd): Fallback to PrivateFrameworks for VideoToolbox. |
+ paths[kModuleVt].push_back(FILE_PATH_LITERAL( |
+ "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox")); |
+ if (!InitializeStubs(paths)) |
+ return false; |
+ } |
+ |
+ // Spawn a thread to handle parsing and calling VideoToolbox. |
+ if (!decoder_thread_.Start()) |
+ return false; |
+ |
+ // Note that --ignore-gpu-blacklist is still required to get here. |
+ return true; |
+} |
+ |
+// TODO(sandersd): Proper error reporting instead of CHECKs. |
+void VTVideoDecodeAccelerator::ConfigureDecoder( |
+ const std::vector<const uint8_t*>& nalu_data_ptrs, |
+ const std::vector<size_t>& nalu_data_sizes) { |
+ format_.reset(); |
+ CHECK(!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() |
+ )); |
+ |
+ // TODO(sandersd): Check if the size has changed and handle picture requests. |
+ CMVideoDimensions coded_size = CMVideoFormatDescriptionGetDimensions(format_); |
+ coded_size_.SetSize(coded_size.width, coded_size.height); |
+ |
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> decoder_config( |
+ CFDictionaryCreateMutable( |
+ kCFAllocatorDefault, |
+ 1, // capacity |
+ &kCFTypeDictionaryKeyCallBacks, |
+ &kCFTypeDictionaryValueCallBacks)); |
+ |
+ CFDictionarySetValue( |
+ decoder_config, |
+ // kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder |
+ CFSTR("EnableHardwareAcceleratedVideoDecoder"), |
+ kCFBooleanTrue); |
+ |
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( |
+ CFDictionaryCreateMutable( |
+ kCFAllocatorDefault, |
+ 4, // capacity |
+ &kCFTypeDictionaryKeyCallBacks, |
+ &kCFTypeDictionaryValueCallBacks)); |
+ |
+ // TODO(sandersd): ARGB for video that is not 4:2:0. |
+ int32_t pixel_format = '2vuy'; |
+#define CFINT(i) CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i) |
+ base::ScopedCFTypeRef<CFNumberRef> cf_pixel_format(CFINT(pixel_format)); |
+ base::ScopedCFTypeRef<CFNumberRef> cf_width(CFINT(coded_size.width)); |
+ base::ScopedCFTypeRef<CFNumberRef> cf_height(CFINT(coded_size.height)); |
+#undef CFINT |
+ CFDictionarySetValue( |
+ image_config, kCVPixelBufferPixelFormatTypeKey, cf_pixel_format); |
+ CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); |
+ CFDictionarySetValue(image_config, kCVPixelBufferHeightKey, cf_height); |
+ CFDictionarySetValue( |
+ image_config, kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); |
+ |
+ // TODO(sandersd): Skip if the session is compatible. |
+ // TODO(sandersd): Flush frames when resetting. |
+ session_.reset(); |
+ CHECK(!VTDecompressionSessionCreate( |
+ kCFAllocatorDefault, |
+ format_, // video_format_description |
+ decoder_config, // video_decoder_specification |
+ image_config, // destination_image_buffer_attributes |
+ &callback_, // output_callback |
+ session_.InitializeInto() |
+ )); |
+ DVLOG(2) << "Created VTDecompressionSession"; |
} |
void VTVideoDecodeAccelerator::Decode(const media::BitstreamBuffer& bitstream) { |
DCHECK(CalledOnValidThread()); |
+ decoder_thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind( |
+ &VTVideoDecodeAccelerator::DecodeTask, base::Unretained(this), |
+ bitstream)); |
+} |
+ |
+void VTVideoDecodeAccelerator::DecodeTask( |
+ const media::BitstreamBuffer bitstream) { |
+ DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
+ |
+ // Map the bitstream buffer. |
+ base::SharedMemory memory(bitstream.handle(), true); |
+ size_t size = bitstream.size(); |
+ CHECK(memory.Map(size)); |
+ const uint8_t* buf = static_cast<uint8_t*>(memory.memory()); |
+ |
+ // Locate relevant NALUs in the buffer. |
+ size_t data_size = 0; |
+ std::vector<media::H264NALU> nalus; |
+ std::vector<const uint8_t*> config_nalu_data_ptrs; |
+ std::vector<size_t> config_nalu_data_sizes; |
+ parser_.SetStream(buf, size); |
+ media::H264NALU nalu; |
+ while (true) { |
+ media::H264Parser::Result result = parser_.AdvanceToNextNALU(&nalu); |
+ if (result == media::H264Parser::kEOStream) |
+ break; |
+ CHECK_EQ(result, media::H264Parser::kOk); |
+ if (nalu.nal_unit_type == media::H264NALU::kSPS || |
+ nalu.nal_unit_type == media::H264NALU::kPPS || |
+ nalu.nal_unit_type == media::H264NALU::kSPSExt) { |
+ config_nalu_data_ptrs.push_back(nalu.data); |
+ config_nalu_data_sizes.push_back(nalu.size); |
+ } |
+ nalus.push_back(nalu); |
+ // Each NALU will have a 4-byte length header prepended. |
+ data_size += kNALUHeaderLength + nalu.size; |
+ } |
+ |
+ if (!config_nalu_data_ptrs.empty()) |
+ ConfigureDecoder(config_nalu_data_ptrs, config_nalu_data_sizes); |
+ |
+ // TODO(sandersd): Rewrite slice NALU headers and send for decoding. |
+} |
+ |
+// This method may be called on any VideoToolbox thread. |
+void VTVideoDecodeAccelerator::Output( |
+ int32_t bitstream_id, |
+ OSStatus status, |
+ VTDecodeInfoFlags info_flags, |
+ CVImageBufferRef image_buffer) { |
+ // TODO(sandersd): Store the frame in a queue. |
+ CFRelease(image_buffer); |
} |
void VTVideoDecodeAccelerator::AssignPictureBuffers( |
@@ -46,14 +217,18 @@ void VTVideoDecodeAccelerator::ReusePictureBuffer(int32_t picture_id) { |
void VTVideoDecodeAccelerator::Flush() { |
DCHECK(CalledOnValidThread()); |
+ // TODO(sandersd): Trigger flush, sending frames. |
} |
void VTVideoDecodeAccelerator::Reset() { |
DCHECK(CalledOnValidThread()); |
+ // TODO(sandersd): Trigger flush, discarding frames. |
} |
void VTVideoDecodeAccelerator::Destroy() { |
DCHECK(CalledOnValidThread()); |
+ // TODO(sandersd): Trigger flush, discarding frames, and wait for them. |
+ delete this; |
} |
bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { |