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

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: Fix lint and formatting issues. 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
« no previous file with comments | « media/cast/sender/h264_vt_encoder.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 <algorithm>
8 #include <vector>
9
10 #include "base/big_endian.h"
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/location.h"
14 #include "base/logging.h"
15
16 namespace media {
17 namespace cast {
18
19 static const char* GetCVErrorString(CVReturn error) {
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) {
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 -
Robert Sesek 2014/08/07 22:49:23 Chrome doesn't typically do this.
jfroy 2014/08/07 23:06:11 Acknowledged.
122
123 // utility to log CFTypes
124
125 std::ostream& operator<<(std::ostream& out, const CFStringRef& cfstring) {
Robert Sesek 2014/08/07 22:49:22 All these osteram functions are just to support DL
jfroy 2014/08/07 23:06:11 Acknowledged.
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) {
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 std::ostream& operator<<(std::ostream& out, const CMTime& time) {
198 return out << "{value=" << time.value << ", timescale=" << time.timescale
199 << ", flags=" << time.flags << ", epoch=" << time.epoch << "}";
200 }
201
202 std::ostream& operator<<(std::ostream& out,
203 const CMSampleTimingInfo& timing_info) {
204 return out << "{duration=" << timing_info.duration
205 << ", pts=" << timing_info.presentationTimeStamp
206 << ", dts=" << timing_info.decodeTimeStamp << "}";
207 }
208
209 #pragma mark -
210
211 template <typename T>
212 bool SetSessionProperty(VTSessionRef session,
Robert Sesek 2014/08/07 22:49:22 Is this really templatized for just logging purpos
jfroy 2014/08/07 23:06:11 Mostly yes, but it's also nice to have setters wit
213 CFStringRef key,
214 T value,
215 CFTypeRef cfvalue) {
216 DVLOG(3) << __func__ << ": " << key << "=" << value;
217 OSStatus status = VTSessionSetProperty(session, key, cfvalue);
218 if (status != noErr) {
219 DLOG(ERROR) << __func__
220 << " VTSessionSetProperty failed: " << GetVTErrorString(status)
221 << " (" << status << ") " << key << "=" << value;
222 }
223 return status == noErr;
224 }
225
226 static bool SetSessionProperty(VTSessionRef session,
227 CFStringRef key,
228 uint32_t value) {
229 base::ScopedCFTypeRef<CFNumberRef> cfvalue(
230 CFNumberCreate(nullptr, kCFNumberSInt32Type, &value));
231 return SetSessionProperty(session, key, value, cfvalue);
232 }
233
234 static bool SetSessionProperty(VTSessionRef session,
235 CFStringRef key,
236 bool value) {
237 CFBooleanRef cfvalue = (value) ? kCFBooleanTrue : kCFBooleanFalse;
238 return SetSessionProperty(session, key, value, cfvalue);
239 }
240
241 static bool SetSessionProperty(VTSessionRef session,
242 CFStringRef key,
243 CFStringRef value) {
244 return SetSessionProperty(session, key, value, value);
245 }
246
247 static base::ScopedCFTypeRef<CFDictionaryRef> DictionaryWithKeyValue(
248 CFTypeRef key,
249 CFTypeRef value) {
250 CFTypeRef keys[1] = {key};
251 CFTypeRef values[1] = {value};
252 return base::ScopedCFTypeRef<CFDictionaryRef>(
253 CFDictionaryCreate(kCFAllocatorDefault,
254 keys,
255 values,
256 1,
257 &kCFTypeDictionaryKeyCallBacks,
258 &kCFTypeDictionaryValueCallBacks));
259 }
260
261 #pragma mark -
262
263 struct H264VideoToolboxEncoder::FrameContext {
264 base::TimeTicks capture_time;
265 FrameEncodedCallback frame_encoded_callback;
266 };
267
268 H264VideoToolboxEncoder::H264VideoToolboxEncoder(
269 scoped_refptr<CastEnvironment> cast_environment,
270 const VideoSenderConfig& video_config)
271 : cast_environment_(cast_environment),
272 cast_config_(video_config),
273 frame_id_(kStartFrameId),
274 last_keyframe_id_(kStartFrameId),
275 encode_next_frame_as_keyframe_(false) {
276 Initialize();
Robert Sesek 2014/08/07 22:49:23 You only call this here, so why is it a separate m
jfroy 2014/08/07 23:06:11 The current code doesn't do it, but I have a suspi
277 }
278
279 H264VideoToolboxEncoder::~H264VideoToolboxEncoder() {
280 Teardown();
Robert Sesek 2014/08/07 22:49:22 Same.
jfroy 2014/08/07 23:06:11 See my reply above.
281 }
282
283 CVPixelBufferPoolRef H264VideoToolboxEncoder::cv_pixel_buffer_pool() const {
284 DCHECK(thread_checker_.CalledOnValidThread());
285 DCHECK(compression_session_);
286 return VTCompressionSessionGetPixelBufferPool(compression_session_);
287 }
288
289 void H264VideoToolboxEncoder::Initialize() {
290 DCHECK(thread_checker_.CalledOnValidThread());
291 DCHECK(!compression_session_);
292
293 DVLOG(3) << __func__ << " width: " << cast_config_.width
294 << ", height: " << cast_config_.height
295 << ", start_bitrate: " << cast_config_.start_bitrate
296 << ", max_frame_rate:" << cast_config_.max_frame_rate;
297
298 // Note that the encoder object is given to the compression session as the
299 // callback context using a raw pointer. The C API does not allow us to use
300 // a smart pointer, nor is this encoder ref counted. However, this is still
301 // safe, because we 1) we own the compression session and 2) we tear it down
302 // safely. When destructing the encoder, the compression session is flushed
303 // and invalidated. Internally, VideoToolbox will join all of its threads
304 // before returning to the client. Therefore, when control returns to us, we
305 // are guaranteed that the output callback will not execute again.
306
307 // On OS X, allow the hardware encoder. Don't require it, it does not support
308 // all configurations (some of which are used for testing).
309 DictionaryPtr encoder_spec(nullptr);
310 #if !defined(OS_IOS)
311 encoder_spec = DictionaryWithKeyValue(
312 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
313 kCFBooleanTrue);
314 #endif
315
316 VTCompressionSessionRef session;
317 OSStatus status =
318 VTCompressionSessionCreate(kCFAllocatorDefault,
319 cast_config_.width,
320 cast_config_.height,
321 kCMVideoCodecType_H264,
322 encoder_spec,
323 nullptr /* sourceImageBufferAttributes */,
324 nullptr /* compressedDataAllocator */,
325 CompressionCallback,
326 reinterpret_cast<void*>(this),
327 &session);
328 if (status != noErr) {
329 DLOG(ERROR) << __func__ << " VTCompressionSessionCreate failed: "
330 << GetVTErrorString(status) << " (" << status << ")";
331 return;
332 }
333 compression_session_.reset(session);
334
335 #if defined(OS_IOS)
336 using_hardware_ = true;
337 #else
338 CFBooleanRef using_hardware_cf = nullptr;
339 status = VTSessionCopyProperty(
340 session,
341 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
342 kCFAllocatorDefault,
343 &using_hardware_cf);
344 if (status == noErr) {
345 using_hardware_ = CFBooleanGetValue(using_hardware_cf);
346 CFRelease(using_hardware_cf);
347 }
348 DVLOG(3) << __func__ << " using hardware: " << using_hardware_;
349 #endif
350
351 ConfigureSession();
352 }
353
354 static void SetConfigurationApplier(CFStringRef key,
355 CFTypeRef value,
356 VTCompressionSessionRef session) {
357 SetSessionProperty(session, key, CFTypeEmittable(value), value);
358 }
359
360 void H264VideoToolboxEncoder::ConfigureSession() {
361 SetSessionProperty(compression_session_,
362 kVTCompressionPropertyKey_ProfileLevel,
363 kVTProfileLevel_H264_Main_AutoLevel);
364
365 SetSessionProperty(
366 compression_session_, kVTCompressionPropertyKey_RealTime, true);
367 SetSessionProperty(compression_session_,
368 kVTCompressionPropertyKey_AllowFrameReordering,
369 false);
370 SetSessionProperty(compression_session_,
371 kVTCompressionPropertyKey_MaxKeyFrameInterval,
372 240u);
373 SetSessionProperty(compression_session_,
374 kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration,
375 240u);
376
377 SetSessionProperty(compression_session_,
378 kVTCompressionPropertyKey_AverageBitRate,
379 static_cast<uint32_t>(cast_config_.start_bitrate));
380
381 SetSessionProperty(compression_session_,
382 kVTCompressionPropertyKey_ExpectedFrameRate,
383 static_cast<uint32_t>(cast_config_.max_frame_rate));
384
385 SetSessionProperty(compression_session_,
386 kVTCompressionPropertyKey_ColorPrimaries,
387 kCVImageBufferColorPrimaries_ITU_R_709_2);
388 SetSessionProperty(compression_session_,
389 kVTCompressionPropertyKey_TransferFunction,
390 kCVImageBufferTransferFunction_ITU_R_709_2);
391 SetSessionProperty(compression_session_,
392 kVTCompressionPropertyKey_YCbCrMatrix,
393 kCVImageBufferYCbCrMatrix_ITU_R_709_2);
394
395 if (compression_properties_) {
396 CFDictionaryApplyFunction(
397 compression_properties_,
398 reinterpret_cast<CFDictionaryApplierFunction>(SetConfigurationApplier),
399 compression_session_.get());
400 }
401 }
402
403 void H264VideoToolboxEncoder::Teardown() {
404 DCHECK(thread_checker_.CalledOnValidThread());
405
406 // If the compression session exists, invalidate it. This blocks until all
407 // pending output callbacks have returned and any internal threads have
408 // joined, ensuring no output callback ever sees a dangling encoder pointer.
409 if (compression_session_) {
410 VTCompressionSessionInvalidate(compression_session_);
411 compression_session_.reset();
412 }
413 }
414
415 #pragma mark -
416
417 bool H264VideoToolboxEncoder::EncodeVideoFrame(
418 const scoped_refptr<media::VideoFrame>& video_frame,
419 const base::TimeTicks& capture_time,
420 const FrameEncodedCallback& frame_encoded_callback) {
421 DCHECK(thread_checker_.CalledOnValidThread());
422
423 if (!compression_session_) {
424 DLOG(ERROR) << __func__ << " compression session is null";
425 return false;
426 }
427
428 PixelBufferPtr pixel_buffer(video_frame->cv_pixel_buffer(),
429 base::scoped_policy::RETAIN);
430 if (!pixel_buffer) {
431 pixel_buffer = WrapVideoFrame(*video_frame);
432 if (!pixel_buffer) {
433 return false;
434 }
435 }
436
437 CMTime timestamp_cm;
438 if (capture_time.is_null()) {
439 timestamp_cm = kCMTimeInvalid;
440 } else {
441 timestamp_cm = CMTimeMake(capture_time.ToInternalValue(), USEC_PER_SEC);
442 }
443
444 FrameContext* fc = new FrameContext();
445 fc->capture_time = capture_time;
446 fc->frame_encoded_callback = frame_encoded_callback;
447
448 DVLOG(3) << __func__ << " pts: " << timestamp_cm;
449
450 DictionaryPtr frame_props(nullptr);
451 if (encode_next_frame_as_keyframe_) {
452 frame_props = DictionaryWithKeyValue(kVTEncodeFrameOptionKey_ForceKeyFrame,
453 kCFBooleanTrue);
454 encode_next_frame_as_keyframe_ = false;
455 }
456
457 VTEncodeInfoFlags info;
458 OSStatus status = VTCompressionSessionEncodeFrame(compression_session_,
459 pixel_buffer,
460 timestamp_cm,
461 kCMTimeInvalid,
462 frame_props,
463 reinterpret_cast<void*>(fc),
464 &info);
465 if (status != noErr) {
466 DLOG(ERROR) << __func__ << " VTCompressionSessionEncodeFrame failed: "
467 << GetVTErrorString(status) << " (" << status << ")";
468 return false;
469 }
470 if ((info & kVTEncodeInfo_FrameDropped)) {
471 DLOG(ERROR) << __func__ << " frame dropped";
472 return false;
473 }
474
475 return true;
476 }
477
478 void H264VideoToolboxEncoder::SetBitRate(int new_bit_rate) {
479 DCHECK(thread_checker_.CalledOnValidThread());
480 // NOTE: VideoToolbox does not seem to support bitrate reconfiguration.
481 }
482
483 void H264VideoToolboxEncoder::GenerateKeyFrame() {
484 DCHECK(thread_checker_.CalledOnValidThread());
485 DCHECK(compression_session_);
486
487 encode_next_frame_as_keyframe_ = true;
488 }
489
490 void H264VideoToolboxEncoder::LatestFrameIdToReference(uint32 /*frame_id*/) {
491 }
492
493 #pragma mark -
494
495 static void VideoFramePixelBufferReleaseCallback(void* frame_ref,
496 const void* data,
497 size_t size,
498 size_t num_planes,
499 const void* planes[]) {
500 free(const_cast<void*>(data));
501 reinterpret_cast<media::VideoFrame*>(frame_ref)->Release();
502 }
503
504 H264VideoToolboxEncoder::PixelBufferPtr H264VideoToolboxEncoder::WrapVideoFrame(
505 const scoped_refptr<media::VideoFrame>& frame) {
506 static const size_t MAX_PLANES = 3;
507
508 media::VideoFrame::Format format = frame->format();
509 size_t num_planes = media::VideoFrame::NumPlanes(format);
510 gfx::Size coded_size = frame->coded_size();
511
512 // media::VideoFrame only supports YUV formats, so there is no way to
513 // leverage VideoToolbox's ability to convert RGBA formats automatically. In
514 // addition, most of the media::VideoFrame formats are YVU, which VT does not
515 // support. Finally, media::VideoFrame formats do not carry any information
516 // about the color space, transform or any other colorimetric information
517 // that is generally needed to fully specify the input data. So essentially
518 // require that the input be YCbCr 4:2:0 (either planar or biplanar) and
519 // assume the standard video dynamic range for samples (although most modern
520 // HDTVs support full-range video these days).
521 OSType pixel_format;
522 if (format == media::VideoFrame::Format::I420) {
523 pixel_format = kCVPixelFormatType_420YpCbCr8Planar;
524 } else if (format == media::VideoFrame::Format::NV12) {
525 pixel_format = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
526 } else {
527 DLOG(ERROR) << __func__ << " unsupported frame format: " << format;
528 return PixelBufferPtr(nullptr);
529 }
530
531 // TODO(jfroy): Support extended pixels (i.e. padding).
532 if (frame->coded_size() != frame->visible_rect().size()) {
533 DLOG(ERROR) << __func__ << " frame with extended pixels not supported: "
534 << " coded_size: " << coded_size.ToString()
535 << ", visible_rect: " << frame->visible_rect().ToString();
536 return PixelBufferPtr(nullptr);
537 }
538
539 DCHECK(media::VideoFrame::NumPlanes(format) <= MAX_PLANES);
540 void* plane_ptrs[MAX_PLANES];
541 size_t plane_widths[MAX_PLANES];
542 size_t plane_heights[MAX_PLANES];
543 size_t plane_bytes_per_row[MAX_PLANES];
544 for (size_t plane_i = 0; plane_i < num_planes; ++plane_i) {
545 plane_ptrs[plane_i] = frame->data(plane_i);
546 gfx::Size plane_size =
547 media::VideoFrame::PlaneSize(format, plane_i, coded_size);
548 plane_widths[plane_i] = plane_size.width();
549 plane_heights[plane_i] = plane_size.height();
550 plane_bytes_per_row[plane_i] = frame->stride(plane_i);
551 }
552
553 // CVPixelBufferCreateWithPlanarBytes needs a dummy plane descriptor or the
554 // release callback will not execute. The descriptor is freed in the callback.
555 void* descriptor =
556 calloc(1,
557 std::max(sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar),
558 sizeof(CVPlanarPixelBufferInfo_YCbCrBiPlanar)));
559
560 // Wrap the frame's data in a CVPixelBuffer. Because this is a C API, we can't
561 // give it a smart pointer to the frame, so instead pass a raw pointer and
562 // increment the frame's reference count manually.
563 CVPixelBufferRef pixel_buffer;
564 CVReturn result =
565 CVPixelBufferCreateWithPlanarBytes(kCFAllocatorDefault,
566 coded_size.width(),
567 coded_size.height(),
568 format,
569 &descriptor,
570 0,
571 num_planes,
572 plane_ptrs,
573 plane_widths,
574 plane_heights,
575 plane_bytes_per_row,
576 VideoFramePixelBufferReleaseCallback,
577 frame.get(),
578 nullptr,
579 &pixel_buffer);
580 if (result != kCVReturnSuccess) {
581 DLOG(ERROR) << __func__ << " CVPixelBufferCreateWithPlanarBytes failed: "
582 << GetCVErrorString(result) << " (" << result << ")";
583 return PixelBufferPtr(nullptr);
584 }
585
586 // The CVPixelBuffer now references the data of the frame, so increment its
587 // reference count manually. The release callback set on the pixel buffer will
588 // release the frame.
589 frame.AddRef();
590
591 return PixelBufferPtr(pixel_buffer);
592 }
593
594 #pragma mark -
595
596 void H264VideoToolboxEncoder::CompressionCallback(void* encoder_opaque,
597 void* frame_opaque,
598 OSStatus status,
599 VTEncodeInfoFlags info,
600 CMSampleBufferRef sbuf) {
601 H264VideoToolboxEncoder* encoder =
602 reinterpret_cast<H264VideoToolboxEncoder*>(encoder_opaque);
603 scoped_ptr<FrameContext> fc(reinterpret_cast<FrameContext*>(frame_opaque));
604
605 if (status != noErr) {
606 DLOG(ERROR) << __func__ << " encoding failed: " << GetVTErrorString(status)
607 << " (" << status << ")";
608 return;
609 }
610 if ((info & kVTEncodeInfo_FrameDropped)) {
611 DVLOG(2) << __func__ << " frame dropped";
612 return;
613 }
614 CMItemCount sample_count = CMSampleBufferGetNumSamples(sbuf);
615 if (sample_count > 1) {
616 DLOG(ERROR) << __func__
617 << " more than one sample in sample buffer: " << sample_count;
618 return;
619 }
620
621 CFDictionaryRef sample_attachments =
622 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(
623 CMSampleBufferGetSampleAttachmentsArray(sbuf, true), 0));
624
625 // If the NotSync key is not present, it implies Sync, which indicates a
626 // keyframe (at least I think, VT documentation is, erm, sparse). Could
627 // alternatively use kCMSampleAttachmentKey_DependsOnOthers == false.
628 bool keyframe =
629 CFDictionaryContainsKey(sample_attachments,
630 kCMSampleAttachmentKey_NotSync) == false;
631
632 // Generate a frame id and update the last keyframe id if needed.
633 // NOTE: VideoToolbox calls the output callback serially, so this is safe.
634 uint32 frame_id = ++encoder->frame_id_;
635 if (keyframe) {
636 encoder->last_keyframe_id_ = frame_id;
637 }
638
639 CMSampleTimingInfo timing_info;
640 CMSampleBufferGetSampleTimingInfo(sbuf, 0, &timing_info);
641 DVLOG(3) << __func__ << ", timing info: " << timing_info
642 << ", keyframe: " << keyframe << ", frame id: " << frame_id;
643
644 scoped_ptr<EncodedFrame> encoded_frame(new EncodedFrame());
645 encoded_frame->frame_id = frame_id;
646 encoded_frame->reference_time = fc->capture_time;
647 encoded_frame->rtp_timestamp = GetVideoRtpTimestamp(fc->capture_time);
648 if (keyframe) {
649 encoded_frame->dependency = EncodedFrame::KEY;
650 encoded_frame->referenced_frame_id = frame_id;
651 } else {
652 encoded_frame->dependency = EncodedFrame::DEPENDENT;
653 // NOTE: Technically wrong, but without parsing the NALs our best guess is
654 // the last keyframe.
655 encoded_frame->referenced_frame_id = encoder->last_keyframe_id_;
656 }
657
658 CopySampleBufferToAnnexBBuffer(sbuf, &encoded_frame->data, keyframe);
659
660 encoder->cast_environment_->PostTask(
661 CastEnvironment::MAIN,
662 FROM_HERE,
663 base::Bind(fc->frame_encoded_callback, base::Passed(&encoded_frame)));
664 }
665
666 template <typename NalSizeType>
667 static void CopyNalsToAnnexB(char* avcc_buffer,
668 const size_t avcc_size,
669 std::string* annexb_buffer) {
670 static_assert(sizeof(NalSizeType) == 1 || sizeof(NalSizeType) == 2 ||
671 sizeof(NalSizeType) == 4,
672 "NAL size type has unsupported size");
673 static const char startcode_3[3] = {0, 0, 1};
674 DCHECK(avcc_buffer);
675 DCHECK(annexb_buffer);
676 size_t bytes_left = avcc_size;
677 while (bytes_left > 0) {
678 DCHECK(bytes_left > sizeof(NalSizeType));
679 NalSizeType nal_size;
680 base::ReadBigEndian(avcc_buffer, &nal_size);
681 bytes_left -= sizeof(NalSizeType);
682 avcc_buffer += sizeof(NalSizeType);
683
684 DCHECK(bytes_left >= nal_size);
685 annexb_buffer->append(startcode_3, sizeof(startcode_3));
686 annexb_buffer->append(avcc_buffer, nal_size);
687 bytes_left -= nal_size;
688 avcc_buffer += nal_size;
689 }
690 }
691
692 void H264VideoToolboxEncoder::CopySampleBufferToAnnexBBuffer(
693 CMSampleBufferRef sbuf,
694 std::string* annexb_buffer,
695 bool keyframe) {
696 // Perform two pass, one to figure out the total output size, and another to
697 // copy the data after having performed a single output allocation. Note that
698 // we'll allocate a bit more because we'll count 4 bytes instead of 3 for
699 // video NALs.
700
701 // TODO(jfroy): There is a bug in
702 // CMVideoFormatDescriptionGetH264ParameterSetAtIndex, iterate until fail.
703
704 OSStatus status;
705
706 // Get the sample buffer's block buffer and format description.
707 CMBlockBufferRef bb = CMSampleBufferGetDataBuffer(sbuf);
708 DCHECK(bb);
709 CMFormatDescriptionRef fdesc = CMSampleBufferGetFormatDescription(sbuf);
710 DCHECK(fdesc);
711
712 size_t bb_size = CMBlockBufferGetDataLength(bb);
713 size_t total_bytes = bb_size;
714
715 size_t pset_count;
716 int nal_size_field_bytes;
717 status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
718 fdesc, 0, nullptr, nullptr, &pset_count, &nal_size_field_bytes);
719 if (status == kCMFormatDescriptionBridgeError_InvalidParameter) {
720 DLOG(WARNING) << __func__ << " assuming 2 parameter sets and 4 bytes NAL "
721 "length header" pset_count = 2;
722 nal_size_field_bytes = 4;
723 } else if (status != noErr) {
724 DLOG(ERROR)
725 << __func__
726 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: "
727 << status;
728 return;
729 }
730
731 if (keyframe) {
732 const uint8_t* pset;
733 size_t pset_size;
734 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) {
735 status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
736 fdesc, pset_i, &pset, &pset_size, nullptr, nullptr);
737 if (status != noErr) {
738 DLOG(ERROR)
739 << __func__
740 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: "
741 << status;
742 return;
743 }
744 total_bytes += pset_size + nal_size_field_bytes;
745 }
746 }
747
748 annexb_buffer->reserve(total_bytes);
749
750 // Copy all parameter sets before keyframes.
751 if (keyframe) {
752 const uint8_t* pset;
753 size_t pset_size;
754 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) {
755 status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
756 fdesc, pset_i, &pset, &pset_size, nullptr, nullptr);
757 if (status != noErr) {
758 DLOG(ERROR)
759 << __func__
760 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: "
761 << status;
762 return;
763 }
764 static const char startcode_4[4] = {0, 0, 0, 1};
765 annexb_buffer->append(startcode_4, sizeof(startcode_4));
766 annexb_buffer->append(reinterpret_cast<const char*>(pset), pset_size);
767 }
768 }
769
770 // Block buffers can be composed of non-contiguous chunks. For the sake of
771 // keeping this code simple, flatten non-contiguous block buffers.
772 base::ScopedCFTypeRef<CMBlockBufferRef> contiguous_bb(
773 bb, base::scoped_policy::RETAIN);
774 if (!CMBlockBufferIsRangeContiguous(bb, 0, 0)) {
775 DVLOG(3) << __func__ << " copying block buffer to contiguous buffer";
776 contiguous_bb.reset();
777 status = CMBlockBufferCreateContiguous(kCFAllocatorDefault,
778 bb,
779 kCFAllocatorDefault,
780 nullptr,
781 0,
782 0,
783 0,
784 contiguous_bb.InitializeInto());
785 if (status != noErr) {
786 DLOG(ERROR) << __func__
787 << " CMBlockBufferCreateContiguous failed: " << status;
788 return;
789 }
790 }
791
792 // Copy all the NAL units. In the process convert them from AVCC format
793 // (length header) to AnnexB format (start code).
794 char* bb_data;
795 status =
796 CMBlockBufferGetDataPointer(contiguous_bb, 0, nullptr, nullptr, &bb_data);
797 if (status != noErr) {
798 DLOG(ERROR) << __func__
799 << " CMBlockBufferGetDataPointer failed: " << status;
800 return;
801 }
802
803 if (nal_size_field_bytes == 1) {
804 CopyNalsToAnnexB<uint8_t>(bb_data, bb_size, annexb_buffer);
805 } else if (nal_size_field_bytes == 2) {
806 CopyNalsToAnnexB<uint16_t>(bb_data, bb_size, annexb_buffer);
807 } else if (nal_size_field_bytes == 4) {
808 CopyNalsToAnnexB<uint32_t>(bb_data, bb_size, annexb_buffer);
809 }
810 }
811
812 } // namespace cast
813 } // namespace media
OLDNEW
« no previous file with comments | « 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