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
3 // found in the LICENSE file.
5 #include <CoreVideo/CoreVideo.h>
6 #include <OpenGL/CGLIOSurface.h>
10 #include "base/command_line.h"
11 #include "base/sys_byteorder.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "content/common/gpu/media/vt_video_decode_accelerator.h"
14 #include "content/public/common/content_switches.h"
15 #include "media/filters/h264_parser.h"
16 #include "ui/gl/scoped_binders.h"
17 #include "ui/gl/scoped_cgl.h"
19 using content_common_gpu_media::kModuleVt
;
20 using content_common_gpu_media::InitializeStubs
;
21 using content_common_gpu_media::IsVtInitialized
;
22 using content_common_gpu_media::StubPathMap
;
26 // Size of NALU length headers in AVCC/MPEG-4 format (can be 1, 2, or 4).
27 static const int kNALUHeaderLength
= 4;
29 // We only request 5 picture buffers from the client which are used to hold the
30 // decoded samples. These buffers are then reused when the client tells us that
31 // it is done with the buffer.
32 static const int kNumPictureBuffers
= 5;
34 // Route decoded frame callbacks back into the VTVideoDecodeAccelerator.
35 static void OutputThunk(
36 void* decompression_output_refcon
,
37 void* source_frame_refcon
,
39 VTDecodeInfoFlags info_flags
,
40 CVImageBufferRef image_buffer
,
41 CMTime presentation_time_stamp
,
42 CMTime presentation_duration
) {
43 // TODO(sandersd): Implement flush-before-delete to guarantee validity.
44 VTVideoDecodeAccelerator
* vda
=
45 reinterpret_cast<VTVideoDecodeAccelerator
*>(decompression_output_refcon
);
46 int32_t bitstream_id
= reinterpret_cast<intptr_t>(source_frame_refcon
);
47 vda
->Output(bitstream_id
, status
, image_buffer
);
50 VTVideoDecodeAccelerator::DecodedFrame::DecodedFrame(
52 CVImageBufferRef image_buffer
)
53 : bitstream_id(bitstream_id
),
54 image_buffer(image_buffer
) {
57 VTVideoDecodeAccelerator::DecodedFrame::~DecodedFrame() {
60 VTVideoDecodeAccelerator::VTVideoDecodeAccelerator(CGLContextObj cgl_context
)
61 : cgl_context_(cgl_context
),
65 gpu_task_runner_(base::ThreadTaskRunnerHandle::Get()),
66 weak_this_factory_(this),
67 decoder_thread_("VTDecoderThread") {
68 callback_
.decompressionOutputCallback
= OutputThunk
;
69 callback_
.decompressionOutputRefCon
= this;
72 VTVideoDecodeAccelerator::~VTVideoDecodeAccelerator() {
75 bool VTVideoDecodeAccelerator::Initialize(
76 media::VideoCodecProfile profile
,
78 DCHECK(CalledOnValidThread());
81 // Only H.264 is supported.
82 if (profile
< media::H264PROFILE_MIN
|| profile
> media::H264PROFILE_MAX
)
85 // Require --no-sandbox until VideoToolbox library loading is part of sandbox
86 // startup (and this VDA is ready for regular users).
87 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoSandbox
))
90 if (!IsVtInitialized()) {
91 // CoreVideo is also required, but the loader stops after the first
92 // path is loaded. Instead we rely on the transitive dependency from
93 // VideoToolbox to CoreVideo.
94 // TODO(sandersd): Fallback to PrivateFrameworks for VideoToolbox.
96 paths
[kModuleVt
].push_back(FILE_PATH_LITERAL(
97 "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox"));
98 if (!InitializeStubs(paths
))
102 // Spawn a thread to handle parsing and calling VideoToolbox.
103 if (!decoder_thread_
.Start())
109 // TODO(sandersd): Proper error reporting instead of CHECKs.
110 void VTVideoDecodeAccelerator::ConfigureDecoder(
111 const std::vector
<const uint8_t*>& nalu_data_ptrs
,
112 const std::vector
<size_t>& nalu_data_sizes
) {
113 DCHECK(decoder_thread_
.message_loop_proxy()->BelongsToCurrentThread());
114 // Construct a new format description from the parameter sets.
115 // TODO(sandersd): Replace this with custom code to support OS X < 10.9.
117 CHECK(!CMVideoFormatDescriptionCreateFromH264ParameterSets(
119 nalu_data_ptrs
.size(), // parameter_set_count
120 &nalu_data_ptrs
.front(), // ¶meter_set_pointers
121 &nalu_data_sizes
.front(), // ¶meter_set_sizes
122 kNALUHeaderLength
, // nal_unit_header_length
123 format_
.InitializeInto()));
124 CMVideoDimensions coded_dimensions
=
125 CMVideoFormatDescriptionGetDimensions(format_
);
127 // Prepare VideoToolbox configuration dictionaries.
128 base::ScopedCFTypeRef
<CFMutableDictionaryRef
> decoder_config(
129 CFDictionaryCreateMutable(
132 &kCFTypeDictionaryKeyCallBacks
,
133 &kCFTypeDictionaryValueCallBacks
));
135 CFDictionarySetValue(
137 // kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder
138 CFSTR("EnableHardwareAcceleratedVideoDecoder"),
141 base::ScopedCFTypeRef
<CFMutableDictionaryRef
> image_config(
142 CFDictionaryCreateMutable(
145 &kCFTypeDictionaryKeyCallBacks
,
146 &kCFTypeDictionaryValueCallBacks
));
148 #define CFINT(i) CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i)
149 // TODO(sandersd): RGBA option for 4:4:4 video.
150 int32_t pixel_format
= kCVPixelFormatType_422YpCbCr8
;
151 base::ScopedCFTypeRef
<CFNumberRef
> cf_pixel_format(CFINT(pixel_format
));
152 base::ScopedCFTypeRef
<CFNumberRef
> cf_width(CFINT(coded_dimensions
.width
));
153 base::ScopedCFTypeRef
<CFNumberRef
> cf_height(CFINT(coded_dimensions
.height
));
155 CFDictionarySetValue(
156 image_config
, kCVPixelBufferPixelFormatTypeKey
, cf_pixel_format
);
157 CFDictionarySetValue(image_config
, kCVPixelBufferWidthKey
, cf_width
);
158 CFDictionarySetValue(image_config
, kCVPixelBufferHeightKey
, cf_height
);
159 CFDictionarySetValue(
160 image_config
, kCVPixelBufferOpenGLCompatibilityKey
, kCFBooleanTrue
);
162 // TODO(sandersd): Check if the session is already compatible.
163 // TODO(sandersd): Flush.
165 CHECK(!VTDecompressionSessionCreate(
167 format_
, // video_format_description
168 decoder_config
, // video_decoder_specification
169 image_config
, // destination_image_buffer_attributes
170 &callback_
, // output_callback
171 session_
.InitializeInto()));
173 // If the size has changed, trigger a request for new picture buffers.
174 gfx::Size
new_coded_size(coded_dimensions
.width
, coded_dimensions
.height
);
175 if (coded_size_
!= new_coded_size
) {
176 coded_size_
= new_coded_size
;
177 gpu_task_runner_
->PostTask(FROM_HERE
, base::Bind(
178 &VTVideoDecodeAccelerator::SizeChangedTask
,
179 weak_this_factory_
.GetWeakPtr(),
184 void VTVideoDecodeAccelerator::Decode(const media::BitstreamBuffer
& bitstream
) {
185 DCHECK(CalledOnValidThread());
186 // TODO(sandersd): Test what happens if bitstream buffers are passed to VT out
188 decoder_thread_
.message_loop_proxy()->PostTask(FROM_HERE
, base::Bind(
189 &VTVideoDecodeAccelerator::DecodeTask
, base::Unretained(this),
193 // TODO(sandersd): Proper error reporting instead of CHECKs.
194 void VTVideoDecodeAccelerator::DecodeTask(
195 const media::BitstreamBuffer bitstream
) {
196 DCHECK(decoder_thread_
.message_loop_proxy()->BelongsToCurrentThread());
198 // Map the bitstream buffer.
199 base::SharedMemory
memory(bitstream
.handle(), true);
200 size_t size
= bitstream
.size();
201 CHECK(memory
.Map(size
));
202 const uint8_t* buf
= static_cast<uint8_t*>(memory
.memory());
204 // NALUs are stored with Annex B format in the bitstream buffer (start codes),
205 // but VideoToolbox expects AVCC/MPEG-4 format (length headers), so we must
208 // 1. Locate relevant NALUs and compute the size of the translated data.
209 // Also record any parameter sets for VideoToolbox initialization.
210 size_t data_size
= 0;
211 std::vector
<media::H264NALU
> nalus
;
212 std::vector
<const uint8_t*> config_nalu_data_ptrs
;
213 std::vector
<size_t> config_nalu_data_sizes
;
214 parser_
.SetStream(buf
, size
);
215 media::H264NALU nalu
;
217 media::H264Parser::Result result
= parser_
.AdvanceToNextNALU(&nalu
);
218 if (result
== media::H264Parser::kEOStream
)
220 CHECK_EQ(result
, media::H264Parser::kOk
);
221 // TODO(sandersd): Check that these are only at the start.
222 if (nalu
.nal_unit_type
== media::H264NALU::kSPS
||
223 nalu
.nal_unit_type
== media::H264NALU::kPPS
||
224 nalu
.nal_unit_type
== media::H264NALU::kSPSExt
) {
225 DVLOG(2) << "Parameter set " << nalu
.nal_unit_type
;
226 config_nalu_data_ptrs
.push_back(nalu
.data
);
227 config_nalu_data_sizes
.push_back(nalu
.size
);
229 nalus
.push_back(nalu
);
230 data_size
+= kNALUHeaderLength
+ nalu
.size
;
234 // 2. Initialize VideoToolbox.
235 // TODO(sandersd): Reinitialize when there are new parameter sets.
237 ConfigureDecoder(config_nalu_data_ptrs
, config_nalu_data_sizes
);
239 // 3. Allocate a memory-backed CMBlockBuffer for the translated data.
240 base::ScopedCFTypeRef
<CMBlockBufferRef
> data
;
241 CHECK(!CMBlockBufferCreateWithMemoryBlock(
243 NULL
, // &memory_block
244 data_size
, // block_length
245 kCFAllocatorDefault
, // block_allocator
246 NULL
, // &custom_block_source
248 data_size
, // data_length
250 data
.InitializeInto()));
252 // 4. Copy NALU data, inserting length headers.
254 for (size_t i
= 0; i
< nalus
.size(); i
++) {
255 media::H264NALU
& nalu
= nalus
[i
];
256 uint32_t header
= base::HostToNet32(static_cast<uint32_t>(nalu
.size
));
257 CHECK(!CMBlockBufferReplaceDataBytes(
258 &header
, data
, offset
, kNALUHeaderLength
));
259 offset
+= kNALUHeaderLength
;
260 CHECK(!CMBlockBufferReplaceDataBytes(nalu
.data
, data
, offset
, nalu
.size
));
264 // 5. Package the data for VideoToolbox and request decoding.
265 base::ScopedCFTypeRef
<CMSampleBufferRef
> frame
;
266 CHECK(!CMSampleBufferCreate(
270 NULL
, // make_data_ready_callback
271 NULL
, // make_data_ready_refcon
272 format_
, // format_description
274 0, // num_sample_timing_entries
275 NULL
, // &sample_timing_array
276 0, // num_sample_size_entries
277 NULL
, // &sample_size_array
278 frame
.InitializeInto()));
280 // Asynchronous Decompression allows for parallel submission of frames
281 // (without it, DecodeFrame() does not return until the frame has been
282 // decoded). We don't enable Temporal Processing so that frames are always
283 // returned in decode order; this makes it easier to avoid deadlock.
284 VTDecodeFrameFlags decode_flags
=
285 kVTDecodeFrame_EnableAsynchronousDecompression
;
287 intptr_t bitstream_id
= bitstream
.id();
288 CHECK(!VTDecompressionSessionDecodeFrame(
290 frame
, // sample_buffer
291 decode_flags
, // decode_flags
292 reinterpret_cast<void*>(bitstream_id
), // source_frame_refcon
293 NULL
)); // &info_flags_out
296 // This method may be called on any VideoToolbox thread.
297 // TODO(sandersd): Proper error reporting instead of CHECKs.
298 void VTVideoDecodeAccelerator::Output(
299 int32_t bitstream_id
,
301 CVImageBufferRef image_buffer
) {
303 CHECK_EQ(CFGetTypeID(image_buffer
), CVPixelBufferGetTypeID());
304 CFRetain(image_buffer
);
305 gpu_task_runner_
->PostTask(FROM_HERE
, base::Bind(
306 &VTVideoDecodeAccelerator::OutputTask
,
307 weak_this_factory_
.GetWeakPtr(),
308 DecodedFrame(bitstream_id
, image_buffer
)));
311 void VTVideoDecodeAccelerator::OutputTask(DecodedFrame frame
) {
312 DCHECK(CalledOnValidThread());
313 decoded_frames_
.push(frame
);
317 void VTVideoDecodeAccelerator::SizeChangedTask(gfx::Size coded_size
) {
318 DCHECK(CalledOnValidThread());
319 texture_size_
= coded_size
;
320 // TODO(sandersd): Dismiss existing picture buffers.
321 client_
->ProvidePictureBuffers(
322 kNumPictureBuffers
, texture_size_
, GL_TEXTURE_RECTANGLE_ARB
);
325 void VTVideoDecodeAccelerator::AssignPictureBuffers(
326 const std::vector
<media::PictureBuffer
>& pictures
) {
327 DCHECK(CalledOnValidThread());
329 for (size_t i
= 0; i
< pictures
.size(); i
++) {
330 CHECK(!texture_ids_
.count(pictures
[i
].id()));
331 available_picture_ids_
.push(pictures
[i
].id());
332 texture_ids_
[pictures
[i
].id()] = pictures
[i
].texture_id();
335 // Pictures are not marked as uncleared until this method returns. They will
336 // become broken if they are used before that happens.
337 gpu_task_runner_
->PostTask(FROM_HERE
, base::Bind(
338 &VTVideoDecodeAccelerator::SendPictures
,
339 weak_this_factory_
.GetWeakPtr()));
342 void VTVideoDecodeAccelerator::ReusePictureBuffer(int32_t picture_id
) {
343 DCHECK(CalledOnValidThread());
344 DCHECK_EQ(CFGetRetainCount(picture_bindings_
[picture_id
]), 1);
345 picture_bindings_
.erase(picture_id
);
346 available_picture_ids_
.push(picture_id
);
350 // TODO(sandersd): Proper error reporting instead of CHECKs.
351 void VTVideoDecodeAccelerator::SendPictures() {
352 DCHECK(CalledOnValidThread());
353 if (available_picture_ids_
.empty() || decoded_frames_
.empty())
356 gfx::ScopedCGLSetCurrentContext
scoped_set_current_context(cgl_context_
);
357 glEnable(GL_TEXTURE_RECTANGLE_ARB
);
359 while (!available_picture_ids_
.empty() && !decoded_frames_
.empty()) {
360 int32_t picture_id
= available_picture_ids_
.front();
361 available_picture_ids_
.pop();
362 DecodedFrame frame
= decoded_frames_
.front();
363 decoded_frames_
.pop();
364 IOSurfaceRef surface
= CVPixelBufferGetIOSurface(frame
.image_buffer
);
366 gfx::ScopedTextureBinder
367 texture_binder(GL_TEXTURE_RECTANGLE_ARB
, texture_ids_
[picture_id
]);
368 CHECK(!CGLTexImageIOSurface2D(
370 GL_TEXTURE_RECTANGLE_ARB
, // target
371 GL_RGB
, // internal_format
372 texture_size_
.width(), // width
373 texture_size_
.height(), // height
374 GL_YCBCR_422_APPLE
, // format
375 GL_UNSIGNED_SHORT_8_8_APPLE
, // type
376 surface
, // io_surface
379 picture_bindings_
[picture_id
] = frame
.image_buffer
;
380 client_
->PictureReady(media::Picture(
381 picture_id
, frame
.bitstream_id
, gfx::Rect(texture_size_
)));
382 client_
->NotifyEndOfBitstreamBuffer(frame
.bitstream_id
);
385 glDisable(GL_TEXTURE_RECTANGLE_ARB
);
388 void VTVideoDecodeAccelerator::Flush() {
389 DCHECK(CalledOnValidThread());
390 // TODO(sandersd): Trigger flush, sending frames.
393 void VTVideoDecodeAccelerator::Reset() {
394 DCHECK(CalledOnValidThread());
395 // TODO(sandersd): Trigger flush, discarding frames.
398 void VTVideoDecodeAccelerator::Destroy() {
399 DCHECK(CalledOnValidThread());
400 // TODO(sandersd): Trigger flush, discarding frames, and wait for them.
404 bool VTVideoDecodeAccelerator::CanDecodeOnIOThread() {
408 } // namespace content