OLD | NEW |
---|---|
(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 | |
OLD | NEW |