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

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

Issue 1636083003: H264 HW encode using VideoToolbox (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: miu@ 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.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 #include "media/base/mac/videotoolbox_helpers.h"
12
13 namespace content {
14
15 namespace {
16
17 // Subjectively chosen.
18 const size_t kNumInputBuffers = 4;
miu 2016/02/04 22:23:20 IIRC, the VT encoder can hold onto several buffers
emircan 2016/02/07 04:24:05 I discussed with jfroy@ and added him as a reviewe
19 const size_t kMaxFrameRateNumerator = 30;
miu 2016/02/04 22:23:20 Is 30 really the max for all hardware solutions?
emircan 2016/02/07 04:24:05 I added a TODO for querying platform capabilities.
20 const size_t kMaxFrameRateDenominator = 1;
21 const size_t kMaxResolutionWidth = 4096;
22 const size_t kMaxResolutionHeight = 2160;
23 const size_t kOutputBufferSizeRatio = 10;
miu 2016/02/04 22:23:20 Please document this. What does 10 mean?
emircan 2016/02/07 04:24:05 Done.
24
25 // Container for the associated data of a video frame being processed.
26 struct InProgressFrameEncode {
27 const base::TimeDelta timestamp;
28 const base::TimeTicks reference_time;
29
30 InProgressFrameEncode(base::TimeDelta rtp_timestamp, base::TimeTicks ref_time)
31 : timestamp(rtp_timestamp), reference_time(ref_time) {}
32
33 private:
34 DISALLOW_IMPLICIT_CONSTRUCTORS(InProgressFrameEncode);
35 };
36
37 } // namespace
38
39 struct VTVideoEncodeAccelerator::BitstreamBufferRef {
40 BitstreamBufferRef(int32_t id,
41 scoped_ptr<base::SharedMemory> shm,
42 size_t size)
43 : id(id), shm(std::move(shm)), size(size) {}
44 const int32_t id;
45 const scoped_ptr<base::SharedMemory> shm;
46 const size_t size;
47
48 private:
49 DISALLOW_IMPLICIT_CONSTRUCTORS(BitstreamBufferRef);
50 };
51
52 VTVideoEncodeAccelerator::VTVideoEncodeAccelerator()
53 : videotoolbox_glue_(VideoToolboxGlue::Get()),
54 client_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
55 }
56
57 VTVideoEncodeAccelerator::~VTVideoEncodeAccelerator() {
58 DCHECK(thread_checker_.CalledOnValidThread());
59 }
60
61 media::VideoEncodeAccelerator::SupportedProfiles
62 VTVideoEncodeAccelerator::GetSupportedProfiles() {
63 DVLOG(3) << __FUNCTION__;
64 DCHECK(thread_checker_.CalledOnValidThread());
65
66 SupportedProfiles profiles;
67 SupportedProfile profile;
68 profile.profile = media::H264PROFILE_BASELINE;
69 profile.max_framerate_numerator = kMaxFrameRateNumerator;
70 profile.max_framerate_denominator = kMaxFrameRateDenominator;
71 profile.max_resolution = gfx::Size(kMaxResolutionWidth, kMaxResolutionHeight);
72 profiles.push_back(profile);
73 return profiles;
74 }
75
76 bool VTVideoEncodeAccelerator::Initialize(
77 media::VideoPixelFormat format,
78 const gfx::Size& input_visible_size,
79 media::VideoCodecProfile output_profile,
80 uint32_t initial_bitrate,
81 Client* client) {
82 DVLOG(3) << __FUNCTION__
83 << ": input_format=" << media::VideoPixelFormatToString(format)
84 << ", input_visible_size=" << input_visible_size.ToString()
85 << ", output_profile=" << output_profile
86 << ", initial_bitrate=" << initial_bitrate;
87 DCHECK(thread_checker_.CalledOnValidThread());
88 DCHECK(client);
89 DCHECK_EQ(media::PIXEL_FORMAT_I420, format);
90 DCHECK_EQ(media::H264PROFILE_BASELINE, output_profile);
91
92 bitrate_ = initial_bitrate;
93 input_visible_size_ = input_visible_size;
94
95 if (!ResetCompressionSession()) {
96 DLOG(ERROR) << "Failed creating compression session";
97 return false;
98 }
99
100 client_ptr_factory_.reset(new base::WeakPtrFactory<Client>(client));
101 client_ = client_ptr_factory_->GetWeakPtr();
102 client_->RequireBitstreamBuffers(
103 kNumInputBuffers, input_visible_size_,
104 input_visible_size_.GetArea() / kOutputBufferSizeRatio);
105 return true;
106 }
107
108 void VTVideoEncodeAccelerator::Encode(
109 const scoped_refptr<media::VideoFrame>& frame,
110 bool force_keyframe) {
111 DVLOG(3) << __FUNCTION__;
112 DCHECK(thread_checker_.CalledOnValidThread());
113 DCHECK(compression_session_);
114 DCHECK(frame);
115
116 base::TimeTicks ref_time;
117 if (!frame->metadata()->GetTimeTicks(
118 media::VideoFrameMetadata::REFERENCE_TIME, &ref_time)) {
119 ref_time = base::TimeTicks::Now();
120 }
121 auto timestamp_cm = CoreMediaGlue::CMTimeMake(
122 (ref_time - base::TimeTicks()).InMicroseconds(), USEC_PER_SEC);
miu 2016/02/04 22:23:20 IIUC, this should be frame->timestamp(), not (ref_
emircan 2016/02/07 04:24:04 Done.
123 // Wrap information we'll need after the frame is encoded in a heap object.
124 // We'll get the pointer back from the VideoToolbox completion callback.
125 scoped_ptr<InProgressFrameEncode> request(new InProgressFrameEncode(
126 frame->timestamp(), ref_time));
127
128 // TODO(emircan): See if we can eliminate a copy here by using
129 // CVPixelBufferPool for the allocation of incoming VideoFrames.
130 base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer =
131 media::WrapVideoFrameInCVPixelBuffer(*frame);
132 base::ScopedCFTypeRef<CFDictionaryRef> frame_props =
133 media::DictionaryWithKeyValue(
134 videotoolbox_glue_->kVTEncodeFrameOptionKey_ForceKeyFrame(),
135 force_keyframe ? kCFBooleanTrue : kCFBooleanFalse);
136
137 OSStatus status = videotoolbox_glue_->VTCompressionSessionEncodeFrame(
138 compression_session_, pixel_buffer, timestamp_cm,
139 CoreMediaGlue::CMTime{0, 0, 0, 0}, frame_props,
140 reinterpret_cast<void*>(request.release()), nullptr);
141 DLOG_IF(ERROR, status != noErr)
142 << " VTCompressionSessionEncodeFrame failed: " << status;
143 }
144
145 void VTVideoEncodeAccelerator::UseOutputBitstreamBuffer(
146 const media::BitstreamBuffer& buffer) {
147 DVLOG(3) << __FUNCTION__;
148 DCHECK(thread_checker_.CalledOnValidThread());
149 DCHECK_GE(buffer.size(), static_cast<size_t>(input_visible_size_.GetArea() /
150 kOutputBufferSizeRatio));
151
152 scoped_ptr<base::SharedMemory> shm(
153 new base::SharedMemory(buffer.handle(), false));
154 if (!shm->Map(buffer.size())) {
155 DLOG(ERROR) << "Failed mapping shared memory.";
156 return;
157 }
158
159 scoped_ptr<BitstreamBufferRef> buffer_ref(
160 new BitstreamBufferRef(buffer.id(), std::move(shm), buffer.size()));
161 encoder_output_queue_.push_back(std::move(buffer_ref));
162 }
163
164 void VTVideoEncodeAccelerator::RequestEncodingParametersChange(
165 uint32_t bitrate,
166 uint32_t framerate) {
167 DVLOG(3) << __FUNCTION__;
168 DCHECK(thread_checker_.CalledOnValidThread());
169
170 bitrate_ = bitrate;
171 if (!compression_session_)
172 return;
173
174 media::SetSessionProperty(
175 compression_session_, videotoolbox_glue_,
176 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(),
177 static_cast<int32_t>(bitrate_));
178 }
179
180 void VTVideoEncodeAccelerator::Destroy() {
181 DVLOG(3) << __FUNCTION__;
182 DCHECK(thread_checker_.CalledOnValidThread());
183
184 DestroyCompressionSession();
185 delete this;
186 }
187
188 // static
189 void VTVideoEncodeAccelerator::CompressionCallback(void* encoder_opaque,
190 void* request_opaque,
191 OSStatus status,
192 VTEncodeInfoFlags info,
193 CMSampleBufferRef sbuf) {
194 DVLOG(3) << __FUNCTION__;
195
196 if (status != noErr)
197 DLOG(ERROR) << " encode failed: " << status;
198
199 if (info & VideoToolboxGlue::kVTEncodeInfo_FrameDropped) {
200 DVLOG(2) << " frame dropped";
201 return;
miu 2016/02/04 22:23:20 You can't return early anywhere in this function.
emircan 2016/02/07 04:24:04 I am holding onto a BitstreamBuffer from the queue
miu 2016/02/09 23:29:22 I could be misunderstanding the intended semantics
emircan 2016/02/10 05:21:53 I see the sequence assumption. Cast VT code seems
202 }
203
204 auto sample_attachments = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(
205 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray(sbuf, true), 0));
206 const bool keyframe =
207 !CFDictionaryContainsKey(sample_attachments,
208 CoreMediaGlue::kCMSampleAttachmentKey_NotSync());
209 auto encoder = reinterpret_cast<VTVideoEncodeAccelerator*>(encoder_opaque);
210 if (!encoder) {
miu 2016/02/04 22:23:20 This should never be null. DCHECK(!encoder) would
emircan 2016/02/07 04:24:05 Done.
211 DLOG(ERROR) << "No encoder reference.";
212 return;
213 }
214 if (encoder->encoder_output_queue_.empty()) {
215 DLOG(ERROR) << "No more bitstream buffer to encode into.";
miu 2016/02/04 22:23:20 You should call Client::NotifyError() and either:
emircan 2016/02/07 04:24:05 I added NotifyError call. However, I don't underst
miu 2016/02/09 23:29:22 When decoding frames, there are implicit dependenc
emircan 2016/02/10 05:21:53 I changed this behavior after posciak@ comments. I
216 return;
217 }
218 scoped_ptr<VTVideoEncodeAccelerator::BitstreamBufferRef> buffer_ref =
219 std::move(encoder->encoder_output_queue_.front());
220 encoder->encoder_output_queue_.pop_front();
221
222 std::string annexb_buffer;
223 media::CopySampleBufferToAnnexBBuffer(sbuf, &annexb_buffer, keyframe);
224 if (buffer_ref->size < annexb_buffer.size()) {
225 DLOG(ERROR) << "Cannot fit the encode output into bitstream buffer.";
226 return;
227 }
228 memcpy(buffer_ref->shm->memory(), annexb_buffer.data(),
miu 2016/02/04 22:23:20 Please remove this redundant memcpy(). Just provi
emircan 2016/02/07 04:24:05 media::CopySampleBufferToAnnexBBuffer() expects a
229 annexb_buffer.size());
230
231 // This method is NOT called on |client_task_runner_|, so we still need to
232 // post a task back to it to reach |client_|.
233 encoder->client_task_runner_->PostTask(
234 FROM_HERE, base::Bind(&Client::BitstreamBufferReady, encoder->client_,
235 buffer_ref->id, annexb_buffer.size(), keyframe));
236 }
237
238 bool VTVideoEncodeAccelerator::ResetCompressionSession() {
239 DCHECK(thread_checker_.CalledOnValidThread());
240
241 DestroyCompressionSession();
242
243 base::ScopedCFTypeRef<CFDictionaryRef> encoder_spec;
244 encoder_spec = media::DictionaryWithKeyValue(
245 videotoolbox_glue_
246 ->kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder() ,
miu 2016/02/04 22:23:20 80 chars
emircan 2016/02/07 04:24:05 Done.
247 kCFBooleanTrue);
248
249 // Keep these in-sync with those in ConfigureSession().
250 CFTypeRef attributes_keys[] = {
251 #if defined(OS_IOS)
252 kCVPixelBufferOpenGLESCompatibilityKey,
253 #else
254 kCVPixelBufferOpenGLCompatibilityKey,
255 #endif
256 kCVPixelBufferIOSurfacePropertiesKey,
257 kCVPixelBufferPixelFormatTypeKey
258 };
259 const int format[] = {
260 CoreVideoGlue::kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange};
261 CFTypeRef attributes_values[] = {
262 kCFBooleanTrue,
263 media::DictionaryWithKeysAndValues(nullptr, nullptr, 0).release(),
264 media::ArrayWithIntegers(format, arraysize(format)).release()};
265 const base::ScopedCFTypeRef<CFDictionaryRef> attributes =
266 media::DictionaryWithKeysAndValues(attributes_keys,
267 attributes_values,
268 arraysize(attributes_keys));
269 for (auto& v : attributes_values)
270 CFRelease(v);
271
272 // Create the compression session.
273 OSStatus status = videotoolbox_glue_->VTCompressionSessionCreate(
274 kCFAllocatorDefault,
275 input_visible_size_.width(),
276 input_visible_size_.height(),
277 CoreMediaGlue::kCMVideoCodecType_H264,
278 encoder_spec,
279 attributes,
280 nullptr /* compressedDataAllocator */,
281 &VTVideoEncodeAccelerator::CompressionCallback,
282 reinterpret_cast<void*>(this),
283 compression_session_.InitializeInto());
284 if (status != noErr) {
285 DLOG(ERROR) << " VTCompressionSessionCreate failed: " << status;
286 return false;
287 }
288 ConfigureCompressionSession();
289 return true;
290 }
291
292 void VTVideoEncodeAccelerator::ConfigureCompressionSession() {
293 DCHECK(thread_checker_.CalledOnValidThread());
294 DCHECK(compression_session_);
295
296 media::SetSessionProperty(
miu 2016/02/04 22:23:20 Suggestion: Instead of passing compression_session
emircan 2016/02/07 04:24:05 Done.
297 compression_session_, videotoolbox_glue_,
298 videotoolbox_glue_->kVTCompressionPropertyKey_ProfileLevel(),
299 videotoolbox_glue_->kVTProfileLevel_H264_Baseline_AutoLevel());
300 media::SetSessionProperty(
301 compression_session_, videotoolbox_glue_,
302 videotoolbox_glue_->kVTCompressionPropertyKey_RealTime(), true);
303 media::SetSessionProperty(
304 compression_session_, videotoolbox_glue_,
305 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(),
306 static_cast<int32_t>(bitrate_));
307 media::SetSessionProperty(
308 compression_session_, videotoolbox_glue_,
309 videotoolbox_glue_->kVTCompressionPropertyKey_AllowFrameReordering(),
310 false);
311 }
312
313 void VTVideoEncodeAccelerator::DestroyCompressionSession() {
314 DCHECK(thread_checker_.CalledOnValidThread());
315
316 if (compression_session_) {
317 videotoolbox_glue_->VTCompressionSessionInvalidate(compression_session_);
318 compression_session_.reset();
319 }
320 }
321
322 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698