OLD | NEW |
---|---|
(Empty) | |
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. | |
4 | |
5 #include "media/cast/sender/h264_vt_encoder.h" | |
6 | |
7 #include "base/big_endian.h" | |
8 #include "base/bind.h" | |
9 #include "base/bind_helpers.h" | |
10 #include "base/location.h" | |
11 #include "base/logging.h" | |
12 | |
13 #include <CoreMedia/CoreMedia.h> | |
14 #include <VideoToolbox/VideoToolbox.h> | |
15 | |
16 namespace media { | |
17 namespace cast { | |
18 | |
19 static const char* GetCVErrorString(CVReturn error) { | |
miu
2014/08/08 17:53:06
This seems overkill for debug logging. Suggest yo
jfroy
2014/08/08 23:23:25
This has all been removed in a following PS.
| |
20 switch (error) { | |
21 case kCVReturnSuccess: | |
22 return "success"; | |
23 case kCVReturnError: | |
24 return "error"; | |
25 case kCVReturnInvalidArgument: | |
26 return "invalid argument"; | |
27 case kCVReturnAllocationFailed: | |
28 return "allocation failed"; | |
29 case kCVReturnInvalidDisplay: | |
30 return "invalid display"; | |
31 case kCVReturnDisplayLinkAlreadyRunning: | |
32 return "display link already running"; | |
33 case kCVReturnDisplayLinkNotRunning: | |
34 return "display link not running"; | |
35 case kCVReturnDisplayLinkCallbacksNotSet: | |
36 return "display link callback not set"; | |
37 case kCVReturnInvalidPixelFormat: | |
38 return "invalid pixel format"; | |
39 case kCVReturnInvalidSize: | |
40 return "invalid size"; | |
41 case kCVReturnInvalidPixelBufferAttributes: | |
42 return "invalid pixel buffer attributes"; | |
43 case kCVReturnPixelBufferNotOpenGLCompatible: | |
44 return "pixel buffer not OpenGL compatible"; | |
45 case kCVReturnWouldExceedAllocationThreshold: | |
46 return "would exceed allocation threshold"; | |
47 case kCVReturnPoolAllocationFailed: | |
48 return "pool allocation failed"; | |
49 case kCVReturnInvalidPoolAttributes: | |
50 return "invalid pool attributes"; | |
51 default: | |
52 return "unknown error"; | |
53 } | |
54 } | |
55 | |
56 static const char* GetVTErrorString(OSStatus error) { | |
miu
2014/08/08 17:53:07
ditto: re: overkill for debug logging
jfroy
2014/08/08 23:23:24
This has all been removed in a following PS.
| |
57 switch (error) { | |
58 case kVTPropertyNotSupportedErr: | |
59 return "property not supported"; | |
60 case kVTPropertyReadOnlyErr: | |
61 return "read only property"; | |
62 case kVTParameterErr: | |
63 return "invalid parameter"; | |
64 case kVTInvalidSessionErr: | |
65 return "invalid session"; | |
66 case kVTAllocationFailedErr: | |
67 return "allocation failed"; | |
68 case kVTPixelTransferNotSupportedErr: | |
69 return "pixel transfer not supported"; | |
70 case kVTCouldNotFindVideoDecoderErr: | |
71 return "could not find video decoder"; | |
72 case kVTCouldNotCreateInstanceErr: | |
73 return "could not create instance"; | |
74 case kVTCouldNotFindVideoEncoderErr: | |
75 return "could not find video encoder"; | |
76 case kVTVideoDecoderBadDataErr: | |
77 return "video decoder bad data"; | |
78 case kVTVideoDecoderUnsupportedDataFormatErr: | |
79 return "video decoder unsupported data format"; | |
80 case kVTVideoDecoderMalfunctionErr: | |
81 return "video decoder malfunction"; | |
82 case kVTVideoEncoderMalfunctionErr: | |
83 return "video encoder malfunction"; | |
84 case kVTVideoDecoderNotAvailableNowErr: | |
85 return "video decoder not available"; | |
86 case kVTImageRotationNotSupportedErr: | |
87 return "image rotation not supported"; | |
88 case kVTVideoEncoderNotAvailableNowErr: | |
89 return "video encoder not available now"; | |
90 case kVTFormatDescriptionChangeNotSupportedErr: | |
91 return "format description change not supported"; | |
92 case kVTInsufficientSourceColorDataErr: | |
93 return "insufficient source color data"; | |
94 case kVTCouldNotCreateColorCorrectionDataErr: | |
95 return "could not create color correction data"; | |
96 case kVTColorSyncTransformConvertFailedErr: | |
97 return "ColorSync transform convert failed"; | |
98 case kVTVideoDecoderAuthorizationErr: | |
99 return "video decoder authorization error"; | |
100 case kVTVideoEncoderAuthorizationErr: | |
101 return "video encoder authorization error"; | |
102 case kVTColorCorrectionPixelTransferFailedErr: | |
103 return "color correction pixel transfer failed"; | |
104 case kVTMultiPassStorageIdentifierMismatchErr: | |
105 return "multi-pass storage identifier mismatch"; | |
106 case kVTMultiPassStorageInvalidErr: | |
107 return "invalid multi-pass storage"; | |
108 case kVTFrameSiloInvalidTimeStampErr: | |
109 return "invalid frame silo timestamp"; | |
110 case kVTFrameSiloInvalidTimeRangeErr: | |
111 return "invalid frame silo time range"; | |
112 case kVTCouldNotFindTemporalFilterErr: | |
113 return "could not find temporal filter"; | |
114 case kVTPixelTransferNotPermittedErr: | |
115 return "pixel transfer not permitted"; | |
116 default: | |
117 return "unknown error"; | |
118 } | |
119 } | |
120 | |
121 #pragma mark - | |
122 | |
123 // utility to log CFTypes | |
124 | |
125 std::ostream& operator<<(std::ostream& out, const CFStringRef& cfstring) { | |
miu
2014/08/08 17:53:07
Rather than rolling your own, how about using the
jfroy
2014/08/08 23:23:24
I had no idea those existed.
This has all been re
| |
126 if (!cfstring) { | |
127 return out << "null"; | |
128 } | |
129 | |
130 const char* c_str; | |
131 c_str = CFStringGetCStringPtr(cfstring, kCFStringEncodingASCII); | |
132 if (c_str) { | |
133 return out << c_str; | |
134 } | |
135 c_str = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8); | |
136 if (c_str) { | |
137 return out << c_str; | |
138 } | |
139 | |
140 CFIndex length = CFStringGetLength(cfstring); | |
141 size_t size = | |
142 CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; | |
143 std::vector<char> c_str_buf; | |
144 c_str_buf.reserve(size); | |
145 CFStringGetCString(cfstring, c_str_buf.data(), size, kCFStringEncodingUTF8); | |
146 return out << c_str_buf.data(); | |
147 } | |
148 | |
149 std::ostream& operator<<(std::ostream& out, const CFNumberRef& cfnumber) { | |
miu
2014/08/08 17:53:08
For this and the other ostream::operator<< definit
jfroy
2014/08/08 23:23:25
This has all been removed in a following PS.
| |
150 if (!cfnumber) { | |
151 return out << "null"; | |
152 } | |
153 | |
154 base::ScopedCFTypeRef<CFNumberFormatterRef> formatter(CFNumberFormatterCreate( | |
155 kCFAllocatorDefault, CFLocaleGetSystem(), kCFNumberFormatterNoStyle)); | |
156 base::ScopedCFTypeRef<CFStringRef> as_str( | |
157 CFNumberFormatterCreateStringWithNumber( | |
158 kCFAllocatorDefault, formatter, cfnumber)); | |
159 return out << as_str; | |
160 } | |
161 | |
162 std::ostream& operator<<(std::ostream& out, const CFBooleanRef& cfboolean) { | |
163 if (!cfboolean) { | |
164 return out << "null"; | |
165 } | |
166 | |
167 base::ScopedCFTypeRef<CFStringRef> as_str( | |
168 CFBooleanGetValue(cfboolean) ? CFSTR("true") : CFSTR("false")); | |
169 return out << as_str; | |
170 } | |
171 | |
172 struct CFTypeEmittable { | |
173 explicit CFTypeEmittable(CFTypeRef cfobject) : cfobject_(cfobject) {} | |
174 explicit operator bool() const { return cfobject_ != nullptr; } | |
175 operator CFTypeRef() const { return cfobject_; } | |
176 CFTypeRef get() const { return cfobject_; } | |
177 friend std::ostream& operator<<(std::ostream&, const CFTypeEmittable&); | |
178 CFTypeRef cfobject_; | |
179 }; | |
180 | |
181 std::ostream& operator<<(std::ostream& out, const CFTypeEmittable& emittable) { | |
182 if (!emittable) { | |
183 return out << "null"; | |
184 } | |
185 | |
186 if (CFGetTypeID(emittable) == CFStringGetTypeID()) { | |
187 return out << static_cast<CFStringRef>(emittable.get()); | |
188 } else if (CFGetTypeID(emittable) == CFNumberGetTypeID()) { | |
189 return out << static_cast<CFNumberRef>(emittable.get()); | |
190 } else if (CFGetTypeID(emittable) == CFBooleanGetTypeID()) { | |
191 return out << static_cast<CFBooleanRef>(emittable.get()); | |
192 } | |
193 base::ScopedCFTypeRef<CFStringRef> as_str(CFCopyDescription(emittable)); | |
194 return out << as_str; | |
195 } | |
196 | |
197 // utility to log CM types | |
198 | |
199 std::ostream& operator<<(std::ostream& out, const CMTime& time) { | |
200 return out << "{value=" << time.value << ", timescale=" << time.timescale | |
201 << ", flags=" << time.flags << ", epoch=" << time.epoch << "}"; | |
202 } | |
203 | |
204 std::ostream& operator<<(std::ostream& out, | |
205 const CMSampleTimingInfo& timing_info) { | |
206 return out << "{duration=" << timing_info.duration | |
207 << ", pts=" << timing_info.presentationTimeStamp | |
208 << ", dts=" << timing_info.decodeTimeStamp << "}"; | |
209 } | |
210 | |
211 #pragma mark - | |
212 | |
213 // utility to configure | |
214 | |
215 template <typename T> | |
216 bool SetSessionProperty(VTSessionRef session, | |
217 CFStringRef key, | |
218 T value, | |
219 CFTypeRef cfvalue) { | |
220 DVLOG(3) << __func__ << ": " << key << "=" << value; | |
221 OSStatus status = VTSessionSetProperty(session, key, cfvalue); | |
222 if (status != noErr) { | |
223 DLOG(ERROR) << __func__ | |
224 << " VTSessionSetProperty failed: " << GetVTErrorString(status) | |
225 << " (" << status << ") " << key << "=" << value; | |
226 } | |
227 return status == noErr; | |
228 } | |
229 | |
230 static bool SetSessionProperty(VTSessionRef session, | |
231 CFStringRef key, | |
232 uint32_t value) { | |
233 base::ScopedCFTypeRef<CFNumberRef> cfvalue( | |
234 CFNumberCreate(nullptr, kCFNumberSInt32Type, &value)); | |
235 return SetSessionProperty(session, key, value, cfvalue); | |
236 } | |
237 | |
238 static bool SetSessionProperty(VTSessionRef session, | |
239 CFStringRef key, | |
240 bool value) { | |
241 CFBooleanRef cfvalue = (value) ? kCFBooleanTrue : kCFBooleanFalse; | |
242 return SetSessionProperty(session, key, value, cfvalue); | |
243 } | |
244 | |
245 static bool SetSessionProperty(VTSessionRef session, | |
246 CFStringRef key, | |
247 CFStringRef value) { | |
248 return SetSessionProperty(session, key, value, value); | |
249 } | |
250 | |
251 static base::ScopedCFTypeRef<CFDictionaryRef> DictionaryWithKeyValue( | |
252 CFTypeRef key, | |
253 CFTypeRef value) { | |
254 CFTypeRef keys[1] = {key}; | |
255 CFTypeRef values[1] = {value}; | |
256 return base::ScopedCFTypeRef<CFDictionaryRef>( | |
257 CFDictionaryCreate(kCFAllocatorDefault, | |
258 keys, | |
259 values, | |
260 1, | |
261 &kCFTypeDictionaryKeyCallBacks, | |
262 &kCFTypeDictionaryValueCallBacks)); | |
263 } | |
264 | |
265 #pragma mark - | |
266 | |
267 struct H264VideoToolboxEncoder::FrameContext { | |
268 base::TimeTicks capture_time; | |
269 FrameEncodedCallback frame_encoded_callback; | |
270 }; | |
271 | |
272 H264VideoToolboxEncoder::H264VideoToolboxEncoder( | |
273 scoped_refptr<CastEnvironment> cast_environment, | |
274 const VideoSenderConfig& video_config) | |
275 : cast_environment_(cast_environment), | |
276 cast_config_(video_config), | |
277 frame_id_(kStartFrameId), | |
278 last_keyframe_id_(kStartFrameId), | |
279 encode_next_frame_as_keyframe_(false) { | |
280 Initialize(); | |
281 } | |
282 | |
283 H264VideoToolboxEncoder::~H264VideoToolboxEncoder() { | |
284 Teardown(); | |
285 } | |
286 | |
287 CVPixelBufferPoolRef H264VideoToolboxEncoder::cv_pixel_buffer_pool() const { | |
288 DCHECK(thread_checker_.CalledOnValidThread()); | |
289 DCHECK(compression_session_); | |
290 return VTCompressionSessionGetPixelBufferPool(compression_session_); | |
291 } | |
292 | |
293 void H264VideoToolboxEncoder::Initialize() { | |
294 DCHECK(thread_checker_.CalledOnValidThread()); | |
295 DCHECK(!compression_session_); | |
296 | |
297 DVLOG(3) << __func__ | |
298 << " width: " << cast_config_.width | |
299 << ", height: " << cast_config_.height | |
300 << ", start_bitrate: " << cast_config_.start_bitrate | |
301 << ", max_frame_rate:" << cast_config_.max_frame_rate; | |
302 | |
303 // create the VT compression session | |
304 | |
305 // Chrome manages input and output buffers and does not allow an encoder to | |
306 // provide buffers through the VideoEncodeAccelerator interface, so we pass | |
307 // null for |sourceImageBufferAttributes|, which otherwise causes VT to | |
308 // allocate a suitable pixel buffer pool. | |
309 | |
310 // Note that the session context is given to the compression session as the | |
311 // callback context using a raw pointer. The C API does not allow us to use | |
312 // a smart pointer. However, this is still safe, because the output callback | |
313 // can only execute while this vea is alive (i.e. we fully own the | |
314 // compression session). This is enforced by a thread join inside | |
315 // VideoToolbox when invalidating the compression session in Destroy(). The | |
316 // only race left to worry about is when dispatching back to the vea thread. | |
317 // This is taken care of by using a smart pointer that will retain the | |
318 // session context for the duration of the task. | |
319 | |
320 // On OS X, allow the hardware encoder. Don't require it, it does not support | |
321 // all configurations (some of which are used for testing). | |
322 DictionaryPtr encoder_spec(nullptr); | |
323 #if !defined(OS_IOS) | |
324 encoder_spec = DictionaryWithKeyValue( | |
325 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder, | |
326 kCFBooleanTrue); | |
327 #endif | |
328 | |
329 VTCompressionSessionRef session; | |
330 OSStatus status = | |
331 VTCompressionSessionCreate(kCFAllocatorDefault, | |
332 cast_config_.width, | |
333 cast_config_.height, | |
334 kCMVideoCodecType_H264, | |
335 encoder_spec, | |
336 nullptr /* sourceImageBufferAttributes */, | |
337 nullptr /* compressedDataAllocator */, | |
338 CompressionCallback, | |
339 reinterpret_cast<void*>(this), | |
340 &session); | |
341 if (status != noErr) { | |
342 DLOG(ERROR) << __func__ << " VTCompressionSessionCreate failed: " | |
343 << GetVTErrorString(status) << " (" << status << ")"; | |
344 return; | |
345 } | |
346 compression_session_.reset(session); | |
347 | |
348 // query if we're using hardware | |
349 #if defined(OS_IOS) | |
350 using_hardware_ = true; | |
351 #else | |
352 CFBooleanRef using_hardware_cf = nullptr; | |
353 status = VTSessionCopyProperty( | |
354 session, | |
355 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder, | |
356 kCFAllocatorDefault, | |
357 &using_hardware_cf); | |
358 if (status == noErr) { | |
359 using_hardware_ = CFBooleanGetValue(using_hardware_cf); | |
360 CFRelease(using_hardware_cf); | |
361 } | |
362 DVLOG(3) << __func__ << " using hardware: " << using_hardware_; | |
363 #endif | |
364 | |
365 // configure the session | |
366 ConfigureSession(); | |
367 } | |
368 | |
369 static void SetConfigurationApplier(CFStringRef key, | |
370 CFTypeRef value, | |
371 VTCompressionSessionRef session) { | |
372 SetSessionProperty(session, key, CFTypeEmittable(value), value); | |
miu
2014/08/08 17:53:08
Here and in ConfigureSession() below:
If you're g
jfroy
2014/08/08 23:23:24
The final SetSessionProperty used to log the error
| |
373 } | |
374 | |
375 void H264VideoToolboxEncoder::ConfigureSession() { | |
376 SetSessionProperty(compression_session_, | |
377 kVTCompressionPropertyKey_ProfileLevel, | |
378 kVTProfileLevel_H264_Main_AutoLevel); | |
379 | |
380 SetSessionProperty(compression_session_, | |
381 kVTCompressionPropertyKey_RealTime, | |
382 true); | |
383 SetSessionProperty(compression_session_, | |
384 kVTCompressionPropertyKey_AllowFrameReordering, | |
385 false); | |
386 SetSessionProperty(compression_session_, | |
387 kVTCompressionPropertyKey_MaxKeyFrameInterval, | |
388 240u); | |
389 SetSessionProperty(compression_session_, | |
390 kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, | |
391 240u); | |
392 | |
393 SetSessionProperty(compression_session_, | |
394 kVTCompressionPropertyKey_AverageBitRate, | |
395 static_cast<uint32_t>(cast_config_.start_bitrate)); | |
396 | |
397 SetSessionProperty(compression_session_, | |
398 kVTCompressionPropertyKey_ExpectedFrameRate, | |
399 static_cast<uint32_t>(cast_config_.max_frame_rate)); | |
400 | |
401 SetSessionProperty(compression_session_, | |
402 kVTCompressionPropertyKey_ColorPrimaries, | |
403 kCVImageBufferColorPrimaries_ITU_R_709_2); | |
404 SetSessionProperty(compression_session_, | |
405 kVTCompressionPropertyKey_TransferFunction, | |
406 kCVImageBufferTransferFunction_ITU_R_709_2); | |
407 SetSessionProperty(compression_session_, | |
408 kVTCompressionPropertyKey_YCbCrMatrix, | |
409 kCVImageBufferYCbCrMatrix_ITU_R_709_2); | |
410 | |
411 if (compression_properties_) { | |
412 CFDictionaryApplyFunction( | |
413 compression_properties_, | |
414 reinterpret_cast<CFDictionaryApplierFunction>(SetConfigurationApplier), | |
415 compression_session_.get()); | |
416 } | |
417 } | |
418 | |
419 void H264VideoToolboxEncoder::Teardown() { | |
420 DCHECK(thread_checker_.CalledOnValidThread()); | |
421 | |
422 // If the compression session exists, invalidate it. This blocks until all | |
miu
2014/08/08 17:53:07
re: "This blocks"
Never block the IO thread. How
jfroy
2014/08/08 23:23:25
This will run in the video thread presumably. Ther
| |
423 // pending output callbacks have returned and any internal threads have | |
424 // joined, ensuring no output callback ever sees a dangling encoder pointer. | |
425 if (compression_session_) { | |
426 VTCompressionSessionInvalidate(compression_session_); | |
427 compression_session_.reset(); | |
428 } | |
429 } | |
430 | |
431 #pragma mark - | |
432 | |
433 bool H264VideoToolboxEncoder::EncodeVideoFrame( | |
434 const scoped_refptr<media::VideoFrame>& video_frame, | |
435 const base::TimeTicks& capture_time, | |
436 const FrameEncodedCallback& frame_encoded_callback) { | |
437 DCHECK(thread_checker_.CalledOnValidThread()); | |
438 | |
439 if (!compression_session_) { | |
440 DLOG(ERROR) << __func__ << " compression session is null"; | |
441 return false; | |
442 } | |
443 | |
444 // if the VideoFrame wraps a CVPixelBuffer, use that, otherwise wrap it | |
445 PixelBufferPtr pixel_buffer(video_frame->cv_pixel_buffer(), | |
miu
2014/08/08 17:53:07
VideoFrame::cv_pixel_buffer() will return NULL no
jfroy
2014/08/08 23:23:25
No. The idea here is that the frame producer will
| |
446 base::scoped_policy::RETAIN); | |
447 if (!pixel_buffer) { | |
448 pixel_buffer = WrapVideoFrame(*video_frame); | |
449 if (!pixel_buffer) { | |
450 return false; | |
451 } | |
452 } | |
453 | |
454 // convert the frame's timestamp to a CMTime | |
455 CMTime timestamp_cm; | |
456 if (capture_time.is_null()) { | |
miu
2014/08/08 17:53:09
It's an illegal argument for capture_time to be nu
jfroy
2014/08/08 23:23:25
Acknowledged.
| |
457 timestamp_cm = kCMTimeInvalid; | |
458 } else { | |
459 timestamp_cm = CMTimeMake(capture_time.ToInternalValue(), USEC_PER_SEC); | |
miu
2014/08/08 17:53:06
Never use ToInternalValue(). The timestamps are r
jfroy
2014/08/08 23:23:26
CMTime is also relative to a clock (which is not p
miu
2014/08/25 19:21:17
My point here is that ToInternalValue() is explici
jfroy
2014/08/25 21:00:23
Done.
| |
460 } | |
461 | |
462 // allocate frame compression context; will be deleted in the output callback | |
463 FrameContext* fc = new FrameContext(); | |
miu
2014/08/08 17:53:07
coding style: Don't abbreviate variable names.
miu
2014/08/08 17:53:08
For safety, use scoped_ptr<> for this, and call sc
jfroy
2014/08/08 23:23:25
Acknowledged.
jfroy
2014/08/08 23:23:25
Acknowledged.
| |
464 fc->capture_time = capture_time; | |
465 fc->frame_encoded_callback = frame_encoded_callback; | |
466 | |
467 // encode the frame | |
468 DVLOG(3) << __func__ << " pts: " << timestamp_cm; | |
469 | |
470 DictionaryPtr frame_props(nullptr); | |
471 if (encode_next_frame_as_keyframe_) { | |
472 frame_props = DictionaryWithKeyValue(kVTEncodeFrameOptionKey_ForceKeyFrame, | |
473 kCFBooleanTrue); | |
474 encode_next_frame_as_keyframe_ = false; | |
475 } | |
476 | |
477 VTEncodeInfoFlags info; | |
478 OSStatus status = VTCompressionSessionEncodeFrame( | |
479 compression_session_, | |
480 pixel_buffer, | |
481 timestamp_cm, | |
482 kCMTimeInvalid, | |
483 frame_props, | |
484 reinterpret_cast<void*>(fc), | |
485 &info); | |
486 if (status != noErr) { | |
487 DLOG(ERROR) << __func__ << " VTCompressionSessionEncodeFrame failed: " | |
488 << GetVTErrorString(status) << " (" << status << ")"; | |
489 return false; | |
490 } | |
491 if ((info & kVTEncodeInfo_FrameDropped)) { | |
492 DLOG(ERROR) << __func__ << " frame dropped"; | |
493 return false; | |
494 } | |
495 | |
496 return true; | |
497 } | |
498 | |
499 void H264VideoToolboxEncoder::SetBitRate(int new_bit_rate) { | |
500 DCHECK(thread_checker_.CalledOnValidThread()); | |
501 | |
502 DVLOG(3) << __func__ << " bitrate: " << new_bit_rate; | |
503 | |
504 if (!compression_session_) { | |
miu
2014/08/08 17:53:08
Should this be DCHECK(compression_session_) instea
jfroy
2014/08/08 23:23:25
I used a log here because I was worried that since
| |
505 DLOG(ERROR) << __func__ << " compression session is null"; | |
506 return; | |
507 } | |
508 | |
509 // if (new_bit_rate) { | |
miu
2014/08/08 17:53:07
Why is this commented out?
jfroy
2014/08/08 23:23:25
See my comment above.
| |
510 // SetSessionProperty(compression_session_, | |
511 // kVTCompressionPropertyKey_AverageBitRate, | |
512 // static_cast<uint32_t>(new_bit_rate)); | |
513 // } | |
514 } | |
515 | |
516 void H264VideoToolboxEncoder::GenerateKeyFrame() { | |
517 DCHECK(thread_checker_.CalledOnValidThread()); | |
518 DCHECK(compression_session_); | |
519 | |
520 encode_next_frame_as_keyframe_ = true; | |
521 } | |
522 | |
523 void H264VideoToolboxEncoder::LatestFrameIdToReference(uint32 /*frame_id*/) {} | |
miu
2014/08/08 17:53:08
For documentation, can you make the body of this m
jfroy
2014/08/08 23:23:25
Acknowledged.
| |
524 | |
525 #pragma mark - | |
526 | |
527 static void VideoFramePixelBufferReleaseCallback(void* frame_ref, | |
528 const void* data, | |
529 size_t size, | |
530 size_t num_planes, | |
531 const void* planes[]) { | |
532 free(const_cast<void*>(data)); | |
533 reinterpret_cast<media::VideoFrame*>(frame_ref)->Release(); | |
534 } | |
535 | |
536 H264VideoToolboxEncoder::PixelBufferPtr | |
537 H264VideoToolboxEncoder::WrapVideoFrame(media::VideoFrame& frame) { | |
miu
2014/08/08 17:53:08
nit: indent 4 spaces
jfroy
2014/08/08 23:23:24
I've been formatting everything via clang-format /
| |
538 static const size_t MAX_PLANES = 3; | |
539 | |
540 media::VideoFrame::Format format = frame.format(); | |
541 size_t num_planes = media::VideoFrame::NumPlanes(format); | |
542 gfx::Size coded_size = frame.coded_size(); | |
543 | |
544 // media::VideoFrame only supports YUV formats, so there is no way to | |
545 // leverage VideoToolbox's ability to convert RGBA formats automatically. In | |
546 // addition, most of the media::VideoFrame formats are YVU, which VT does not | |
547 // support. Finally, media::VideoFrame formats do not carry any information | |
548 // about the color space, transform or any other colorimetric information | |
549 // that is generally needed to fully specify the input data. So essentially | |
550 // require that the input be YCbCr 4:2:0 (either planar or biplanar) and | |
551 // assume the standard video dynamic range for samples (although most modern | |
552 // HDTVs support full-range video these days). | |
553 OSType pixel_format; | |
554 if (format == media::VideoFrame::Format::I420) { | |
555 pixel_format = kCVPixelFormatType_420YpCbCr8Planar; | |
556 } else if (format == media::VideoFrame::Format::NV12) { | |
557 pixel_format = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; | |
558 } else { | |
559 DLOG(ERROR) << __func__ << " unsupported frame format: " << format; | |
560 return PixelBufferPtr(nullptr); | |
561 } | |
562 | |
563 // TODO(jfroy): support extended pixels (i.e. padding) | |
564 if (frame.coded_size() != frame.visible_rect().size()) { | |
565 DLOG(ERROR) << __func__ << " frame with extended pixels not supported: " | |
566 << " coded_size: " << coded_size.ToString() | |
567 << ", visible_rect: " << frame.visible_rect().ToString(); | |
568 return PixelBufferPtr(nullptr); | |
569 } | |
570 | |
571 DCHECK(media::VideoFrame::NumPlanes(format) <= MAX_PLANES); | |
miu
2014/08/08 17:53:07
Consider moving this DCHECK up to line 542, so the
jfroy
2014/08/08 23:23:25
Acknowledged.
| |
572 void* plane_ptrs[MAX_PLANES]; | |
573 size_t plane_widths[MAX_PLANES]; | |
574 size_t plane_heights[MAX_PLANES]; | |
575 size_t plane_bytes_per_row[MAX_PLANES]; | |
576 for (size_t plane_i = 0; plane_i < num_planes; ++plane_i) { | |
577 plane_ptrs[plane_i] = frame.data(plane_i); | |
578 gfx::Size plane_size = | |
579 media::VideoFrame::PlaneSize(format, plane_i, coded_size); | |
580 plane_widths[plane_i] = plane_size.width(); | |
581 plane_heights[plane_i] = plane_size.height(); | |
582 plane_bytes_per_row[plane_i] = frame.stride(plane_i); | |
583 } | |
584 | |
585 // CVPixelBufferCreateWithPlanarBytes needs a dummy plane descriptor or the | |
586 // release callback will not execute | |
587 void* descriptor = | |
588 calloc(1, | |
miu
2014/08/08 17:53:08
nit: malloc(std::max(...));
jfroy
2014/08/08 23:23:25
The memory needs to be zeroed. I thought calloc wa
| |
589 std::max(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar), | |
590 sizeof(CVPlanarPixelBufferInfo_YCbCrBiPlanar))); | |
591 | |
592 CVPixelBufferRef pixel_buffer; | |
593 CVReturn result = | |
594 CVPixelBufferCreateWithPlanarBytes(kCFAllocatorDefault, | |
595 coded_size.width(), | |
596 coded_size.height(), | |
597 format, | |
598 &descriptor, | |
miu
2014/08/08 17:53:07
I think you meant descriptor and not &descriptor h
miu
2014/08/08 17:53:08
It's weird that you're passing an uninitialized st
jfroy
2014/08/08 23:23:24
Good catch.
jfroy
2014/08/08 23:23:25
Yes, I've extensively tested this behavior, decomp
| |
599 0, | |
600 num_planes, | |
601 plane_ptrs, | |
602 plane_widths, | |
603 plane_heights, | |
604 plane_bytes_per_row, | |
605 VideoFramePixelBufferReleaseCallback, | |
606 &frame, | |
miu
2014/08/08 17:53:09
You meant frame.get(), not &frame.
jfroy
2014/08/08 23:23:24
No, this is correct, since frame here is a media::
| |
607 nullptr, | |
608 &pixel_buffer); | |
609 if (result != kCVReturnSuccess) { | |
610 DLOG(ERROR) << __func__ << " CVPixelBufferCreateWithPlanarBytes failed: " | |
611 << GetCVErrorString(result) << " (" << result << ")"; | |
612 return PixelBufferPtr(nullptr); | |
613 } | |
614 | |
615 // The CVPixelBuffer now references the data of the frame, so increment its | |
616 // reference count manually. The release callback set on the pixel buffer will | |
617 // release the frame. | |
618 frame.AddRef(); | |
619 | |
620 return PixelBufferPtr(pixel_buffer); | |
621 } | |
622 | |
623 #pragma mark - | |
624 | |
625 void H264VideoToolboxEncoder::CompressionCallback( | |
626 void* encoder_opaque, | |
627 void* frame_opaque, | |
628 OSStatus status, | |
629 VTEncodeInfoFlags info, | |
630 CMSampleBufferRef sbuf) { | |
631 H264VideoToolboxEncoder* encoder = | |
632 reinterpret_cast<H264VideoToolboxEncoder*>(encoder_opaque); | |
633 scoped_ptr<FrameContext> fc(reinterpret_cast<FrameContext*>(frame_opaque)); | |
miu
2014/08/08 17:53:06
style: Don't abbreviating variable names.
jfroy
2014/08/08 23:23:24
Acknowledged.
| |
634 | |
635 // if encoding failed, report a platform error and bail | |
636 if (status != noErr) { | |
637 DLOG(ERROR) << __func__ << " encoding failed: " << GetVTErrorString(status) | |
638 << " (" << status << ")"; | |
639 return; | |
640 } | |
641 | |
642 // if the frame had to be dropped, bail | |
643 if ((info & kVTEncodeInfo_FrameDropped)) { | |
644 DVLOG(2) << __func__ << " frame dropped"; | |
645 return; | |
646 } | |
647 | |
648 // implementation only supports one frame (sample) per sample buffer | |
649 CMItemCount sample_count = CMSampleBufferGetNumSamples(sbuf); | |
650 if (sample_count > 1) { | |
miu
2014/08/08 17:53:07
Is this expected? Perhaps this should be DCHECK_E
jfroy
2014/08/08 23:23:24
For video, not likely (it certainly is for audio).
| |
651 DLOG(ERROR) << __func__ | |
652 << " more than one sample in sample buffer: " << sample_count; | |
653 return; | |
654 } | |
655 | |
656 // get the sample attachments, which will tell us if this is a keyframe | |
657 CFDictionaryRef sample_attachments = | |
658 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex( | |
659 CMSampleBufferGetSampleAttachmentsArray(sbuf, true), 0)); | |
660 | |
661 // If the NotSync key is not present, it implies Sync, which indicates a | |
662 // keyframe (at least I think, VT documentation is, erm, sparse). Could | |
663 // alternatively use kCMSampleAttachmentKey_DependsOnOthers == false. | |
664 bool keyframe = | |
665 CFDictionaryContainsKey(sample_attachments, | |
666 kCMSampleAttachmentKey_NotSync) == false; | |
667 | |
668 // Generate a frame id and update the last keyframe id if needed. | |
669 // NOTE: VideoToolbox calls the output callback serially, so this is safe. | |
670 uint32 frame_id = ++encoder->frame_id_; | |
671 if (keyframe) { | |
672 encoder->last_keyframe_id_ = frame_id; | |
673 } | |
674 | |
675 CMSampleTimingInfo timing_info; | |
miu
2014/08/08 17:53:09
Should you be mapping timing_info back into a capt
jfroy
2014/08/08 23:23:24
Didn't know you could do that. The timing info was
| |
676 CMSampleBufferGetSampleTimingInfo(sbuf, 0, &timing_info); | |
677 DVLOG(3) << __func__ | |
678 << ", timing info: " << timing_info | |
679 << ", keyframe: " << keyframe | |
680 << ", frame id: " << frame_id; | |
681 | |
682 // create an EncodedFrame and fill in the basic information | |
683 scoped_ptr<EncodedFrame> encoded_frame(new EncodedFrame()); | |
684 encoded_frame->frame_id = frame_id; | |
685 encoded_frame->reference_time = fc->capture_time; | |
686 encoded_frame->rtp_timestamp = GetVideoRtpTimestamp(fc->capture_time); | |
687 if (keyframe) { | |
688 encoded_frame->dependency = EncodedFrame::KEY; | |
689 encoded_frame->referenced_frame_id = frame_id; | |
690 } else { | |
691 encoded_frame->dependency = EncodedFrame::DEPENDENT; | |
692 // NOTE: Technically wrong, but without parsing the NALs our best guess is | |
693 // the last keyframe. | |
694 encoded_frame->referenced_frame_id = encoder->last_keyframe_id_; | |
miu
2014/08/08 17:53:07
This needs to be true to the H264 codec. If you d
jfroy
2014/08/08 23:23:25
H.264 is allowed multiple frame references backwar
| |
695 } | |
696 | |
697 // copy the frame data from the CM sample buffer to the encoded frame | |
698 CopySampleBufferToAnnexBBuffer(sbuf, encoded_frame->data, keyframe); | |
699 | |
700 // post the frame's encoded callback | |
701 encoder->cast_environment_->PostTask( | |
702 CastEnvironment::MAIN, | |
703 FROM_HERE, | |
704 base::Bind(fc->frame_encoded_callback, | |
705 base::Passed(&encoded_frame))); | |
706 } | |
707 | |
708 template <typename NalSizeType> | |
709 static void CopyNalsToAnnexB( | |
710 char* avcc_buffer, | |
711 const size_t avcc_size, | |
712 std::string& annexb_buffer) { | |
miu
2014/08/08 17:53:07
Chromium style prohibits passing by reference. Pa
jfroy
2014/08/08 23:23:25
Yep linting indicated that. Fixed in a later PS.
| |
713 static_assert(sizeof(NalSizeType) == 1 || | |
miu
2014/08/08 17:53:07
Replace static_assert with COMPILE_ASSERT for Chro
jfroy
2014/08/08 23:23:24
Yep, other reviewer pointed it out. Fixed in a lat
| |
714 sizeof(NalSizeType) == 2 || | |
715 sizeof(NalSizeType) == 4, | |
716 "NAL size type has unsupported size"); | |
717 static const char startcode_3[3] = {0, 0, 1}; | |
718 size_t bytes_left = avcc_size; | |
719 while (bytes_left > 0) { | |
720 DCHECK(bytes_left > sizeof(NalSizeType)); | |
721 NalSizeType nal_size; | |
722 base::ReadBigEndian(avcc_buffer, &nal_size); | |
723 bytes_left -= sizeof(NalSizeType); | |
miu
2014/08/08 17:53:08
Consider using base::BigEndianReader instead. It
jfroy
2014/08/08 23:23:25
I can't because BigEndianReader does not have a te
| |
724 avcc_buffer += sizeof(NalSizeType); | |
725 | |
726 DCHECK(bytes_left >= nal_size); | |
727 annexb_buffer.append(startcode_3, sizeof(startcode_3)); | |
728 annexb_buffer.append(avcc_buffer, nal_size); | |
729 bytes_left -= nal_size; | |
730 avcc_buffer += nal_size; | |
731 } | |
732 } | |
733 | |
734 void H264VideoToolboxEncoder::CopySampleBufferToAnnexBBuffer( | |
735 CMSampleBufferRef sbuf, | |
736 std::string& annexb_buffer, | |
miu
2014/08/08 17:53:08
ditto: Pass by pointer, not by reference.
jfroy
2014/08/08 23:23:26
Yep, fixed in a later PS.
| |
737 bool keyframe) { | |
738 // Perform two pass, one to figure out the total output size, and another to | |
739 // copy the data after having performed a single output allocation. Note that | |
740 // we'll allocate a bit more because we'll count 4 bytes instead of 3 for | |
741 // video NALs. | |
742 | |
743 // TODO(jfroy): There is a bug in | |
744 // CMVideoFormatDescriptionGetH264ParameterSetAtIndex, iterate until fail. | |
745 // rdar://17514276 | |
746 | |
747 OSStatus status; | |
748 | |
749 // Get the sample buffer's block buffer and format description. | |
750 CMBlockBufferRef bb = CMSampleBufferGetDataBuffer(sbuf); | |
751 DCHECK(bb); | |
752 CMFormatDescriptionRef fdesc = CMSampleBufferGetFormatDescription(sbuf); | |
753 DCHECK(fdesc); | |
754 | |
755 size_t bb_size = CMBlockBufferGetDataLength(bb); | |
756 size_t total_bytes = bb_size; | |
757 | |
758 size_t pset_count; | |
759 int nal_size_field_bytes; | |
760 status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
761 fdesc, | |
762 0, | |
763 nullptr, | |
764 nullptr, | |
765 &pset_count, | |
766 &nal_size_field_bytes); | |
767 if (status == kCMFormatDescriptionBridgeError_InvalidParameter) { | |
768 // rdar://17514276 | |
769 DLOG(WARNING) << __func__ | |
770 << " assuming 2 parameter sets and 4 bytes NAL length header " | |
771 << "(rdar://17514276)"; | |
772 pset_count = 2; | |
773 nal_size_field_bytes = 4; | |
774 } else if (status != noErr) { | |
775 DLOG(ERROR) << __func__ | |
776 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
777 << status; | |
778 return; | |
779 } | |
780 | |
781 if (keyframe) { | |
782 const uint8_t* pset; | |
783 size_t pset_size; | |
784 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { | |
785 status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
786 fdesc, | |
787 pset_i, | |
788 &pset, | |
789 &pset_size, | |
790 nullptr, | |
791 nullptr); | |
792 if (status != noErr) { | |
793 DLOG(ERROR) << __func__ | |
794 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
795 << status; | |
796 return; | |
797 } | |
798 total_bytes += pset_size + nal_size_field_bytes; | |
799 } | |
800 } | |
801 | |
802 annexb_buffer.reserve(total_bytes); | |
803 | |
804 // Copy all parameter sets before keyframes. | |
805 if (keyframe) { | |
806 const uint8_t* pset; | |
807 size_t pset_size; | |
808 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { | |
809 status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex( | |
810 fdesc, | |
811 pset_i, | |
812 &pset, | |
813 &pset_size, | |
814 nullptr, | |
815 nullptr); | |
816 if (status != noErr) { | |
817 DLOG(ERROR) << __func__ | |
818 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " | |
819 << status; | |
820 return; | |
821 } | |
822 static const char startcode_4[4] = {0, 0, 0, 1}; | |
823 annexb_buffer.append(startcode_4, sizeof(startcode_4)); | |
824 annexb_buffer.append(reinterpret_cast<const char*>(pset), pset_size); | |
825 } | |
826 } | |
827 | |
828 // Block buffers can be composed of non-contiguous chunks. For the sake of | |
829 // keeping this code simple, flatten non-contiguous block buffers. | |
830 base::ScopedCFTypeRef<CMBlockBufferRef> contiguous_bb( | |
831 bb, base::scoped_policy::RETAIN); | |
832 if (!CMBlockBufferIsRangeContiguous(bb, 0, 0)) { | |
833 DVLOG(3) << __func__ << " copying block buffer to contiguous buffer"; | |
834 contiguous_bb.reset(); | |
835 status = CMBlockBufferCreateContiguous(kCFAllocatorDefault, | |
836 bb, | |
837 kCFAllocatorDefault, | |
838 nullptr, | |
839 0, | |
840 0, | |
841 0, | |
842 contiguous_bb.InitializeInto()); | |
843 if (status != noErr) { | |
844 DLOG(ERROR) << __func__ << " CMBlockBufferCreateContiguous failed: " | |
845 << status; | |
846 return; | |
847 } | |
848 | |
849 } | |
850 | |
851 // Copy all the NAL units. In the process convert them from AVCC format | |
852 // (length header) to AnnexB format (start code). | |
853 char* bb_data; | |
854 status = CMBlockBufferGetDataPointer(contiguous_bb, | |
855 0, | |
856 nullptr, | |
857 nullptr, | |
858 &bb_data); | |
859 if (status != noErr) { | |
860 DLOG(ERROR) << __func__ << " CMBlockBufferGetDataPointer failed: " | |
861 << status; | |
862 return; | |
863 } | |
864 | |
865 if (nal_size_field_bytes == 1) { | |
866 CopyNalsToAnnexB<uint8_t>(bb_data, bb_size, annexb_buffer); | |
867 } else if (nal_size_field_bytes == 2) { | |
868 CopyNalsToAnnexB<uint16_t>(bb_data, bb_size, annexb_buffer); | |
869 } else if (nal_size_field_bytes == 4) { | |
870 CopyNalsToAnnexB<uint32_t>(bb_data, bb_size, annexb_buffer); | |
871 } | |
872 } | |
873 | |
874 } // namespace cast | |
875 } // namespace media | |
OLD | NEW |