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

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: Rename vt_video_encode_accelerator to vt_video_encode_accelerator_mac. 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 = 1;
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 // The ratio of |input_visible_size| area to the max expected output
26 // BitstreamBuffer size in bytes. VideoToolbox returns variable sized encoded
27 // data whereas media::VideoEncodeAccelerator provides a uniform BitstreamBuffer
28 // size to fill this data into. This ratio is used to determine a size that
29 // would ideally be big enough to fit all frames.
30 const size_t kOutputBufferSizeRatio = 10;
miu 2016/02/09 23:29:22 Instead of a constant, shouldn't this depend on th
emircan 2016/02/10 05:21:53 I tried doing that but it would be very dependent
miu 2016/02/10 21:04:55 Sounds fine. I'll leave it to the VEA owners to w
31
32 } // namespace
33
34 struct VTVideoEncodeAccelerator::InProgressFrameEncode {
35 const base::TimeDelta timestamp;
36 const base::TimeTicks reference_time;
37
38 InProgressFrameEncode(base::TimeDelta rtp_timestamp, base::TimeTicks ref_time)
39 : timestamp(rtp_timestamp), reference_time(ref_time) {}
40
41 private:
42 DISALLOW_IMPLICIT_CONSTRUCTORS(InProgressFrameEncode);
43 };
44
45 struct VTVideoEncodeAccelerator::BitstreamBufferRef {
46 BitstreamBufferRef(int32_t id,
47 scoped_ptr<base::SharedMemory> shm,
48 size_t size)
49 : id(id), shm(std::move(shm)), size(size) {}
50 const int32_t id;
51 const scoped_ptr<base::SharedMemory> shm;
52 const size_t size;
53
54 private:
55 DISALLOW_IMPLICIT_CONSTRUCTORS(BitstreamBufferRef);
56 };
57
58 VTVideoEncodeAccelerator::VTVideoEncodeAccelerator()
59 : client_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
60 }
61
62 VTVideoEncodeAccelerator::~VTVideoEncodeAccelerator() {
63 DCHECK(thread_checker_.CalledOnValidThread());
64 }
65
66 media::VideoEncodeAccelerator::SupportedProfiles
67 VTVideoEncodeAccelerator::GetSupportedProfiles() {
68 DVLOG(3) << __FUNCTION__;
69 DCHECK(thread_checker_.CalledOnValidThread());
70
71 SupportedProfiles profiles;
72 SupportedProfile profile;
73 profile.profile = media::H264PROFILE_BASELINE;
74 profile.max_framerate_numerator = kMaxFrameRateNumerator;
75 profile.max_framerate_denominator = kMaxFrameRateDenominator;
76 profile.max_resolution = gfx::Size(kMaxResolutionWidth, kMaxResolutionHeight);
77 profiles.push_back(profile);
78 return profiles;
79 }
80
81 bool VTVideoEncodeAccelerator::Initialize(
82 media::VideoPixelFormat format,
83 const gfx::Size& input_visible_size,
84 media::VideoCodecProfile output_profile,
85 uint32_t initial_bitrate,
86 Client* client) {
87 DVLOG(3) << __FUNCTION__
88 << ": input_format=" << media::VideoPixelFormatToString(format)
89 << ", input_visible_size=" << input_visible_size.ToString()
90 << ", output_profile=" << output_profile
91 << ", initial_bitrate=" << initial_bitrate;
92 DCHECK(thread_checker_.CalledOnValidThread());
93 DCHECK(client);
94
95 if (media::PIXEL_FORMAT_I420 != format) {
96 DLOG(ERROR) << "Input format not supported= "
97 << media::VideoPixelFormatToString(format);
98 return false;
99 }
100 if (media::H264PROFILE_BASELINE != output_profile) {
101 DLOG(ERROR) << "Output profile not supported= "
102 << output_profile;
103 return false;
104 }
105
106 videotoolbox_glue_ = VideoToolboxGlue::Get();
107 if (!videotoolbox_glue_) {
108 DLOG(ERROR) << "Failed creating VideoToolbox glue";
109 return false;
110 }
111
112 client_ptr_factory_.reset(new base::WeakPtrFactory<Client>(client));
113 client_ = client_ptr_factory_->GetWeakPtr();
114 bitrate_ = initial_bitrate;
115 input_visible_size_ = input_visible_size;
116
117 if (!ResetCompressionSession()) {
118 DLOG(ERROR) << "Failed creating compression session";
119 return false;
120 }
121
122 client_->RequireBitstreamBuffers(
123 kNumInputBuffers, input_visible_size_,
124 input_visible_size_.GetArea() / kOutputBufferSizeRatio);
125 return true;
126 }
127
128 void VTVideoEncodeAccelerator::Encode(
129 const scoped_refptr<media::VideoFrame>& frame,
130 bool force_keyframe) {
131 DVLOG(3) << __FUNCTION__;
132 DCHECK(thread_checker_.CalledOnValidThread());
133 DCHECK(compression_session_);
134 DCHECK(frame);
135
136 base::TimeTicks ref_time;
137 if (!frame->metadata()->GetTimeTicks(
138 media::VideoFrameMetadata::REFERENCE_TIME, &ref_time)) {
139 ref_time = base::TimeTicks::Now();
140 }
141 auto timestamp_cm = CoreMediaGlue::CMTimeMake(
142 frame->timestamp().InMicroseconds(), USEC_PER_SEC);
143 // Wrap information we'll need after the frame is encoded in a heap object.
144 // We'll get the pointer back from the VideoToolbox completion callback.
145 scoped_ptr<InProgressFrameEncode> request(new InProgressFrameEncode(
146 frame->timestamp(), ref_time));
147
148 // TODO(emircan): See if we can eliminate a copy here by using
149 // CVPixelBufferPool for the allocation of incoming VideoFrames.
150 base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer =
151 media::WrapVideoFrameInCVPixelBuffer(*frame);
152 base::ScopedCFTypeRef<CFDictionaryRef> frame_props =
153 media::video_toolbox::DictionaryWithKeyValue(
154 videotoolbox_glue_->kVTEncodeFrameOptionKey_ForceKeyFrame(),
155 force_keyframe ? kCFBooleanTrue : kCFBooleanFalse);
156
157 OSStatus status = videotoolbox_glue_->VTCompressionSessionEncodeFrame(
158 compression_session_, pixel_buffer, timestamp_cm,
159 CoreMediaGlue::CMTime{0, 0, 0, 0}, frame_props,
160 reinterpret_cast<void*>(request.release()), nullptr);
miu 2016/02/09 23:29:22 I could be mistaken, but it seems memory is being
emircan 2016/02/10 05:21:53 Thanks for pointing out. I will make a scoped_ptr
161 if (status != noErr) {
162 DLOG(ERROR) << " VTCompressionSessionEncodeFrame failed: " << status;
163 client_->NotifyError(kPlatformFailureError);
164 }
165 }
166
167 void VTVideoEncodeAccelerator::UseOutputBitstreamBuffer(
168 const media::BitstreamBuffer& buffer) {
169 DVLOG(3) << __FUNCTION__;
170 DCHECK(thread_checker_.CalledOnValidThread());
171 if (buffer.size() < static_cast<size_t>(input_visible_size_.GetArea() /
172 kOutputBufferSizeRatio)) {
173 DLOG(ERROR) << "Output BitstreamBuffer isn't big enough: "
174 << buffer.size()
175 << " vs. "
176 << static_cast<size_t>(input_visible_size_.GetArea() /
177 kOutputBufferSizeRatio);
178 client_->NotifyError(kInvalidArgumentError);
179 return;
180 }
181
182 scoped_ptr<base::SharedMemory> shm(
183 new base::SharedMemory(buffer.handle(), false));
184 if (!shm->Map(buffer.size())) {
185 DLOG(ERROR) << "Failed mapping shared memory.";
186 client_->NotifyError(kPlatformFailureError);
187 return;
188 }
189
190 // If there are already CMSampleBufferRef waiting, copy their output first.
191 if (!encoder_output_sample_buffer_queue_.empty()) {
192 CMSampleBufferRef sbuf = encoder_output_sample_buffer_queue_.front();
193 encoder_output_sample_buffer_queue_.pop_front();
194
195 auto sample_attachments =
196 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(
197 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray(sbuf, true),
198 0));
199 const bool keyframe = !CFDictionaryContainsKey(
200 sample_attachments,
201 CoreMediaGlue::kCMSampleAttachmentKey_NotSync());
202 size_t used_buffer_size = 0;
203 const bool copy_rv = media::video_toolbox::CopySampleBufferToAnnexBBuffer(
204 sbuf, reinterpret_cast<uint8_t*>(shm->memory()), buffer.size(),
205 keyframe, &used_buffer_size);
206 CFRelease(sbuf);
207 if (copy_rv) {
208 client_->BitstreamBufferReady(buffer.id(), used_buffer_size, keyframe);
209 return;
210 }
211 }
212
213 scoped_ptr<BitstreamBufferRef> buffer_ref(
214 new BitstreamBufferRef(buffer.id(), std::move(shm), buffer.size()));
215 encoder_output_queue_.push_back(std::move(buffer_ref));
216 }
217
218 void VTVideoEncodeAccelerator::RequestEncodingParametersChange(
219 uint32_t bitrate,
220 uint32_t framerate) {
221 DVLOG(3) << __FUNCTION__;
222 DCHECK(thread_checker_.CalledOnValidThread());
223
224 bitrate_ = bitrate > 1 ? bitrate : 1;
225
226 if (!compression_session_) {
227 client_->NotifyError(kPlatformFailureError);
228 return;
229 }
230 // TODO(emircan): VideoToolbox does not seem to support bitrate
231 // reconfiguration, see crbug.com/425352.
232 const bool rv = session_property_setter_->SetSessionProperty(
233 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(),
234 static_cast<int32_t>(bitrate_));
235 if (!rv) {
236 DLOG(ERROR) << "Couldn't change session bitrate.";
237 }
238 }
239
240 void VTVideoEncodeAccelerator::Destroy() {
241 DVLOG(3) << __FUNCTION__;
242 DCHECK(thread_checker_.CalledOnValidThread());
243
244 DestroyCompressionSession();
245 delete this;
246 }
247
248 // static
249 void VTVideoEncodeAccelerator::CompressionCallback(void* encoder_opaque,
250 void* request_opaque,
251 OSStatus status,
252 VTEncodeInfoFlags info,
253 CMSampleBufferRef sbuf) {
254 // This function may be called asynchronously, on a different thread from the
255 // one that calls VTCompressionSessionEncodeFrame.
256 DVLOG(3) << __FUNCTION__;
257
258 auto encoder = reinterpret_cast<VTVideoEncodeAccelerator*>(encoder_opaque);
259 DCHECK(encoder);
260
261 if (status != noErr) {
262 DLOG(ERROR) << " encode failed: " << status;
263 encoder->client_task_runner_->PostTask(
264 FROM_HERE, base::Bind(&Client::NotifyError, encoder->client_,
265 kPlatformFailureError));
266 return;
267 }
268
269 if (info & VideoToolboxGlue::kVTEncodeInfo_FrameDropped) {
270 DVLOG(2) << " frame dropped";
271 return;
272 }
273
274 // CFRetain is required to hold onto CMSampleBufferRef when posting task
275 // between threads. The object should be released later using CFRelease.
276 CFRetain(sbuf);
277 // This method is NOT called on |client_task_runner_|, so we still need to
278 // post a task back to it to reach |client_|.
279 encoder->client_task_runner_->PostTask(
280 FROM_HERE,
281 base::Bind(&VTVideoEncodeAccelerator::CompressionCallbackTask,
282 base::Unretained(encoder), sbuf));
283 }
284
285 void VTVideoEncodeAccelerator::CompressionCallbackTask(CMSampleBufferRef sbuf) {
286 DVLOG(3) << __FUNCTION__;
287 DCHECK(thread_checker_.CalledOnValidThread());
288
289 // If there isn't any BitstreamBuffer to copy into, add it to a queue for
290 // later use.
291 if (encoder_output_queue_.empty()) {
292 encoder_output_sample_buffer_queue_.push_back(sbuf);
293 return;
294 }
295
296 auto sample_attachments = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(
297 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray(sbuf, true), 0));
298 const bool keyframe =
299 !CFDictionaryContainsKey(sample_attachments,
300 CoreMediaGlue::kCMSampleAttachmentKey_NotSync());
301
302 scoped_ptr<VTVideoEncodeAccelerator::BitstreamBufferRef> buffer_ref =
303 std::move(encoder_output_queue_.front());
304 encoder_output_queue_.pop_front();
305
306 size_t used_buffer_size = 0;
307 const bool copy_rv = media::video_toolbox::CopySampleBufferToAnnexBBuffer(
308 sbuf, reinterpret_cast<uint8_t*>(buffer_ref->shm->memory()),
309 buffer_ref->size, keyframe, &used_buffer_size);
310 CFRelease(sbuf);
311 if (!copy_rv) {
312 DLOG(ERROR) << "Cannot copy output from SampleBuffer to AnnexBBuffer.";
313 encoder_output_queue_.push_back(std::move(buffer_ref));
314 return;
315 }
316
317 client_->BitstreamBufferReady(buffer_ref->id, used_buffer_size, keyframe);
318 }
319
320 bool VTVideoEncodeAccelerator::ResetCompressionSession() {
321 DCHECK(thread_checker_.CalledOnValidThread());
322
323 DestroyCompressionSession();
324
325 base::ScopedCFTypeRef<CFDictionaryRef> encoder_spec =
326 media::video_toolbox::DictionaryWithKeyValue(videotoolbox_glue_
327 ->kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder(),
328 kCFBooleanTrue);
329
330 // Keep these in-sync with those in ConfigureCompressionSession().
331 CFTypeRef attributes_keys[] = {
332 #if defined(OS_IOS)
333 kCVPixelBufferOpenGLESCompatibilityKey,
334 #else
335 kCVPixelBufferOpenGLCompatibilityKey,
336 #endif
337 kCVPixelBufferIOSurfacePropertiesKey,
338 kCVPixelBufferPixelFormatTypeKey
339 };
340 const int format[] = {
341 CoreVideoGlue::kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange};
342 CFTypeRef attributes_values[] = {
343 kCFBooleanTrue,
344 media::video_toolbox::DictionaryWithKeysAndValues(nullptr, nullptr, 0)
345 .release(),
346 media::video_toolbox::ArrayWithIntegers(format, arraysize(format))
347 .release()};
348 const base::ScopedCFTypeRef<CFDictionaryRef> attributes =
349 media::video_toolbox::DictionaryWithKeysAndValues(
350 attributes_keys, attributes_values, arraysize(attributes_keys));
351 for (auto& v : attributes_values)
352 CFRelease(v);
353
354 // Create the compression session.
355 OSStatus status = videotoolbox_glue_->VTCompressionSessionCreate(
356 kCFAllocatorDefault,
357 input_visible_size_.width(),
358 input_visible_size_.height(),
359 CoreMediaGlue::kCMVideoCodecType_H264,
360 encoder_spec,
361 attributes,
362 nullptr /* compressedDataAllocator */,
363 &VTVideoEncodeAccelerator::CompressionCallback,
364 reinterpret_cast<void*>(this),
365 compression_session_.InitializeInto());
366 if (status != noErr) {
367 DLOG(ERROR) << " VTCompressionSessionCreate failed: " << status;
368 return false;
369 }
370
371 return ConfigureCompressionSession();
372 }
373
374 bool VTVideoEncodeAccelerator::ConfigureCompressionSession() {
375 DCHECK(thread_checker_.CalledOnValidThread());
376 DCHECK(compression_session_);
377
378 session_property_setter_.reset(
miu 2016/02/09 23:29:22 This shouldn't be allocated for the lifetime of th
emircan 2016/02/10 05:21:53 It is used on l.233 as well. Following example: ht
miu 2016/02/10 21:04:55 The point is that the optimizing compiler can do a
379 new media::video_toolbox::SessionPropertySetter(compression_session_,
380 videotoolbox_glue_));
381 bool rv = true;
382 rv &= session_property_setter_->SetSessionProperty(
383 videotoolbox_glue_->kVTCompressionPropertyKey_ProfileLevel(),
384 videotoolbox_glue_->kVTProfileLevel_H264_Baseline_AutoLevel());
385 rv &= session_property_setter_->SetSessionProperty(
386 videotoolbox_glue_->kVTCompressionPropertyKey_RealTime(), true);
387 rv &= session_property_setter_->SetSessionProperty(
388 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(),
389 static_cast<int32_t>(bitrate_));
390 rv &= session_property_setter_->SetSessionProperty(
391 videotoolbox_glue_->kVTCompressionPropertyKey_AllowFrameReordering(),
392 false);
393 DLOG_IF(ERROR, !rv) << " SetSessionProperty failed.";
394 return rv;
395 }
396
397 void VTVideoEncodeAccelerator::DestroyCompressionSession() {
398 DCHECK(thread_checker_.CalledOnValidThread());
399
400 if (compression_session_) {
401 videotoolbox_glue_->VTCompressionSessionInvalidate(compression_session_);
402 compression_session_.reset();
403 }
404 }
405
406 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698