| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <algorithm> | 5 #include <algorithm> |
| 6 | 6 |
| 7 #include <CoreVideo/CoreVideo.h> | 7 #include <CoreVideo/CoreVideo.h> |
| 8 #include <OpenGL/CGLIOSurface.h> | 8 #include <OpenGL/CGLIOSurface.h> |
| 9 #include <OpenGL/gl.h> | 9 #include <OpenGL/gl.h> |
| 10 | 10 |
| 11 #include "base/bind.h" | 11 #include "base/bind.h" |
| 12 #include "base/command_line.h" | 12 #include "base/command_line.h" |
| 13 #include "base/logging.h" |
| 14 #include "base/mac/mac_logging.h" |
| 13 #include "base/sys_byteorder.h" | 15 #include "base/sys_byteorder.h" |
| 14 #include "base/thread_task_runner_handle.h" | 16 #include "base/thread_task_runner_handle.h" |
| 15 #include "content/common/gpu/media/vt_video_decode_accelerator.h" | 17 #include "content/common/gpu/media/vt_video_decode_accelerator.h" |
| 16 #include "content/public/common/content_switches.h" | 18 #include "content/public/common/content_switches.h" |
| 17 #include "media/base/limits.h" | 19 #include "media/base/limits.h" |
| 18 #include "ui/gl/scoped_binders.h" | 20 #include "ui/gl/scoped_binders.h" |
| 19 | 21 |
| 20 using content_common_gpu_media::kModuleVt; | 22 using content_common_gpu_media::kModuleVt; |
| 21 using content_common_gpu_media::InitializeStubs; | 23 using content_common_gpu_media::InitializeStubs; |
| 22 using content_common_gpu_media::IsVtInitialized; | 24 using content_common_gpu_media::IsVtInitialized; |
| 23 using content_common_gpu_media::StubPathMap; | 25 using content_common_gpu_media::StubPathMap; |
| 24 | 26 |
| 25 #define NOTIFY_STATUS(name, status) \ | 27 #define NOTIFY_STATUS(name, status) \ |
| 26 do { \ | 28 do { \ |
| 27 DLOG(ERROR) << name << " failed with status " << status; \ | 29 OSSTATUS_DLOG(ERROR, status) << name; \ |
| 28 NotifyError(PLATFORM_FAILURE); \ | 30 NotifyError(PLATFORM_FAILURE); \ |
| 29 } while (0) | 31 } while (0) |
| 30 | 32 |
| 31 namespace content { | 33 namespace content { |
| 32 | 34 |
| 33 // Size to use for NALU length headers in AVC format (can be 1, 2, or 4). | 35 // Size to use for NALU length headers in AVC format (can be 1, 2, or 4). |
| 34 static const int kNALUHeaderLength = 4; | 36 static const int kNALUHeaderLength = 4; |
| 35 | 37 |
| 36 // We request 5 picture buffers from the client, each of which has a texture ID | 38 // We request 5 picture buffers from the client, each of which has a texture ID |
| 37 // that we can bind decoded frames to. We need enough to satisfy preroll, and | 39 // that we can bind decoded frames to. We need enough to satisfy preroll, and |
| 38 // enough to avoid unnecessary stalling, but no more than that. The resource | 40 // enough to avoid unnecessary stalling, but no more than that. The resource |
| 39 // requirements are low, as we don't need the textures to be backed by storage. | 41 // requirements are low, as we don't need the textures to be backed by storage. |
| 40 static const int kNumPictureBuffers = media::limits::kMaxVideoFrames + 1; | 42 static const int kNumPictureBuffers = media::limits::kMaxVideoFrames + 1; |
| 41 | 43 |
| 42 // Maximum number of frames to queue for reordering before we stop asking for | 44 // Maximum number of frames to queue for reordering before we stop asking for |
| 43 // more. (NotifyEndOfBitstreamBuffer() is called when frames are moved into the | 45 // more. (NotifyEndOfBitstreamBuffer() is called when frames are moved into the |
| 44 // reorder queue.) | 46 // reorder queue.) |
| 45 static const int kMaxReorderQueueSize = 16; | 47 static const int kMaxReorderQueueSize = 16; |
| 46 | 48 |
| 49 // Build an |image_config| dictionary for VideoToolbox initialization. |
| 50 static base::ScopedCFTypeRef<CFMutableDictionaryRef> |
| 51 BuildImageConfig(CMVideoDimensions coded_dimensions) { |
| 52 // TODO(sandersd): RGBA option for 4:4:4 video. |
| 53 int32_t pixel_format = kCVPixelFormatType_422YpCbCr8; |
| 54 |
| 55 #define CFINT(i) CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i) |
| 56 base::ScopedCFTypeRef<CFNumberRef> cf_pixel_format(CFINT(pixel_format)); |
| 57 base::ScopedCFTypeRef<CFNumberRef> cf_width(CFINT(coded_dimensions.width)); |
| 58 base::ScopedCFTypeRef<CFNumberRef> cf_height(CFINT(coded_dimensions.height)); |
| 59 #undef CFINT |
| 60 |
| 61 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( |
| 62 CFDictionaryCreateMutable( |
| 63 kCFAllocatorDefault, |
| 64 4, // capacity |
| 65 &kCFTypeDictionaryKeyCallBacks, |
| 66 &kCFTypeDictionaryValueCallBacks)); |
| 67 CFDictionarySetValue(image_config, kCVPixelBufferPixelFormatTypeKey, |
| 68 cf_pixel_format); |
| 69 CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); |
| 70 CFDictionarySetValue(image_config, kCVPixelBufferHeightKey, cf_height); |
| 71 CFDictionarySetValue(image_config, kCVPixelBufferOpenGLCompatibilityKey, |
| 72 kCFBooleanTrue); |
| 73 |
| 74 return image_config; |
| 75 } |
| 76 |
| 77 // The purpose of this function is to preload the generic and hardware-specific |
| 78 // libraries required by VideoToolbox before the GPU sandbox is enabled. |
| 79 // VideoToolbox normally loads the hardware-specific libraries lazily, so we |
| 80 // must actually create a decompression session. |
| 81 // |
| 82 // If creating a decompression session fails, hardware decoding will be disabled |
| 83 // (Initialize() will always return false). If it succeeds but a required |
| 84 // library is not loaded yet (I have not experienced this, but the details are |
| 85 // not documented), then VideoToolbox will fall back on software decoding |
| 86 // internally. If that happens, the likely solution is to expand the scope of |
| 87 // this initialization. |
| 88 void InitializeVideoToolbox() { |
| 89 if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 90 switches::kDisableAcceleratedVideoDecode)) { |
| 91 return; |
| 92 } |
| 93 |
| 94 if (!IsVtInitialized()) { |
| 95 // CoreVideo is also required, but the loader stops after the first path is |
| 96 // loaded. Instead we rely on the transitive dependency from VideoToolbox to |
| 97 // CoreVideo. |
| 98 // TODO(sandersd): Fallback to PrivateFrameworks to support OS X < 10.8. |
| 99 StubPathMap paths; |
| 100 paths[kModuleVt].push_back(FILE_PATH_LITERAL( |
| 101 "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox")); |
| 102 if (!InitializeStubs(paths)) |
| 103 return; |
| 104 } |
| 105 |
| 106 // Create a decoding session. |
| 107 // SPS and PPS data were taken from the 480p encoding of Big Buck Bunny. |
| 108 const uint8_t sps[] = {0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x80, 0xd4, 0x3d, |
| 109 0xa1, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, |
| 110 0x00, 0x30, 0x8f, 0x16, 0x2d, 0x9a}; |
| 111 const uint8_t pps[] = {0x68, 0xe9, 0x7b, 0xcb}; |
| 112 const uint8_t* data_ptrs[] = {sps, pps}; |
| 113 const size_t data_sizes[] = {arraysize(sps), arraysize(pps)}; |
| 114 |
| 115 base::ScopedCFTypeRef<CMFormatDescriptionRef> format; |
| 116 OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets( |
| 117 kCFAllocatorDefault, |
| 118 2, // parameter_set_count |
| 119 data_ptrs, // ¶meter_set_pointers |
| 120 data_sizes, // ¶meter_set_sizes |
| 121 kNALUHeaderLength, // nal_unit_header_length |
| 122 format.InitializeInto()); |
| 123 if (status) { |
| 124 OSSTATUS_LOG(ERROR, status) << "Failed to create CMVideoFormatDescription " |
| 125 << "while initializing VideoToolbox"; |
| 126 content_common_gpu_media::UninitializeVt(); |
| 127 return; |
| 128 } |
| 129 |
| 130 base::ScopedCFTypeRef<CFMutableDictionaryRef> decoder_config( |
| 131 CFDictionaryCreateMutable( |
| 132 kCFAllocatorDefault, |
| 133 1, // capacity |
| 134 &kCFTypeDictionaryKeyCallBacks, |
| 135 &kCFTypeDictionaryValueCallBacks)); |
| 136 |
| 137 CFDictionarySetValue( |
| 138 decoder_config, |
| 139 // kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder |
| 140 CFSTR("RequireHardwareAcceleratedVideoDecoder"), |
| 141 kCFBooleanTrue); |
| 142 |
| 143 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( |
| 144 BuildImageConfig(CMVideoFormatDescriptionGetDimensions(format))); |
| 145 |
| 146 VTDecompressionOutputCallbackRecord callback = {0}; |
| 147 |
| 148 base::ScopedCFTypeRef<VTDecompressionSessionRef> session; |
| 149 status = VTDecompressionSessionCreate( |
| 150 kCFAllocatorDefault, |
| 151 format, // video_format_description |
| 152 decoder_config, // video_decoder_specification |
| 153 image_config, // destination_image_buffer_attributes |
| 154 &callback, // output_callback |
| 155 session.InitializeInto()); |
| 156 if (status) { |
| 157 OSSTATUS_LOG(ERROR, status) << "Failed to create VTDecompressionSession " |
| 158 << "while initializing VideoToolbox"; |
| 159 content_common_gpu_media::UninitializeVt(); |
| 160 return; |
| 161 } |
| 162 } |
| 163 |
| 47 // Route decoded frame callbacks back into the VTVideoDecodeAccelerator. | 164 // Route decoded frame callbacks back into the VTVideoDecodeAccelerator. |
| 48 static void OutputThunk( | 165 static void OutputThunk( |
| 49 void* decompression_output_refcon, | 166 void* decompression_output_refcon, |
| 50 void* source_frame_refcon, | 167 void* source_frame_refcon, |
| 51 OSStatus status, | 168 OSStatus status, |
| 52 VTDecodeInfoFlags info_flags, | 169 VTDecodeInfoFlags info_flags, |
| 53 CVImageBufferRef image_buffer, | 170 CVImageBufferRef image_buffer, |
| 54 CMTime presentation_time_stamp, | 171 CMTime presentation_time_stamp, |
| 55 CMTime presentation_duration) { | 172 CMTime presentation_duration) { |
| 56 VTVideoDecodeAccelerator* vda = | 173 VTVideoDecodeAccelerator* vda = |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 105 | 222 |
| 106 VTVideoDecodeAccelerator::~VTVideoDecodeAccelerator() { | 223 VTVideoDecodeAccelerator::~VTVideoDecodeAccelerator() { |
| 107 } | 224 } |
| 108 | 225 |
| 109 bool VTVideoDecodeAccelerator::Initialize( | 226 bool VTVideoDecodeAccelerator::Initialize( |
| 110 media::VideoCodecProfile profile, | 227 media::VideoCodecProfile profile, |
| 111 Client* client) { | 228 Client* client) { |
| 112 DCHECK(gpu_thread_checker_.CalledOnValidThread()); | 229 DCHECK(gpu_thread_checker_.CalledOnValidThread()); |
| 113 client_ = client; | 230 client_ = client; |
| 114 | 231 |
| 232 if (!IsVtInitialized()) |
| 233 return false; |
| 234 |
| 115 // Only H.264 is supported. | 235 // Only H.264 is supported. |
| 116 if (profile < media::H264PROFILE_MIN || profile > media::H264PROFILE_MAX) | 236 if (profile < media::H264PROFILE_MIN || profile > media::H264PROFILE_MAX) |
| 117 return false; | 237 return false; |
| 118 | 238 |
| 119 // Require --no-sandbox until VideoToolbox library loading is part of sandbox | |
| 120 // startup (and this VDA is ready for regular users). | |
| 121 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoSandbox)) | |
| 122 return false; | |
| 123 | |
| 124 if (!IsVtInitialized()) { | |
| 125 // CoreVideo is also required, but the loader stops after the first | |
| 126 // path is loaded. Instead we rely on the transitive dependency from | |
| 127 // VideoToolbox to CoreVideo. | |
| 128 // TODO(sandersd): Fallback to PrivateFrameworks for VideoToolbox. | |
| 129 StubPathMap paths; | |
| 130 paths[kModuleVt].push_back(FILE_PATH_LITERAL( | |
| 131 "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox")); | |
| 132 if (!InitializeStubs(paths)) | |
| 133 return false; | |
| 134 } | |
| 135 | |
| 136 // Spawn a thread to handle parsing and calling VideoToolbox. | 239 // Spawn a thread to handle parsing and calling VideoToolbox. |
| 137 if (!decoder_thread_.Start()) | 240 if (!decoder_thread_.Start()) |
| 138 return false; | 241 return false; |
| 139 | 242 |
| 140 return true; | 243 return true; |
| 141 } | 244 } |
| 142 | 245 |
| 143 bool VTVideoDecodeAccelerator::FinishDelayedFrames() { | 246 bool VTVideoDecodeAccelerator::FinishDelayedFrames() { |
| 144 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 247 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
| 145 if (session_) { | 248 if (session_) { |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 206 &kCFTypeDictionaryKeyCallBacks, | 309 &kCFTypeDictionaryKeyCallBacks, |
| 207 &kCFTypeDictionaryValueCallBacks)); | 310 &kCFTypeDictionaryValueCallBacks)); |
| 208 | 311 |
| 209 CFDictionarySetValue( | 312 CFDictionarySetValue( |
| 210 decoder_config, | 313 decoder_config, |
| 211 // kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder | 314 // kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder |
| 212 CFSTR("EnableHardwareAcceleratedVideoDecoder"), | 315 CFSTR("EnableHardwareAcceleratedVideoDecoder"), |
| 213 kCFBooleanTrue); | 316 kCFBooleanTrue); |
| 214 | 317 |
| 215 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( | 318 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( |
| 216 CFDictionaryCreateMutable( | 319 BuildImageConfig(coded_dimensions)); |
| 217 kCFAllocatorDefault, | |
| 218 4, // capacity | |
| 219 &kCFTypeDictionaryKeyCallBacks, | |
| 220 &kCFTypeDictionaryValueCallBacks)); | |
| 221 | |
| 222 #define CFINT(i) CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i) | |
| 223 // TODO(sandersd): RGBA option for 4:4:4 video. | |
| 224 int32_t pixel_format = kCVPixelFormatType_422YpCbCr8; | |
| 225 base::ScopedCFTypeRef<CFNumberRef> cf_pixel_format(CFINT(pixel_format)); | |
| 226 base::ScopedCFTypeRef<CFNumberRef> cf_width(CFINT(coded_dimensions.width)); | |
| 227 base::ScopedCFTypeRef<CFNumberRef> cf_height(CFINT(coded_dimensions.height)); | |
| 228 #undef CFINT | |
| 229 CFDictionarySetValue( | |
| 230 image_config, kCVPixelBufferPixelFormatTypeKey, cf_pixel_format); | |
| 231 CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); | |
| 232 CFDictionarySetValue(image_config, kCVPixelBufferHeightKey, cf_height); | |
| 233 CFDictionarySetValue( | |
| 234 image_config, kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); | |
| 235 | 320 |
| 236 // TODO(sandersd): Does the old session need to be flushed first? | 321 // TODO(sandersd): Does the old session need to be flushed first? |
| 237 session_.reset(); | 322 session_.reset(); |
| 238 status = VTDecompressionSessionCreate( | 323 status = VTDecompressionSessionCreate( |
| 239 kCFAllocatorDefault, | 324 kCFAllocatorDefault, |
| 240 format_, // video_format_description | 325 format_, // video_format_description |
| 241 decoder_config, // video_decoder_specification | 326 decoder_config, // video_decoder_specification |
| 242 image_config, // destination_image_buffer_attributes | 327 image_config, // destination_image_buffer_attributes |
| 243 &callback_, // output_callback | 328 &callback_, // output_callback |
| 244 session_.InitializeInto()); | 329 session_.InitializeInto()); |
| (...skipping 555 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 800 assigned_bitstream_ids_.clear(); | 885 assigned_bitstream_ids_.clear(); |
| 801 state_ = STATE_DESTROYING; | 886 state_ = STATE_DESTROYING; |
| 802 QueueFlush(TASK_DESTROY); | 887 QueueFlush(TASK_DESTROY); |
| 803 } | 888 } |
| 804 | 889 |
| 805 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { | 890 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { |
| 806 return false; | 891 return false; |
| 807 } | 892 } |
| 808 | 893 |
| 809 } // namespace content | 894 } // namespace content |
| OLD | NEW |