Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Side by Side Diff: media/cast/sender/h264_vt_encoder.cc

Issue 450693006: VideoToolbox encoder for cast senders. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW
« media/cast/sender/h264_vt_encoder.h ('K') | « media/cast/sender/h264_vt_encoder.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698