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

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 unit test link error. Created 6 years 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 <algorithm>
miu 2014/11/20 23:16:55 Don't think you need this anymore. ...but you do
jfroy 2014/11/20 23:38:30 I don't need algo anymore indeed, but also don't t
8 #include <string>
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 #include "media/base/mac/corevideo_glue.h"
16 #include "media/base/mac/video_frame_mac.h"
17 #include "media/cast/sender/video_frame_factory.h"
18
19 namespace media {
20 namespace cast {
21
22 namespace {
23
24 // Container for the associated data of a video frame being processed.
25 struct InProgressFrameEncode {
26 const RtpTimestamp rtp_timestamp;
27 const base::TimeTicks reference_time;
28 const VideoEncoder::FrameEncodedCallback frame_encoded_callback;
29
30 InProgressFrameEncode(RtpTimestamp rtp,
31 base::TimeTicks r_time,
32 VideoEncoder::FrameEncodedCallback callback)
33 : rtp_timestamp(rtp),
34 reference_time(r_time),
35 frame_encoded_callback(callback) {}
36 };
37
38 base::ScopedCFTypeRef<CFDictionaryRef> DictionaryWithKeyValue(CFTypeRef key,
39 CFTypeRef value) {
40 CFTypeRef keys[1] = {key};
41 CFTypeRef values[1] = {value};
42 return base::ScopedCFTypeRef<CFDictionaryRef>(CFDictionaryCreate(
43 kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks,
44 &kCFTypeDictionaryValueCallBacks));
45 }
46
47 template <typename NalSizeType>
48 void CopyNalsToAnnexB(char* avcc_buffer,
49 const size_t avcc_size,
50 std::string* annexb_buffer) {
51 COMPILE_ASSERT(sizeof(NalSizeType) == 1 || sizeof(NalSizeType) == 2 ||
52 sizeof(NalSizeType) == 4,
53 "NAL size type has unsupported size");
54 static const char startcode_3[3] = {0, 0, 1};
55 DCHECK(avcc_buffer);
56 DCHECK(annexb_buffer);
57 size_t bytes_left = avcc_size;
58 while (bytes_left > 0) {
59 DCHECK_GT(bytes_left, sizeof(NalSizeType));
60 NalSizeType nal_size;
61 base::ReadBigEndian(avcc_buffer, &nal_size);
62 bytes_left -= sizeof(NalSizeType);
63 avcc_buffer += sizeof(NalSizeType);
64
65 DCHECK_GE(bytes_left, nal_size);
66 annexb_buffer->append(startcode_3, sizeof(startcode_3));
67 annexb_buffer->append(avcc_buffer, nal_size);
68 bytes_left -= nal_size;
69 avcc_buffer += nal_size;
70 }
71 }
72
73 // Copy a H.264 frame stored in a CM sample buffer to an Annex B buffer. Copies
74 // parameter sets for keyframes before the frame data as well.
75 void CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf,
76 std::string* annexb_buffer,
77 bool keyframe) {
78 // Perform two pass, one to figure out the total output size, and another to
79 // copy the data after having performed a single output allocation. Note that
80 // we'll allocate a bit more because we'll count 4 bytes instead of 3 for
81 // video NALs.
82
83 OSStatus status;
84
85 // Get the sample buffer's block buffer and format description.
86 auto bb = CoreMediaGlue::CMSampleBufferGetDataBuffer(sbuf);
87 DCHECK(bb);
88 auto fdesc = CoreMediaGlue::CMSampleBufferGetFormatDescription(sbuf);
89 DCHECK(fdesc);
90
91 size_t bb_size = CoreMediaGlue::CMBlockBufferGetDataLength(bb);
92 size_t total_bytes = bb_size;
93
94 size_t pset_count;
95 int nal_size_field_bytes;
96 status = CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
97 fdesc, 0, nullptr, nullptr, &pset_count, &nal_size_field_bytes);
98 if (status ==
99 CoreMediaGlue::kCMFormatDescriptionBridgeError_InvalidParameter) {
100 DLOG(WARNING) << " assuming 2 parameter sets and 4 bytes NAL length header";
101 pset_count = 2;
102 nal_size_field_bytes = 4;
103 } else if (status != noErr) {
104 DLOG(ERROR)
105 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: "
106 << status;
107 return;
108 }
109
110 if (keyframe) {
111 const uint8_t* pset;
112 size_t pset_size;
113 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) {
114 status =
115 CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
116 fdesc, pset_i, &pset, &pset_size, nullptr, nullptr);
117 if (status != noErr) {
118 DLOG(ERROR)
119 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: "
120 << status;
121 return;
122 }
123 total_bytes += pset_size + nal_size_field_bytes;
124 }
125 }
126
127 annexb_buffer->reserve(total_bytes);
128
129 // Copy all parameter sets before keyframes.
130 if (keyframe) {
131 const uint8_t* pset;
132 size_t pset_size;
133 for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) {
134 status =
135 CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
136 fdesc, pset_i, &pset, &pset_size, nullptr, nullptr);
137 if (status != noErr) {
138 DLOG(ERROR)
139 << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: "
140 << status;
141 return;
142 }
143 static const char startcode_4[4] = {0, 0, 0, 1};
144 annexb_buffer->append(startcode_4, sizeof(startcode_4));
145 annexb_buffer->append(reinterpret_cast<const char*>(pset), pset_size);
146 }
147 }
148
149 // Block buffers can be composed of non-contiguous chunks. For the sake of
150 // keeping this code simple, flatten non-contiguous block buffers.
151 base::ScopedCFTypeRef<CoreMediaGlue::CMBlockBufferRef> contiguous_bb(
152 bb, base::scoped_policy::RETAIN);
153 if (!CoreMediaGlue::CMBlockBufferIsRangeContiguous(bb, 0, 0)) {
154 contiguous_bb.reset();
155 status = CoreMediaGlue::CMBlockBufferCreateContiguous(
156 kCFAllocatorDefault, bb, kCFAllocatorDefault, nullptr, 0, 0, 0,
157 contiguous_bb.InitializeInto());
158 if (status != noErr) {
159 DLOG(ERROR) << " CMBlockBufferCreateContiguous failed: " << status;
160 return;
161 }
162 }
163
164 // Copy all the NAL units. In the process convert them from AVCC format
165 // (length header) to AnnexB format (start code).
166 char* bb_data;
167 status = CoreMediaGlue::CMBlockBufferGetDataPointer(contiguous_bb, 0, nullptr,
168 nullptr, &bb_data);
169 if (status != noErr) {
170 DLOG(ERROR) << " CMBlockBufferGetDataPointer failed: " << status;
171 return;
172 }
173
174 if (nal_size_field_bytes == 1) {
175 CopyNalsToAnnexB<uint8_t>(bb_data, bb_size, annexb_buffer);
176 } else if (nal_size_field_bytes == 2) {
177 CopyNalsToAnnexB<uint16_t>(bb_data, bb_size, annexb_buffer);
178 } else if (nal_size_field_bytes == 4) {
179 CopyNalsToAnnexB<uint32_t>(bb_data, bb_size, annexb_buffer);
180 } else {
181 NOTREACHED();
182 }
183 }
184
185 // Implementation of the VideoFrameFactory interface using |CVPixelBufferPool|.
186 class VideoFrameFactoryCVPixelBufferPoolImpl : public VideoFrameFactory {
187 public:
188 VideoFrameFactoryCVPixelBufferPoolImpl(
189 base::ScopedCFTypeRef<CVPixelBufferPoolRef> pool)
miu 2014/11/20 23:16:55 const base::ScopedCFTypeRef<CVPixelBufferPoolRef>&
190 : pool_(pool) {}
191
192 ~VideoFrameFactoryCVPixelBufferPoolImpl() override {}
193
194 scoped_refptr<VideoFrame> CreateFrame(base::TimeDelta timestamp) override {
195 base::ScopedCFTypeRef<CVPixelBufferRef> buffer;
196 CHECK_EQ(kCVReturnSuccess,
197 CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool_,
198 buffer.InitializeInto()));
199 return VideoFrame::WrapCVPixelBuffer(buffer, timestamp);
200 }
201
202 private:
203 base::ScopedCFTypeRef<CVPixelBufferPoolRef> pool_;
204
205 DISALLOW_COPY_AND_ASSIGN(VideoFrameFactoryCVPixelBufferPoolImpl);
206 };
207
208 } // namespace
209
210 H264VideoToolboxEncoder::H264VideoToolboxEncoder(
211 scoped_refptr<CastEnvironment> cast_environment,
212 const VideoSenderConfig& video_config,
213 const CastInitializationCallback& initialization_cb)
214 : cast_environment_(cast_environment),
215 videotoolbox_glue_(VideoToolboxGlue::Get()),
216 frame_id_(kStartFrameId),
217 encode_next_frame_as_keyframe_(false) {
218 DCHECK(!initialization_cb.is_null());
219 CastInitializationStatus initialization_status;
220 if (videotoolbox_glue_) {
221 initialization_status = (Initialize(video_config))
222 ? STATUS_VIDEO_INITIALIZED
223 : STATUS_INVALID_VIDEO_CONFIGURATION;
224 } else {
225 LOG(ERROR) << " VideoToolbox is not available";
226 initialization_status = STATUS_HW_VIDEO_ENCODER_NOT_SUPPORTED;
227 }
228 cast_environment_->PostTask(
229 CastEnvironment::MAIN, FROM_HERE,
230 base::Bind(initialization_cb, initialization_status));
231 }
232
233 H264VideoToolboxEncoder::~H264VideoToolboxEncoder() {
234 Teardown();
235 }
236
237 bool H264VideoToolboxEncoder::Initialize(
238 const VideoSenderConfig& video_config) {
239 DCHECK(thread_checker_.CalledOnValidThread());
240 DCHECK(!compression_session_);
241
242 // Note that the encoder object is given to the compression session as the
243 // callback context using a raw pointer. The C API does not allow us to use
244 // a smart pointer, nor is this encoder ref counted. However, this is still
245 // safe, because we 1) we own the compression session and 2) we tear it down
246 // safely. When destructing the encoder, the compression session is flushed
247 // and invalidated. Internally, VideoToolbox will join all of its threads
248 // before returning to the client. Therefore, when control returns to us, we
249 // are guaranteed that the output callback will not execute again.
250
251 // On OS X, allow the hardware encoder. Don't require it, it does not support
252 // all configurations (some of which are used for testing).
253 base::ScopedCFTypeRef<CFDictionaryRef> encoder_spec;
254 #if !defined(OS_IOS)
255 encoder_spec = DictionaryWithKeyValue(
256 videotoolbox_glue_
257 ->kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder() ,
258 kCFBooleanTrue);
259 #endif
260
261 VTCompressionSessionRef session;
262 OSStatus status = videotoolbox_glue_->VTCompressionSessionCreate(
263 kCFAllocatorDefault, video_config.width, video_config.height,
264 CoreMediaGlue::kCMVideoCodecType_H264, encoder_spec,
265 nullptr /* sourceImageBufferAttributes */,
266 nullptr /* compressedDataAllocator */,
267 &H264VideoToolboxEncoder::CompressionCallback,
268 reinterpret_cast<void*>(this), &session);
269 if (status != noErr) {
270 DLOG(ERROR) << " VTCompressionSessionCreate failed: " << status;
271 return false;
272 }
273 compression_session_.reset(session);
274
275 ConfigureSession(video_config);
276
277 return true;
278 }
279
280 void H264VideoToolboxEncoder::ConfigureSession(
281 const VideoSenderConfig& video_config) {
282 SetSessionProperty(
283 videotoolbox_glue_->kVTCompressionPropertyKey_ProfileLevel(),
284 videotoolbox_glue_->kVTProfileLevel_H264_Main_AutoLevel());
285 SetSessionProperty(videotoolbox_glue_->kVTCompressionPropertyKey_RealTime(),
286 true);
287 SetSessionProperty(
288 videotoolbox_glue_->kVTCompressionPropertyKey_AllowFrameReordering(),
289 false);
290 SetSessionProperty(
291 videotoolbox_glue_->kVTCompressionPropertyKey_MaxKeyFrameInterval(), 240);
292 SetSessionProperty(
293 videotoolbox_glue_
294 ->kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration(),
295 240);
296 // TODO(jfroy): implement better bitrate control
297 // https://crbug.com/425352
298 SetSessionProperty(
299 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(),
300 (video_config.min_bitrate + video_config.max_bitrate) / 2);
301 SetSessionProperty(
302 videotoolbox_glue_->kVTCompressionPropertyKey_ExpectedFrameRate(),
303 video_config.max_frame_rate);
304 SetSessionProperty(
305 videotoolbox_glue_->kVTCompressionPropertyKey_ColorPrimaries(),
306 kCVImageBufferColorPrimaries_ITU_R_709_2);
307 SetSessionProperty(
308 videotoolbox_glue_->kVTCompressionPropertyKey_TransferFunction(),
309 kCVImageBufferTransferFunction_ITU_R_709_2);
310 SetSessionProperty(
311 videotoolbox_glue_->kVTCompressionPropertyKey_YCbCrMatrix(),
312 kCVImageBufferYCbCrMatrix_ITU_R_709_2);
313 }
314
315 void H264VideoToolboxEncoder::Teardown() {
316 DCHECK(thread_checker_.CalledOnValidThread());
317
318 // If the compression session exists, invalidate it. This blocks until all
319 // pending output callbacks have returned and any internal threads have
320 // joined, ensuring no output callback ever sees a dangling encoder pointer.
321 if (compression_session_) {
322 videotoolbox_glue_->VTCompressionSessionInvalidate(compression_session_);
323 compression_session_.reset();
324 }
325 }
326
327 bool H264VideoToolboxEncoder::EncodeVideoFrame(
328 const scoped_refptr<media::VideoFrame>& video_frame,
329 const base::TimeTicks& reference_time,
330 const FrameEncodedCallback& frame_encoded_callback) {
331 DCHECK(thread_checker_.CalledOnValidThread());
332 DCHECK(!reference_time.is_null());
333
334 if (!compression_session_) {
335 DLOG(ERROR) << " compression session is null";
336 return false;
337 }
338
339 // Wrap the VideoFrame in a CVPixelBuffer. In all cases, no data will be
340 // copied. If the VideoFrame was created by this encoder's video frame
341 // factory, then the returned CVPixelBuffer will have been obtained from the
342 // compression session's pixel buffer pool. This will eliminate a copy of the
343 // frame into memory visible by the hardware encoder. The VideoFrame's
344 // lifetime is extended for the lifetime of the returned CVPixelBuffer.
345 auto pixel_buffer = media::WrapVideoFrameInCVPixelBuffer(*video_frame);
346 if (!pixel_buffer) {
347 return false;
348 }
349
350 auto timestamp_cm = CoreMediaGlue::CMTimeMake(
351 (reference_time - base::TimeTicks()).InMicroseconds(), USEC_PER_SEC);
352
353 scoped_ptr<InProgressFrameEncode> request(new InProgressFrameEncode(
354 TimeDeltaToRtpDelta(video_frame->timestamp(), kVideoFrequency),
355 reference_time, frame_encoded_callback));
356
357 base::ScopedCFTypeRef<CFDictionaryRef> frame_props;
358 if (encode_next_frame_as_keyframe_) {
359 frame_props = DictionaryWithKeyValue(
360 videotoolbox_glue_->kVTEncodeFrameOptionKey_ForceKeyFrame(),
361 kCFBooleanTrue);
362 encode_next_frame_as_keyframe_ = false;
363 }
364
365 VTEncodeInfoFlags info;
366 OSStatus status = videotoolbox_glue_->VTCompressionSessionEncodeFrame(
367 compression_session_, pixel_buffer, timestamp_cm,
368 CoreMediaGlue::CMTime{0, 0, 0, 0}, frame_props,
369 reinterpret_cast<void*>(request.release()), &info);
370 if (status != noErr) {
371 DLOG(ERROR) << " VTCompressionSessionEncodeFrame failed: " << status;
372 return false;
373 }
374 if ((info & VideoToolboxGlue::kVTEncodeInfo_FrameDropped)) {
375 DLOG(ERROR) << " frame dropped";
376 return false;
377 }
378
379 return true;
380 }
381
382 void H264VideoToolboxEncoder::SetBitRate(int new_bit_rate) {
383 DCHECK(thread_checker_.CalledOnValidThread());
384 // VideoToolbox does not seem to support bitrate reconfiguration.
385 }
386
387 void H264VideoToolboxEncoder::GenerateKeyFrame() {
388 DCHECK(thread_checker_.CalledOnValidThread());
389 DCHECK(compression_session_);
390
391 encode_next_frame_as_keyframe_ = true;
392 }
393
394 void H264VideoToolboxEncoder::LatestFrameIdToReference(uint32 /*frame_id*/) {
395 // Not supported by VideoToolbox in any meaningful manner.
396 }
397
398 scoped_ptr<VideoFrameFactory>
399 H264VideoToolboxEncoder::CreateVideoFrameFactory() {
400 base::ScopedCFTypeRef<CVPixelBufferPoolRef> pool(
401 videotoolbox_glue_->VTCompressionSessionGetPixelBufferPool(
402 compression_session_),
403 base::scoped_policy::RETAIN);
404 return scoped_ptr<VideoFrameFactory>(
405 new VideoFrameFactoryCVPixelBufferPoolImpl(pool));
406 }
407
408 bool H264VideoToolboxEncoder::SetSessionProperty(CFStringRef key,
409 int32_t value) {
410 base::ScopedCFTypeRef<CFNumberRef> cfvalue(
411 CFNumberCreate(nullptr, kCFNumberSInt32Type, &value));
412 return videotoolbox_glue_->VTSessionSetProperty(compression_session_, key,
413 cfvalue) == noErr;
414 }
415
416 bool H264VideoToolboxEncoder::SetSessionProperty(CFStringRef key, bool value) {
417 CFBooleanRef cfvalue = (value) ? kCFBooleanTrue : kCFBooleanFalse;
418 return videotoolbox_glue_->VTSessionSetProperty(compression_session_, key,
419 cfvalue) == noErr;
420 }
421
422 bool H264VideoToolboxEncoder::SetSessionProperty(CFStringRef key,
423 CFStringRef value) {
424 return videotoolbox_glue_->VTSessionSetProperty(compression_session_, key,
425 value) == noErr;
426 }
427
428 void H264VideoToolboxEncoder::CompressionCallback(void* encoder_opaque,
429 void* request_opaque,
430 OSStatus status,
431 VTEncodeInfoFlags info,
432 CMSampleBufferRef sbuf) {
433 if (status != noErr) {
434 DLOG(ERROR) << " encoding failed: " << status;
435 return;
436 }
437 if ((info & VideoToolboxGlue::kVTEncodeInfo_FrameDropped)) {
438 DVLOG(2) << " frame dropped";
439 return;
440 }
441
442 auto encoder = reinterpret_cast<H264VideoToolboxEncoder*>(encoder_opaque);
443 const scoped_ptr<InProgressFrameEncode> request(
444 reinterpret_cast<InProgressFrameEncode*>(request_opaque));
445 auto sample_attachments = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(
446 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray(sbuf, true), 0));
447
448 // If the NotSync key is not present, it implies Sync, which indicates a
449 // keyframe (at least I think, VT documentation is, erm, sparse). Could
450 // alternatively use kCMSampleAttachmentKey_DependsOnOthers == false.
451 bool keyframe =
452 !CFDictionaryContainsKey(sample_attachments,
453 CoreMediaGlue::kCMSampleAttachmentKey_NotSync());
454
455 // Increment the encoder-scoped frame id and assign the new value to this
456 // frame. VideoToolbox calls the output callback serially, so this is safe.
457 uint32 frame_id = ++encoder->frame_id_;
458
459 scoped_ptr<EncodedFrame> encoded_frame(new EncodedFrame());
460 encoded_frame->frame_id = frame_id;
461 encoded_frame->reference_time = request->reference_time;
462 encoded_frame->rtp_timestamp = request->rtp_timestamp;
463 if (keyframe) {
464 encoded_frame->dependency = EncodedFrame::KEY;
465 encoded_frame->referenced_frame_id = frame_id;
466 } else {
467 encoded_frame->dependency = EncodedFrame::DEPENDENT;
468 // H.264 supports complex frame reference schemes (multiple reference
469 // frames, slice references, backward and forward references, etc). Cast
470 // doesn't support the concept of forward-referencing frame dependencies or
471 // multiple frame dependencies; so pretend that all frames are only
472 // decodable after their immediately preceding frame is decoded. This will
473 // ensure a Cast receiver only attempts to decode the frames sequentially
474 // and in order. Furthermore, the encoder is configured to never use forward
475 // references (see |kVTCompressionPropertyKey_AllowFrameReordering|). There
476 // is no way to prevent multiple reference frames.
477 encoded_frame->referenced_frame_id = frame_id - 1;
478 }
479
480 CopySampleBufferToAnnexBBuffer(sbuf, &encoded_frame->data, keyframe);
481
482 encoder->cast_environment_->PostTask(
483 CastEnvironment::MAIN, FROM_HERE,
484 base::Bind(request->frame_encoded_callback,
485 base::Passed(&encoded_frame)));
486 }
487
488 } // namespace cast
489 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698