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 <CoreVideo/CoreVideo.h> | 5 #include <CoreVideo/CoreVideo.h> |
6 #include <OpenGL/CGLIOSurface.h> | 6 #include <OpenGL/CGLIOSurface.h> |
7 #include <OpenGL/gl.h> | 7 #include <OpenGL/gl.h> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
11 #include "base/logging.h" | |
12 #include "base/mac/mac_logging.h" | |
11 #include "base/sys_byteorder.h" | 13 #include "base/sys_byteorder.h" |
12 #include "base/thread_task_runner_handle.h" | 14 #include "base/thread_task_runner_handle.h" |
13 #include "content/common/gpu/media/vt_video_decode_accelerator.h" | 15 #include "content/common/gpu/media/vt_video_decode_accelerator.h" |
14 #include "content/public/common/content_switches.h" | 16 #include "content/public/common/content_switches.h" |
15 #include "media/base/limits.h" | 17 #include "media/base/limits.h" |
16 #include "media/filters/h264_parser.h" | 18 #include "media/filters/h264_parser.h" |
17 #include "ui/gl/scoped_binders.h" | 19 #include "ui/gl/scoped_binders.h" |
18 | 20 |
19 using content_common_gpu_media::kModuleVt; | 21 using content_common_gpu_media::kModuleVt; |
20 using content_common_gpu_media::InitializeStubs; | 22 using content_common_gpu_media::InitializeStubs; |
21 using content_common_gpu_media::IsVtInitialized; | 23 using content_common_gpu_media::IsVtInitialized; |
22 using content_common_gpu_media::StubPathMap; | 24 using content_common_gpu_media::StubPathMap; |
23 | 25 |
24 #define NOTIFY_STATUS(name, status) \ | 26 #define NOTIFY_STATUS(name, status) \ |
25 do { \ | 27 do { \ |
26 DLOG(ERROR) << name << " failed with status " << status; \ | 28 OSSTATUS_DLOG(ERROR, status) << name; \ |
27 NotifyError(PLATFORM_FAILURE); \ | 29 NotifyError(PLATFORM_FAILURE); \ |
28 } while (0) | 30 } while (0) |
29 | 31 |
30 namespace content { | 32 namespace content { |
31 | 33 |
32 // Size to use for NALU length headers in AVC format (can be 1, 2, or 4). | 34 // Size to use for NALU length headers in AVC format (can be 1, 2, or 4). |
33 static const int kNALUHeaderLength = 4; | 35 static const int kNALUHeaderLength = 4; |
34 | 36 |
35 // We request 5 picture buffers from the client, each of which has a texture ID | 37 // We request 5 picture buffers from the client, each of which has a texture ID |
36 // that we can bind decoded frames to. We need enough to satisfy preroll, and | 38 // that we can bind decoded frames to. We need enough to satisfy preroll, and |
37 // enough to avoid unnecessary stalling, but no more than that. The resource | 39 // enough to avoid unnecessary stalling, but no more than that. The resource |
38 // requirements are low, as we don't need the textures to be backed by storage. | 40 // requirements are low, as we don't need the textures to be backed by storage. |
39 static const int kNumPictureBuffers = media::limits::kMaxVideoFrames + 1; | 41 static const int kNumPictureBuffers = media::limits::kMaxVideoFrames + 1; |
40 | 42 |
43 // Build an |image_config| dictionary for VideoToolbox initialization. | |
44 static CFMutableDictionaryRef BuildImageConfig( | |
jeremy
2014/11/23 10:29:16
return a ScopedCFTypeRef ?
sandersd (OOO until July 31)
2014/11/24 19:06:16
Done.
| |
45 CMVideoDimensions coded_dimensions) { | |
jeremy
2014/11/23 10:29:16
nit: will this fit on the line above?
sandersd (OOO until July 31)
2014/11/24 19:06:16
Nope. (And especially not anymore.)
sandersd (OOO until July 31)
2014/11/24 19:06:16
Nope.
| |
46 // TODO(sandersd): RGBA option for 4:4:4 video. | |
47 int32_t pixel_format = kCVPixelFormatType_422YpCbCr8; | |
48 | |
49 #define CFINT(i) CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i) | |
50 base::ScopedCFTypeRef<CFNumberRef> cf_pixel_format(CFINT(pixel_format)); | |
51 base::ScopedCFTypeRef<CFNumberRef> cf_width(CFINT(coded_dimensions.width)); | |
52 base::ScopedCFTypeRef<CFNumberRef> cf_height(CFINT(coded_dimensions.height)); | |
53 #undef CFINT | |
54 | |
55 CFMutableDictionaryRef image_config = CFDictionaryCreateMutable( | |
56 kCFAllocatorDefault, | |
57 4, // capacity | |
58 &kCFTypeDictionaryKeyCallBacks, | |
59 &kCFTypeDictionaryValueCallBacks); | |
60 CFDictionarySetValue(image_config, kCVPixelBufferPixelFormatTypeKey, | |
61 cf_pixel_format); | |
62 CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); | |
63 CFDictionarySetValue(image_config, kCVPixelBufferHeightKey, cf_height); | |
64 CFDictionarySetValue(image_config, kCVPixelBufferOpenGLCompatibilityKey, | |
65 kCFBooleanTrue); | |
66 | |
67 return image_config; | |
68 } | |
69 | |
70 // The purpose of this function is to preload the generic and hardware-specific | |
71 // libraries required by VideoToolbox before the GPU sandbox is enabled. | |
72 // VideoToolbox normally loads the hardware-specific libraries lazily, so we | |
73 // must actually create a decompression session. | |
74 // | |
75 // If creating a decompression session fails, hardware decoding will be disabled | |
76 // (Initialize() will always return false). If it succeeds but a required | |
77 // library is not loaded yet (I have not experienced this, but the details are | |
78 // not documented), then VideoToolbox will fall back on software decoding | |
79 // internally. If that happens, the likely solution is to expand the scope of | |
80 // this initialization. | |
81 void InitializeVideoToolbox() { | |
82 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | |
83 switches::kDisableAcceleratedVideoDecode)) { | |
84 return; | |
85 } | |
86 | |
87 if (!IsVtInitialized()) { | |
88 // CoreVideo is also required, but the loader stops after the first path is | |
89 // loaded. Instead we rely on the transitive dependency from VideoToolbox to | |
90 // CoreVideo. | |
91 // TODO(sandersd): Fallback to PrivateFrameworks. | |
jeremy
2014/11/23 10:29:16
Can you expand on this TODO a bit, why do we need
sandersd (OOO until July 31)
2014/11/24 19:06:16
Done.
| |
92 StubPathMap paths; | |
93 paths[kModuleVt].push_back(FILE_PATH_LITERAL( | |
94 "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox")); | |
95 if (!InitializeStubs(paths)) | |
96 return; | |
97 } | |
98 | |
99 // Create a decoding session. | |
100 // SPS and PPS data were taken from the 480p encoding of Big Buck Bunny. | |
101 const uint8_t sps[] = {0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x80, 0xd4, 0x3d, | |
102 0xa1, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, | |
103 0x00, 0x30, 0x8f, 0x16, 0x2d, 0x9a}; | |
104 const uint8_t pps[] = {0x68, 0xe9, 0x7b, 0xcb}; | |
105 const uint8_t* data_ptrs[] = {sps, pps}; | |
106 const size_t data_sizes[] = {arraysize(sps), arraysize(pps)}; | |
107 | |
108 base::ScopedCFTypeRef<CMFormatDescriptionRef> format; | |
109 OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets( | |
110 kCFAllocatorDefault, | |
111 2, // parameter_set_count | |
112 data_ptrs, // ¶meter_set_pointers | |
113 data_sizes, // ¶meter_set_sizes | |
114 kNALUHeaderLength, // nal_unit_header_length | |
115 format.InitializeInto()); | |
116 if (status) { | |
117 OSSTATUS_LOG(ERROR, status) << "Failed to create CMVideoFormatDescription " | |
118 << "while initializing VideoToolbox"; | |
119 content_common_gpu_media::UninitializeVt(); | |
jeremy
2014/11/23 10:29:16
nit: I'd feel better if there was a scoped object
sandersd (OOO until July 31)
2014/11/24 19:06:16
A good point, but I think it's overkill in this ca
| |
120 return; | |
121 } | |
122 | |
123 base::ScopedCFTypeRef<CFMutableDictionaryRef> decoder_config( | |
124 CFDictionaryCreateMutable( | |
125 kCFAllocatorDefault, | |
126 1, // capacity | |
127 &kCFTypeDictionaryKeyCallBacks, | |
128 &kCFTypeDictionaryValueCallBacks)); | |
129 | |
130 CFDictionarySetValue( | |
131 decoder_config, | |
132 // kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder | |
133 CFSTR("RequireHardwareAcceleratedVideoDecoder"), | |
134 kCFBooleanTrue); | |
135 | |
136 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( | |
137 BuildImageConfig(CMVideoFormatDescriptionGetDimensions(format))); | |
138 | |
139 VTDecompressionOutputCallbackRecord callback = {0}; | |
140 | |
141 base::ScopedCFTypeRef<VTDecompressionSessionRef> session; | |
142 status = VTDecompressionSessionCreate( | |
143 kCFAllocatorDefault, | |
144 format, // video_format_description | |
145 decoder_config, // video_decoder_specification | |
146 image_config, // destination_image_buffer_attributes | |
147 &callback, // output_callback | |
148 session.InitializeInto()); | |
149 if (status) { | |
150 OSSTATUS_LOG(ERROR, status) << "Failed to create VTDecompressionSession " | |
151 << "while initializing VideoToolbox"; | |
152 content_common_gpu_media::UninitializeVt(); | |
153 return; | |
154 } | |
155 } | |
156 | |
41 // Route decoded frame callbacks back into the VTVideoDecodeAccelerator. | 157 // Route decoded frame callbacks back into the VTVideoDecodeAccelerator. |
42 static void OutputThunk( | 158 static void OutputThunk( |
43 void* decompression_output_refcon, | 159 void* decompression_output_refcon, |
44 void* source_frame_refcon, | 160 void* source_frame_refcon, |
45 OSStatus status, | 161 OSStatus status, |
46 VTDecodeInfoFlags info_flags, | 162 VTDecodeInfoFlags info_flags, |
47 CVImageBufferRef image_buffer, | 163 CVImageBufferRef image_buffer, |
48 CMTime presentation_time_stamp, | 164 CMTime presentation_time_stamp, |
49 CMTime presentation_duration) { | 165 CMTime presentation_duration) { |
50 VTVideoDecodeAccelerator* vda = | 166 VTVideoDecodeAccelerator* vda = |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
85 | 201 |
86 VTVideoDecodeAccelerator::~VTVideoDecodeAccelerator() { | 202 VTVideoDecodeAccelerator::~VTVideoDecodeAccelerator() { |
87 } | 203 } |
88 | 204 |
89 bool VTVideoDecodeAccelerator::Initialize( | 205 bool VTVideoDecodeAccelerator::Initialize( |
90 media::VideoCodecProfile profile, | 206 media::VideoCodecProfile profile, |
91 Client* client) { | 207 Client* client) { |
92 DCHECK(gpu_thread_checker_.CalledOnValidThread()); | 208 DCHECK(gpu_thread_checker_.CalledOnValidThread()); |
93 client_ = client; | 209 client_ = client; |
94 | 210 |
211 if (!IsVtInitialized()) | |
212 return false; | |
213 | |
95 // Only H.264 is supported. | 214 // Only H.264 is supported. |
96 if (profile < media::H264PROFILE_MIN || profile > media::H264PROFILE_MAX) | 215 if (profile < media::H264PROFILE_MIN || profile > media::H264PROFILE_MAX) |
97 return false; | 216 return false; |
98 | 217 |
99 // Require --no-sandbox until VideoToolbox library loading is part of sandbox | |
100 // startup (and this VDA is ready for regular users). | |
101 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoSandbox)) | |
102 return false; | |
103 | |
104 if (!IsVtInitialized()) { | |
105 // CoreVideo is also required, but the loader stops after the first | |
106 // path is loaded. Instead we rely on the transitive dependency from | |
107 // VideoToolbox to CoreVideo. | |
108 // TODO(sandersd): Fallback to PrivateFrameworks for VideoToolbox. | |
109 StubPathMap paths; | |
110 paths[kModuleVt].push_back(FILE_PATH_LITERAL( | |
111 "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox")); | |
112 if (!InitializeStubs(paths)) | |
113 return false; | |
114 } | |
115 | |
116 // Spawn a thread to handle parsing and calling VideoToolbox. | 218 // Spawn a thread to handle parsing and calling VideoToolbox. |
117 if (!decoder_thread_.Start()) | 219 if (!decoder_thread_.Start()) |
118 return false; | 220 return false; |
119 | 221 |
120 return true; | 222 return true; |
121 } | 223 } |
122 | 224 |
123 bool VTVideoDecodeAccelerator::FinishDelayedFrames() { | 225 bool VTVideoDecodeAccelerator::FinishDelayedFrames() { |
124 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); | 226 DCHECK(decoder_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
125 if (session_) { | 227 if (session_) { |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
186 &kCFTypeDictionaryKeyCallBacks, | 288 &kCFTypeDictionaryKeyCallBacks, |
187 &kCFTypeDictionaryValueCallBacks)); | 289 &kCFTypeDictionaryValueCallBacks)); |
188 | 290 |
189 CFDictionarySetValue( | 291 CFDictionarySetValue( |
190 decoder_config, | 292 decoder_config, |
191 // kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder | 293 // kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder |
192 CFSTR("EnableHardwareAcceleratedVideoDecoder"), | 294 CFSTR("EnableHardwareAcceleratedVideoDecoder"), |
193 kCFBooleanTrue); | 295 kCFBooleanTrue); |
194 | 296 |
195 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( | 297 base::ScopedCFTypeRef<CFMutableDictionaryRef> image_config( |
196 CFDictionaryCreateMutable( | 298 BuildImageConfig(coded_dimensions)); |
197 kCFAllocatorDefault, | |
198 4, // capacity | |
199 &kCFTypeDictionaryKeyCallBacks, | |
200 &kCFTypeDictionaryValueCallBacks)); | |
201 | |
202 #define CFINT(i) CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i) | |
203 // TODO(sandersd): RGBA option for 4:4:4 video. | |
204 int32_t pixel_format = kCVPixelFormatType_422YpCbCr8; | |
205 base::ScopedCFTypeRef<CFNumberRef> cf_pixel_format(CFINT(pixel_format)); | |
206 base::ScopedCFTypeRef<CFNumberRef> cf_width(CFINT(coded_dimensions.width)); | |
207 base::ScopedCFTypeRef<CFNumberRef> cf_height(CFINT(coded_dimensions.height)); | |
208 #undef CFINT | |
209 CFDictionarySetValue( | |
210 image_config, kCVPixelBufferPixelFormatTypeKey, cf_pixel_format); | |
211 CFDictionarySetValue(image_config, kCVPixelBufferWidthKey, cf_width); | |
212 CFDictionarySetValue(image_config, kCVPixelBufferHeightKey, cf_height); | |
213 CFDictionarySetValue( | |
214 image_config, kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); | |
215 | 299 |
216 // TODO(sandersd): Does the old session need to be flushed first? | 300 // TODO(sandersd): Does the old session need to be flushed first? |
217 session_.reset(); | 301 session_.reset(); |
218 status = VTDecompressionSessionCreate( | 302 status = VTDecompressionSessionCreate( |
219 kCFAllocatorDefault, | 303 kCFAllocatorDefault, |
220 format_, // video_format_description | 304 format_, // video_format_description |
221 decoder_config, // video_decoder_specification | 305 decoder_config, // video_decoder_specification |
222 image_config, // destination_image_buffer_attributes | 306 image_config, // destination_image_buffer_attributes |
223 &callback_, // output_callback | 307 &callback_, // output_callback |
224 session_.InitializeInto()); | 308 session_.InitializeInto()); |
(...skipping 447 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
672 assigned_bitstream_ids_.clear(); | 756 assigned_bitstream_ids_.clear(); |
673 state_ = STATE_DESTROYING; | 757 state_ = STATE_DESTROYING; |
674 QueueFlush(TASK_DESTROY); | 758 QueueFlush(TASK_DESTROY); |
675 } | 759 } |
676 | 760 |
677 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { | 761 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() { |
678 return false; | 762 return false; |
679 } | 763 } |
680 | 764 |
681 } // namespace content | 765 } // namespace content |
OLD | NEW |