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

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: 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;
19 const size_t kMaxFrameRateNumerator = 30;
20 const size_t kMaxFrameRateDenominator = 1;
21 const size_t kMaxResolutionWidth = 4096;
22 const size_t kMaxResolutionHeight = 2160;
23 const size_t kOutputBufferSizeRatio = 10;
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(
31 base::TimeDelta rtp_timestamp,
32 base::TimeTicks ref_time)
33 : timestamp(rtp_timestamp),
34 reference_time(ref_time) {}
35 };
mcasas 2016/02/03 20:01:31 Recommend a private: DISALLOW_IMPLICIT_CONSTRUCT
emircan 2016/02/04 05:18:52 Done.
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 };
mcasas 2016/02/03 20:01:31 Same here, use DISALLOW_IMPLICIT_CONSTRUCTORS
emircan 2016/02/04 05:18:51 Done.
48
49 VTVideoEncodeAccelerator::VTVideoEncodeAccelerator()
50 : videotoolbox_glue_(VideoToolboxGlue::Get()),
51 child_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
52 }
53
54 VTVideoEncodeAccelerator::~VTVideoEncodeAccelerator() {
55 DCHECK(child_task_runner_->BelongsToCurrentThread());
56 }
57
58 media::VideoEncodeAccelerator::SupportedProfiles
59 VTVideoEncodeAccelerator::GetSupportedProfiles() {
60 DVLOG(3) << __FUNCTION__;
61
62 SupportedProfiles profiles;
63 SupportedProfile profile;
64 profile.profile = media::H264PROFILE_BASELINE;
65 profile.max_framerate_numerator = kMaxFrameRateNumerator;
66 profile.max_framerate_denominator = kMaxFrameRateDenominator;
67 profile.max_resolution = gfx::Size(kMaxResolutionWidth, kMaxResolutionHeight);
68 profiles.push_back(profile);
69 return profiles;
70 }
71
72 bool VTVideoEncodeAccelerator::Initialize(
73 media::VideoPixelFormat format,
74 const gfx::Size& input_visible_size,
75 media::VideoCodecProfile output_profile,
76 uint32_t initial_bitrate,
77 Client* client) {
78 DVLOG(3) << __FUNCTION__
79 << ": input_format=" << media::VideoPixelFormatToString(format)
80 << ", input_visible_size=" << input_visible_size.ToString()
81 << ", output_profile=" << output_profile
82 << ", initial_bitrate=" << initial_bitrate;
83 DCHECK(client);
84 DCHECK_EQ(media::PIXEL_FORMAT_I420, format);
85 DCHECK_EQ(media::H264PROFILE_BASELINE, output_profile);
86
87 bitrate_ = initial_bitrate;
88 input_visible_size_ = input_visible_size;
89
90 const bool session_created = ResetCompressionSession();
mcasas 2016/02/03 20:01:31 No need for temporary?
emircan 2016/02/04 05:18:52 Done. I was trying to be verbose.
91 if (!session_created) {
92 DLOG(ERROR) << "Failed creating compression session";
93 return false;
94 }
95
96 client_ptr_factory_.reset(new base::WeakPtrFactory<Client>(client));
mcasas 2016/02/03 20:01:31 This |client_ptr_factory_| is only used here, do y
emircan 2016/02/04 05:18:51 I can invalidate the weakptrs in Destroy() using i
97 client_ = client_ptr_factory_->GetWeakPtr();
98
99 child_task_runner_->PostTask(
100 FROM_HERE,
101 base::Bind(&Client::RequireBitstreamBuffers, client_, kNumInputBuffers,
102 input_visible_size_,
103 input_visible_size_.GetArea() / kOutputBufferSizeRatio));
hbos_chromium 2016/02/03 18:21:08 About thread-safety, how is this class used across
emircan 2016/02/04 05:18:52 Thanks for pointing it out. Actually, I don't need
104 return true;
105 }
106
107 void VTVideoEncodeAccelerator::Encode(
108 const scoped_refptr<media::VideoFrame>& frame,
109 bool force_keyframe) {
110 DVLOG(3) << __FUNCTION__;
111 DCHECK(thread_checker_.CalledOnValidThread());
112 DCHECK(compression_session_);
113 DCHECK(frame);
114
115 base::TimeTicks ref_time;
116 if (!frame->metadata()->GetTimeTicks(
117 media::VideoFrameMetadata::REFERENCE_TIME, &ref_time)) {
118 ref_time = base::TimeTicks::Now();
119 }
120 auto timestamp_cm = CoreMediaGlue::CMTimeMake(
121 (ref_time - base::TimeTicks()).InMicroseconds(), USEC_PER_SEC);
122 // Wrap information we'll need after the frame is encoded in a heap object.
123 // We'll get the pointer back from the VideoToolbox completion callback.
124 scoped_ptr<InProgressFrameEncode> request(new InProgressFrameEncode(
125 frame->timestamp(), ref_time));
126
127 // TODO(emircan): See if we can eliminate a copy here by using
128 // CVPixelBufferPool for the allocation of incoming video frames.
mcasas 2016/02/03 20:01:31 nit: s/video frames/VideoFrames/?
emircan 2016/02/04 05:18:52 Done.
129 base::ScopedCFTypeRef<CVPixelBufferRef> pixel_buffer =
130 media::WrapVideoFrameInCVPixelBuffer(*frame);
131 base::ScopedCFTypeRef<CFDictionaryRef> frame_props =
132 media::DictionaryWithKeyValue(
133 videotoolbox_glue_->kVTEncodeFrameOptionKey_ForceKeyFrame(),
134 force_keyframe ? kCFBooleanTrue : kCFBooleanFalse);
135
136 OSStatus status = videotoolbox_glue_->VTCompressionSessionEncodeFrame(
137 compression_session_, pixel_buffer, timestamp_cm,
138 CoreMediaGlue::CMTime{0, 0, 0, 0}, frame_props,
139 reinterpret_cast<void*>(request.release()), nullptr);
140 DLOG_IF(ERROR, status != noErr)
141 << " VTCompressionSessionEncodeFrame failed: " << status;
142 }
143
144 void VTVideoEncodeAccelerator::UseOutputBitstreamBuffer(
145 const media::BitstreamBuffer& buffer) {
146 DVLOG(3) << __FUNCTION__;
147 DCHECK(child_task_runner_->BelongsToCurrentThread());
148 DCHECK_GE(buffer.size(), static_cast<size_t>(input_visible_size_.GetArea() /
149 kOutputBufferSizeRatio));
150
151 scoped_ptr<base::SharedMemory> shm(
152 new base::SharedMemory(buffer.handle(), false));
153 if (!shm->Map(buffer.size())) {
154 DLOG(ERROR) << "Failed mapping shared memory.";
155 return;
156 }
157
158 scoped_ptr<BitstreamBufferRef> buffer_ref(
159 new BitstreamBufferRef(buffer.id(), std::move(shm), buffer.size()));
160 encoder_output_queue_.push_back(std::move(buffer_ref));
161 }
162
163 void VTVideoEncodeAccelerator::RequestEncodingParametersChange(
164 uint32_t bitrate,
165 uint32_t framerate) {
166 DVLOG(3) << __FUNCTION__;
167
168 bitrate_ = bitrate;
169 if (compression_session_) {
hbos_chromium 2016/02/03 18:21:08 Does it make sense to DCHECK instead?
mcasas 2016/02/03 20:01:31 nit: if (!compression_session_) return; ...
emircan 2016/02/04 05:18:52 Done.
emircan 2016/02/04 05:18:52 Acknowledged.
170 media::SetSessionProperty(
171 compression_session_, videotoolbox_glue_,
172 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(),
173 static_cast<int32_t>(bitrate_));
174 }
175 }
176
177 void VTVideoEncodeAccelerator::Destroy() {
178 DVLOG(3) << __FUNCTION__;
179
180 client_ptr_factory_.reset();
181 DestroyCompressionSession();
182 delete this;
183 }
184
185 // static
186 void VTVideoEncodeAccelerator::CompressionCallback(void* encoder_opaque,
187 void* request_opaque,
188 OSStatus status,
189 VTEncodeInfoFlags info,
190 CMSampleBufferRef sbuf) {
191 DVLOG(3) << __FUNCTION__;
192 DLOG_IF(ERROR, status != noErr) << " encode failed: " << status;
193
194 if (info & VideoToolboxGlue::kVTEncodeInfo_FrameDropped) {
195 DVLOG(2) << " frame dropped";
196 return;
197 }
198
199 auto sample_attachments = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(
200 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray(sbuf, true), 0));
201 const bool keyframe =
202 !CFDictionaryContainsKey(sample_attachments,
203 CoreMediaGlue::kCMSampleAttachmentKey_NotSync());
204
205 auto encoder = reinterpret_cast<VTVideoEncodeAccelerator*>(encoder_opaque);
206 if (encoder->encoder_output_queue_.empty()) {
207 DLOG(ERROR) << "No more bitstream buffer to encode into.";
208 return;
209 }
210 scoped_ptr<VTVideoEncodeAccelerator::BitstreamBufferRef> buffer_ref =
211 std::move(encoder->encoder_output_queue_.front());
212 encoder->encoder_output_queue_.pop_front();
213
214 std::string annexb_buffer;
215 media::CopySampleBufferToAnnexBBuffer(sbuf, &annexb_buffer, keyframe);
216 if (buffer_ref->size < annexb_buffer.size()) {
217 DLOG(ERROR) << "Cannot fit the encode output into bitstream buffer.";
218 return;
219 }
220 memcpy(buffer_ref->shm->memory(), annexb_buffer.data(),
221 annexb_buffer.size());
222
223 encoder->child_task_runner_->PostTask(
224 FROM_HERE, base::Bind(&Client::BitstreamBufferReady, encoder->client_,
225 buffer_ref->id, annexb_buffer.size(), keyframe));
226 }
227
228 bool VTVideoEncodeAccelerator::ResetCompressionSession() {
229 DCHECK(thread_checker_.CalledOnValidThread());
230
231 DestroyCompressionSession();
232
233 // TODO(emircan): Investigate IOS constraints.
mcasas 2016/02/03 20:01:31 ?
emircan 2016/02/04 05:18:51 Removed.
234 base::ScopedCFTypeRef<CFDictionaryRef> encoder_spec;
235 encoder_spec = media::DictionaryWithKeyValue(
236 videotoolbox_glue_
237 ->kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder() ,
238 kCFBooleanTrue);
239
240 // Keep these in-sync with those in ConfigureSession().
241 CFTypeRef attributes_keys[] = {
242 #if defined(OS_IOS)
243 kCVPixelBufferOpenGLESCompatibilityKey,
244 #else
245 kCVPixelBufferOpenGLCompatibilityKey,
246 #endif
247 kCVPixelBufferIOSurfacePropertiesKey,
248 kCVPixelBufferPixelFormatTypeKey
249 };
250 const int format[] = {
251 CoreVideoGlue::kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange};
252 CFTypeRef attributes_values[] = {
253 kCFBooleanTrue,
254 media::DictionaryWithKeysAndValues(nullptr, nullptr, 0).release(),
255 media::ArrayWithIntegers(format, arraysize(format)).release()};
256 const base::ScopedCFTypeRef<CFDictionaryRef> attributes =
257 media::DictionaryWithKeysAndValues(attributes_keys,
258 attributes_values,
259 arraysize(attributes_keys));
260 for (auto& v : attributes_values)
261 CFRelease(v);
262
263 // Create the compression session.
264 OSStatus status = videotoolbox_glue_->VTCompressionSessionCreate(
265 kCFAllocatorDefault,
266 input_visible_size_.width(),
267 input_visible_size_.height(),
268 CoreMediaGlue::kCMVideoCodecType_H264,
269 encoder_spec,
270 attributes,
271 nullptr /* compressedDataAllocator */,
272 &VTVideoEncodeAccelerator::CompressionCallback,
273 reinterpret_cast<void*>(this),
274 compression_session_.InitializeInto());
275 if (status != noErr) {
276 DLOG(ERROR) << " VTCompressionSessionCreate failed: " << status;
277 return false;
278 }
279 ConfigureCompressionSession();
280 return true;
281 }
282
283 void VTVideoEncodeAccelerator::ConfigureCompressionSession() {
284 DCHECK(compression_session_);
285
286 media::SetSessionProperty(
287 compression_session_, videotoolbox_glue_,
288 videotoolbox_glue_->kVTCompressionPropertyKey_ProfileLevel(),
289 videotoolbox_glue_->kVTProfileLevel_H264_Baseline_AutoLevel());
290 media::SetSessionProperty(
291 compression_session_, videotoolbox_glue_,
292 videotoolbox_glue_->kVTCompressionPropertyKey_RealTime(), true);
293 media::SetSessionProperty(
294 compression_session_, videotoolbox_glue_,
295 videotoolbox_glue_->kVTCompressionPropertyKey_AverageBitRate(),
296 static_cast<int32_t>(bitrate_));
297 media::SetSessionProperty(
298 compression_session_, videotoolbox_glue_,
299 videotoolbox_glue_->kVTCompressionPropertyKey_AllowFrameReordering(),
300 false);
301 }
302
303 void VTVideoEncodeAccelerator::DestroyCompressionSession() {
304 DCHECK(thread_checker_.CalledOnValidThread());
305
306 if (compression_session_) {
307 videotoolbox_glue_->VTCompressionSessionInvalidate(compression_session_);
308 compression_session_.reset();
309 }
310 }
311
312 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698