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

Side by Side Diff: content/common/gpu/media/vt_video_encode_accelerator_mac.cc

Issue 1636083003: H264 HW encode using VideoToolbox (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: miu@ and jfroy@ comments Created 4 years, 10 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 2016 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 "content/common/gpu/media/vt_video_encode_accelerator_mac.h"
6
7 #include "base/thread_task_runner_handle.h"
8 #include "media/base/mac/coremedia_glue.h"
9 #include "media/base/mac/corevideo_glue.h"
10 #include "media/base/mac/video_frame_mac.h"
11
12 namespace content {
13
14 namespace {
15
16 // Subjectively chosen.
17 // TODO(emircan): Check if we can find the actual system capabilities via
18 // creating VTCompressionSessions with varying requirements.
19 // See crbug.com/584784.
20 const size_t kNumInputBuffers = 4;
21 const size_t kMaxFrameRateNumerator = 30;
22 const size_t kMaxFrameRateDenominator = 1;
23 const size_t kMaxResolutionWidth = 4096;
24 const size_t kMaxResolutionHeight = 2160;
25 const size_t kBitsPerByte = 8;
26 // The ratio of expected |max_bitrate_| to the given |average_bitrate_|.
27 const double kMaxToAverageBitrateRatio = 1.25;
28
29 } // namespace
30
31 struct VTVideoEncodeAccelerator::InProgressFrameEncode {
32 InProgressFrameEncode(base::TimeDelta rtp_timestamp, base::TimeTicks ref_time)
33 : timestamp(rtp_timestamp), reference_time(ref_time) {}
34 const base::TimeDelta timestamp;
35 const base::TimeTicks reference_time;
36
37 private:
38 DISALLOW_IMPLICIT_CONSTRUCTORS(InProgressFrameEncode);
39 };
40
41 struct VTVideoEncodeAccelerator::EncodeOutput {
42 EncodeOutput(VTEncodeInfoFlags info_flags, CMSampleBufferRef sbuf)
43 : info(info_flags), sample_buffer(sbuf) {}
44 const VTEncodeInfoFlags info;
45 const CMSampleBufferRef sample_buffer;
46
47 private:
48 DISALLOW_IMPLICIT_CONSTRUCTORS(EncodeOutput);
49 };
50
51 struct VTVideoEncodeAccelerator::BitstreamBufferRef {
52 BitstreamBufferRef(int32_t id,
53 scoped_ptr<base::SharedMemory> shm,
54 size_t size)
55 : id(id), shm(std::move(shm)), size(size) {}
56 const int32_t id;
57 const scoped_ptr<base::SharedMemory> shm;
58 const size_t size;
59
60 private:
61 DISALLOW_IMPLICIT_CONSTRUCTORS(BitstreamBufferRef);
62 };
63
64 VTVideoEncodeAccelerator::VTVideoEncodeAccelerator()
65 : client_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
66 }
67
68 VTVideoEncodeAccelerator::~VTVideoEncodeAccelerator() {
69 DCHECK(thread_checker_.CalledOnValidThread());
70 }
71
72 media::VideoEncodeAccelerator::SupportedProfiles
73 VTVideoEncodeAccelerator::GetSupportedProfiles() {
74 DVLOG(3) << __FUNCTION__;
75 DCHECK(thread_checker_.CalledOnValidThread());
76
77 SupportedProfiles profiles;
78 SupportedProfile profile;
79 profile.profile = media::H264PROFILE_BASELINE;
80 profile.max_framerate_numerator = kMaxFrameRateNumerator;
81 profile.max_framerate_denominator = kMaxFrameRateDenominator;
82 profile.max_resolution = gfx::Size(kMaxResolutionWidth, kMaxResolutionHeight);
83 profiles.push_back(profile);
84 return profiles;
85 }
86
87 bool VTVideoEncodeAccelerator::Initialize(
88 media::VideoPixelFormat format,
89 const gfx::Size& input_visible_size,
90 media::VideoCodecProfile output_profile,
91 uint32_t initial_bitrate,
92 Client* client) {
93 DVLOG(3) << __FUNCTION__
94 << ": input_format=" << media::VideoPixelFormatToString(format)
95 << ", input_visible_size=" << input_visible_size.ToString()
96 << ", output_profile=" << output_profile
97 << ", initial_bitrate=" << initial_bitrate;
98 DCHECK(thread_checker_.CalledOnValidThread());
99 DCHECK(client);
100
101 if (media::PIXEL_FORMAT_I420 != format) {
102 DLOG(ERROR) << "Input format not supported= "
103 << media::VideoPixelFormatToString(format);
104 return false;
105 }
106 if (media::H264PROFILE_BASELINE != output_profile) {
107 DLOG(ERROR) << "Output profile not supported= "
108 << output_profile;
109 return false;
110 }
111
112 videotoolbox_glue_ = VideoToolboxGlue::Get();
113 if (!videotoolbox_glue_) {
114 DLOG(ERROR) << "Failed creating VideoToolbox glue";
115 return false;
116 }
117
118 client_ptr_factory_.reset(new base::WeakPtrFactory<Client>(client));
119 client_ = client_ptr_factory_->GetWeakPtr();
120 input_visible_size_ = input_visible_size;
121 frame_rate_ = kMaxFrameRateNumerator / kMaxFrameRateDenominator;
122 average_bitrate_ = initial_bitrate;
123 max_bitrate_ = average_bitrate_ * kMaxToAverageBitrateRatio;
124 bitstream_buffer_size_ = max_bitrate_ / kBitsPerByte;
125
126 if (!ResetCompressionSession()) {
127 DLOG(ERROR) << "Failed creating compression session";
128 return false;
129 }
130
131 client_->RequireBitstreamBuffers(kNumInputBuffers, input_visible_size_,
132 bitstream_buffer_size_);
133 return true;
134 }
135
136 void VTVideoEncodeAccelerator::Encode(
137 const scoped_refptr<media::VideoFrame>& frame,
138 bool force_keyframe) {
139 DVLOG(3) << __FUNCTION__;
140 DCHECK(thread_checker_.CalledOnValidThread());
141 DCHECK(compression_session_);
142 DCHECK(frame);
143
144 base::TimeTicks ref_time;
145 if (!frame->metadata()->GetTimeTicks(
146 media::VideoFrameMetadata::REFERENCE_TIME, &ref_time)) {
147 ref_time = base::TimeTicks::Now();
148 }
149 auto timestamp_cm = CoreMediaGlue::CMTimeMake(
150 frame->timestamp().InMicroseconds(), USEC_PER_SEC);
151 // Wrap information we'll need after the frame is encoded in a heap object.
152 // We'll get the pointer back from the VideoToolbox completion callback.
153 scoped_ptr<InProgressFrameEncode> request(new InProgressFrameEncode(
154 frame->timestamp(), ref_time));
155
156 // TODO(emircan): See if we can eliminate a copy here by using
157 // CVPixelBufferPool for the allocation of incoming VideoFrames.
158 base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer =
159 media::WrapVideoFrameInCVPixelBuffer(*frame);
160 base::ScopedCFTypeRef<CFDictionaryRef> frame_props =
161 media::video_toolbox::DictionaryWithKeyValue(
162 videotoolbox_glue_->kVTEncodeFrameOptionKey_ForceKeyFrame(),
163 force_keyframe ? kCFBooleanTrue : kCFBooleanFalse);
164
165 OSStatus status = videotoolbox_glue_->VTCompressionSessionEncodeFrame(
166 compression_session_, pixel_buffer, timestamp_cm,
167 CoreMediaGlue::CMTime{0, 0, 0, 0}, frame_props,
168 reinterpret_cast<void*>(request.release()), nullptr);
169 if (status != noErr) {
170 DLOG(ERROR) << " VTCompressionSessionEncodeFrame failed: " << status;
171 client_->NotifyError(kPlatformFailureError);
172 }
173 }
174
175 void VTVideoEncodeAccelerator::UseOutputBitstreamBuffer(
176 const media::BitstreamBuffer& buffer) {
177 DVLOG(3) << __FUNCTION__;
178 DCHECK(thread_checker_.CalledOnValidThread());
179 if (buffer.size() < bitstream_buffer_size_) {
180 DLOG(ERROR) << "Output BitstreamBuffer isn't big enough: " << buffer.size()
181 << " vs. " << bitstream_buffer_size_;
182 client_->NotifyError(kInvalidArgumentError);
183 return;
184 }
185
186 scoped_ptr<base::SharedMemory> shm(
187 new base::SharedMemory(buffer.handle(), false));
188 if (!shm->Map(buffer.size())) {
189 DLOG(ERROR) << "Failed mapping shared memory.";
190 client_->NotifyError(kPlatformFailureError);
191 return;
192 }
193
194 scoped_ptr<BitstreamBufferRef> buffer_ref(
195 new BitstreamBufferRef(buffer.id(), std::move(shm), buffer.size()));
196
197 // If there is already EncodeOutput waiting, copy its output first.
198 if (!encoder_output_queue_.empty()) {
199 scoped_ptr<VTVideoEncodeAccelerator::EncodeOutput> encode_output =
200 std::move(encoder_output_queue_.front());
201 encoder_output_queue_.pop_front();
202 ReturnBitstreamBuffer(encode_output->info, encode_output->sample_buffer,
203 std::move(buffer_ref));
204 return;
205 }
206
207 bitstream_buffer_queue_.push_back(std::move(buffer_ref));
208 }
209
210 void VTVideoEncodeAccelerator::RequestEncodingParametersChange(
211 uint32_t bitrate,
212 uint32_t framerate) {
213 DVLOG(3) << __FUNCTION__ << ": bitrate=" << bitrate;
214 DCHECK(thread_checker_.CalledOnValidThread());
215
216 frame_rate_ = framerate > 1 ? framerate : 1;
217 average_bitrate_ = bitrate > 1 ? bitrate : 1;
218 max_bitrate_ = average_bitrate_ * kMaxToAverageBitrateRatio;
219
220 if (!compression_session_) {
221 client_->NotifyError(kPlatformFailureError);
222 return;
223 }
224
225 // TODO(emircan): See crbug.com/425352.
226 const int data_rate_limits[] = {max_bitrate_ / kBitsPerByte, 1};
227 bool rv = session_property_setter_.SetSessionProperty(
228 videotoolbox_glue_->kVTCompressionPropertyKey_DataRateLimits(),
229 media::video_toolbox::ArrayWithIntegers(
230 data_rate_limits, arraysize(data_rate_limits)).release());
231 rv &= session_property_setter_.SetSessionProperty(
232 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(),
233 average_bitrate_);
234 rv &= session_property_setter_.SetSessionProperty(
235 videotoolbox_glue_->kVTCompressionPropertyKey_ExpectedFrameRate(),
236 frame_rate_);
237
238 DLOG_IF(ERROR, !rv) << "Couldn't change session encoding parameters.";
239 }
240
241 void VTVideoEncodeAccelerator::Destroy() {
242 DVLOG(3) << __FUNCTION__;
243 DCHECK(thread_checker_.CalledOnValidThread());
244
245 DestroyCompressionSession();
246 delete this;
247 }
248
249 // static
250 void VTVideoEncodeAccelerator::CompressionCallback(void* encoder_opaque,
251 void* request_opaque,
252 OSStatus status,
253 VTEncodeInfoFlags info,
254 CMSampleBufferRef sbuf) {
255 // This function may be called asynchronously, on a different thread from the
256 // one that calls VTCompressionSessionEncodeFrame.
257 DVLOG(3) << __FUNCTION__;
258
259 auto encoder = reinterpret_cast<VTVideoEncodeAccelerator*>(encoder_opaque);
260 DCHECK(encoder);
261
262 // Release InProgressFrameEncode, since we don't have support to return
263 // timestamps at this point.
264 scoped_ptr<InProgressFrameEncode> request(
265 reinterpret_cast<InProgressFrameEncode*>(request_opaque));
266 request.reset();
267
268 if (status != noErr) {
269 DLOG(ERROR) << " encode failed: " << status;
270 encoder->client_task_runner_->PostTask(
271 FROM_HERE, base::Bind(&Client::NotifyError, encoder->client_,
272 kPlatformFailureError));
273 return;
274 }
275
276 // CFRetain is required to hold onto CMSampleBufferRef when posting task
277 // between threads. The object should be released later using CFRelease.
278 CFRetain(sbuf);
279 // This method is NOT called on |client_task_runner_|, so we still need to
280 // post a task back to it to reach |client_|.
281 encoder->client_task_runner_->PostTask(
282 FROM_HERE,
283 base::Bind(&VTVideoEncodeAccelerator::CompressionCallbackTask,
284 base::Unretained(encoder), info, sbuf));
285 }
286
287 void VTVideoEncodeAccelerator::CompressionCallbackTask(VTEncodeInfoFlags info,
288 CMSampleBufferRef sbuf) {
289 DVLOG(3) << __FUNCTION__;
290 DCHECK(thread_checker_.CalledOnValidThread());
291
292 // If there isn't any BitstreamBuffer to copy into, add it to a queue for
293 // later use.
294 if (bitstream_buffer_queue_.empty()) {
295 scoped_ptr<EncodeOutput> encode_output(new EncodeOutput(info, sbuf));
296 encoder_output_queue_.push_back(std::move(encode_output));
297 return;
298 }
299
300 scoped_ptr<VTVideoEncodeAccelerator::BitstreamBufferRef> buffer_ref =
301 std::move(bitstream_buffer_queue_.front());
302 bitstream_buffer_queue_.pop_front();
303 ReturnBitstreamBuffer(info, sbuf, std::move(buffer_ref));
304 }
305
306 void VTVideoEncodeAccelerator::ReturnBitstreamBuffer(
307 VTEncodeInfoFlags info,
308 CMSampleBufferRef sbuf,
309 scoped_ptr<VTVideoEncodeAccelerator::BitstreamBufferRef> buffer_ref) {
310 if (info & VideoToolboxGlue::kVTEncodeInfo_FrameDropped) {
miu 2016/02/11 21:09:40 nit: Add DCHECK(thread_checker_...);
emircan 2016/02/12 03:40:56 Done.
311 DVLOG(2) << " frame dropped";
312 CFRelease(sbuf);
313 client_->BitstreamBufferReady(buffer_ref->id, 0, false);
314 return;
315 }
316
317 auto sample_attachments = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(
318 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray(sbuf, true), 0));
319 const bool keyframe =
320 !CFDictionaryContainsKey(sample_attachments,
321 CoreMediaGlue::kCMSampleAttachmentKey_NotSync());
322
323 size_t used_buffer_size = 0;
324 const bool copy_rv = media::video_toolbox::CopySampleBufferToAnnexBBuffer(
325 sbuf, keyframe, buffer_ref->size,
326 reinterpret_cast<char*>(buffer_ref->shm->memory()), &used_buffer_size);
327 CFRelease(sbuf);
328 if (!copy_rv) {
329 DLOG(ERROR) << "Cannot copy output from SampleBuffer to AnnexBBuffer.";
330 used_buffer_size = 0;
331 }
332
333 client_->BitstreamBufferReady(buffer_ref->id, used_buffer_size, keyframe);
334 }
335
336 bool VTVideoEncodeAccelerator::ResetCompressionSession() {
337 DCHECK(thread_checker_.CalledOnValidThread());
338
339 DestroyCompressionSession();
340
341 // Keep these in-sync with those in ConfigureCompressionSession().
342 CFTypeRef attributes_keys[] = {
343 kCVPixelBufferOpenGLCompatibilityKey,
344 kCVPixelBufferIOSurfacePropertiesKey,
345 kCVPixelBufferPixelFormatTypeKey
346 };
347 const int format[] = {
348 CoreVideoGlue::kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange};
349 CFTypeRef attributes_values[] = {
350 kCFBooleanTrue,
351 media::video_toolbox::DictionaryWithKeysAndValues(nullptr, nullptr, 0)
352 .release(),
353 media::video_toolbox::ArrayWithIntegers(format, arraysize(format))
354 .release()};
355 const base::ScopedCFTypeRef<CFDictionaryRef> attributes =
356 media::video_toolbox::DictionaryWithKeysAndValues(
357 attributes_keys, attributes_values, arraysize(attributes_keys));
358 for (auto& v : attributes_values)
359 CFRelease(v);
360
361 bool session_rv = CreateCompressionSession(attributes, true);
362 if (!session_rv) {
363 // Try creating session again without forcing HW encode.
364 session_rv = CreateCompressionSession(attributes, false);
365 if (!session_rv)
366 return false;
367 }
368
369 const bool configure_rv = ConfigureCompressionSession();
370 RequestEncodingParametersChange(average_bitrate_, frame_rate_);
371 return configure_rv;
372 }
373
374 bool VTVideoEncodeAccelerator::CreateCompressionSession(
375 base::ScopedCFTypeRef<CFDictionaryRef> attributes,
376 bool require_hw_encoding) {
377 DCHECK(thread_checker_.CalledOnValidThread());
378
379 std::vector<CFTypeRef> encoder_keys;
380 std::vector<CFTypeRef> encoder_values;
381 encoder_keys.push_back(videotoolbox_glue_
382 ->kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder());
383 encoder_values.push_back(kCFBooleanTrue);
384
385 if (require_hw_encoding) {
386 encoder_keys.push_back(
387 videotoolbox_glue_
388 ->kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder());
389 encoder_values.push_back(kCFBooleanTrue);
390 }
391 base::ScopedCFTypeRef<CFDictionaryRef> encoder_spec =
392 media::video_toolbox::DictionaryWithKeysAndValues(
393 encoder_keys.data(), encoder_values.data(), encoder_keys.size());
394
395 // Create the compression session.
396 OSStatus status = videotoolbox_glue_->VTCompressionSessionCreate(
397 kCFAllocatorDefault,
398 input_visible_size_.width(),
399 input_visible_size_.height(),
400 CoreMediaGlue::kCMVideoCodecType_H264,
401 encoder_spec,
402 attributes,
403 nullptr /* compressedDataAllocator */,
404 &VTVideoEncodeAccelerator::CompressionCallback,
405 reinterpret_cast<void*>(this),
406 compression_session_.InitializeInto());
407 if (status != noErr) {
408 DLOG(ERROR) << " VTCompressionSessionCreate failed: " << status;
409 return false;
410 }
411 DVLOG(3) << " VTCompressionSession created with HW encode: "
412 << require_hw_encoding;
413 return true;
414 }
415
416 bool VTVideoEncodeAccelerator::ConfigureCompressionSession() {
417 DCHECK(thread_checker_.CalledOnValidThread());
418 DCHECK(compression_session_);
419 session_property_setter_ = media::video_toolbox::SessionPropertySetter(
miu 2016/02/11 21:09:40 Could you explain why you're trying to keep the |s
emircan 2016/02/12 03:40:56 I am changing it as you suggest. I understand you
420 compression_session_, videotoolbox_glue_);
421
422 bool rv = true;
423 rv &= session_property_setter_.SetSessionProperty(
424 videotoolbox_glue_->kVTCompressionPropertyKey_ProfileLevel(),
425 videotoolbox_glue_->kVTProfileLevel_H264_Baseline_AutoLevel());
426 rv &= session_property_setter_.SetSessionProperty(
427 videotoolbox_glue_->kVTCompressionPropertyKey_RealTime(), true);
428 rv &= session_property_setter_.SetSessionProperty(
429 videotoolbox_glue_->kVTCompressionPropertyKey_AllowFrameReordering(),
430 false);
431 DLOG_IF(ERROR, !rv) << " SetSessionProperty failed.";
432 return rv;
433 }
434
435 void VTVideoEncodeAccelerator::DestroyCompressionSession() {
436 DCHECK(thread_checker_.CalledOnValidThread());
437
438 if (compression_session_) {
439 videotoolbox_glue_->VTCompressionSessionInvalidate(compression_session_);
440 compression_session_.reset();
441 }
442 }
443
444 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698