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 |