blob: 8a2bafa09d02015f3836484c8b84041fa9d7f737 [file] [log] [blame]
magjed73c0eb52017-08-07 06:55:28 -07001/*
2 * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 *
10 */
11
12#import "WebRTC/RTCVideoCodecH264.h"
13
14#import <VideoToolbox/VideoToolbox.h>
15#include <vector>
16
17#if defined(WEBRTC_IOS)
18#import "Common/RTCUIApplicationStatusObserver.h"
19#import "WebRTC/UIDevice+RTCDevice.h"
20#endif
21#import "PeerConnection/RTCVideoCodec+Private.h"
22#import "WebRTC/RTCVideoCodec.h"
23#import "WebRTC/RTCVideoFrame.h"
24#import "WebRTC/RTCVideoFrameBuffer.h"
25#import "helpers.h"
26#include "libyuv/convert_from.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020027#include "common_video/h264/h264_bitstream_parser.h"
28#include "common_video/h264/profile_level_id.h"
29#include "common_video/include/bitrate_adjuster.h"
30#include "modules/include/module_common_types.h"
31#include "modules/video_coding/include/video_error_codes.h"
32#include "rtc_base/buffer.h"
33#include "rtc_base/logging.h"
34#include "rtc_base/timeutils.h"
35#include "sdk/objc/Framework/Classes/VideoToolbox/nalu_rewriter.h"
36#include "system_wrappers/include/clock.h"
magjed73c0eb52017-08-07 06:55:28 -070037
38@interface RTCVideoEncoderH264 ()
39
40- (void)frameWasEncoded:(OSStatus)status
41 flags:(VTEncodeInfoFlags)infoFlags
42 sampleBuffer:(CMSampleBufferRef)sampleBuffer
43 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
44 width:(int32_t)width
45 height:(int32_t)height
46 renderTimeMs:(int64_t)renderTimeMs
47 timestamp:(uint32_t)timestamp
48 rotation:(RTCVideoRotation)rotation;
49
50@end
51
Kári Tristan Helgason0bf60712017-09-25 10:26:42 +020052namespace { // anonymous namespace
53
magjed73c0eb52017-08-07 06:55:28 -070054// The ratio between kVTCompressionPropertyKey_DataRateLimits and
55// kVTCompressionPropertyKey_AverageBitRate. The data rate limit is set higher
56// than the average bit rate to avoid undershooting the target.
57const float kLimitToAverageBitRateFactor = 1.5f;
58// These thresholds deviate from the default h264 QP thresholds, as they
59// have been found to work better on devices that support VideoToolbox
60const int kLowH264QpThreshold = 28;
61const int kHighH264QpThreshold = 39;
62
63// Struct that we pass to the encoder per frame to encode. We receive it again
64// in the encoder callback.
65struct RTCFrameEncodeParams {
66 RTCFrameEncodeParams(RTCVideoEncoderH264 *e,
67 RTCCodecSpecificInfoH264 *csi,
68 int32_t w,
69 int32_t h,
70 int64_t rtms,
71 uint32_t ts,
72 RTCVideoRotation r)
73 : encoder(e), width(w), height(h), render_time_ms(rtms), timestamp(ts), rotation(r) {
74 if (csi) {
75 codecSpecificInfo = csi;
76 } else {
77 codecSpecificInfo = [[RTCCodecSpecificInfoH264 alloc] init];
78 }
79 }
80
81 RTCVideoEncoderH264 *encoder;
82 RTCCodecSpecificInfoH264 *codecSpecificInfo;
83 int32_t width;
84 int32_t height;
85 int64_t render_time_ms;
86 uint32_t timestamp;
87 RTCVideoRotation rotation;
88};
89
90// We receive I420Frames as input, but we need to feed CVPixelBuffers into the
91// encoder. This performs the copy and format conversion.
92// TODO(tkchin): See if encoder will accept i420 frames and compare performance.
93bool CopyVideoFrameToPixelBuffer(id<RTCI420Buffer> frameBuffer, CVPixelBufferRef pixelBuffer) {
94 RTC_DCHECK(pixelBuffer);
95 RTC_DCHECK_EQ(CVPixelBufferGetPixelFormatType(pixelBuffer),
96 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange);
97 RTC_DCHECK_EQ(CVPixelBufferGetHeightOfPlane(pixelBuffer, 0), frameBuffer.height);
98 RTC_DCHECK_EQ(CVPixelBufferGetWidthOfPlane(pixelBuffer, 0), frameBuffer.width);
99
100 CVReturn cvRet = CVPixelBufferLockBaseAddress(pixelBuffer, 0);
101 if (cvRet != kCVReturnSuccess) {
102 LOG(LS_ERROR) << "Failed to lock base address: " << cvRet;
103 return false;
104 }
105 uint8_t *dstY = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0));
106 int dstStrideY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
107 uint8_t *dstUV = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1));
108 int dstStrideUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
109 // Convert I420 to NV12.
110 int ret = libyuv::I420ToNV12(frameBuffer.dataY,
111 frameBuffer.strideY,
112 frameBuffer.dataU,
113 frameBuffer.strideU,
114 frameBuffer.dataV,
115 frameBuffer.strideV,
116 dstY,
117 dstStrideY,
118 dstUV,
119 dstStrideUV,
120 frameBuffer.width,
121 frameBuffer.height);
122 CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
123 if (ret) {
124 LOG(LS_ERROR) << "Error converting I420 VideoFrame to NV12 :" << ret;
125 return false;
126 }
127 return true;
128}
129
130CVPixelBufferRef CreatePixelBuffer(CVPixelBufferPoolRef pixel_buffer_pool) {
131 if (!pixel_buffer_pool) {
132 LOG(LS_ERROR) << "Failed to get pixel buffer pool.";
133 return nullptr;
134 }
135 CVPixelBufferRef pixel_buffer;
136 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(nullptr, pixel_buffer_pool, &pixel_buffer);
137 if (ret != kCVReturnSuccess) {
138 LOG(LS_ERROR) << "Failed to create pixel buffer: " << ret;
139 // We probably want to drop frames here, since failure probably means
140 // that the pool is empty.
141 return nullptr;
142 }
143 return pixel_buffer;
144}
145
146// This is the callback function that VideoToolbox calls when encode is
147// complete. From inspection this happens on its own queue.
148void compressionOutputCallback(void *encoder,
149 void *params,
150 OSStatus status,
151 VTEncodeInfoFlags infoFlags,
152 CMSampleBufferRef sampleBuffer) {
magjed85d18d42017-09-01 06:32:57 -0700153 RTC_CHECK(params);
magjed73c0eb52017-08-07 06:55:28 -0700154 std::unique_ptr<RTCFrameEncodeParams> encodeParams(
155 reinterpret_cast<RTCFrameEncodeParams *>(params));
magjed85d18d42017-09-01 06:32:57 -0700156 RTC_CHECK(encodeParams->encoder);
magjed73c0eb52017-08-07 06:55:28 -0700157 [encodeParams->encoder frameWasEncoded:status
158 flags:infoFlags
159 sampleBuffer:sampleBuffer
160 codecSpecificInfo:encodeParams->codecSpecificInfo
161 width:encodeParams->width
162 height:encodeParams->height
163 renderTimeMs:encodeParams->render_time_ms
164 timestamp:encodeParams->timestamp
165 rotation:encodeParams->rotation];
166}
167
168// Extract VideoToolbox profile out of the cricket::VideoCodec. If there is no
169// specific VideoToolbox profile for the specified level, AutoLevel will be
170// returned. The user must initialize the encoder with a resolution and
171// framerate conforming to the selected H264 level regardless.
172CFStringRef ExtractProfile(const cricket::VideoCodec &codec) {
173 const rtc::Optional<webrtc::H264::ProfileLevelId> profile_level_id =
174 webrtc::H264::ParseSdpProfileLevelId(codec.params);
175 RTC_DCHECK(profile_level_id);
176 switch (profile_level_id->profile) {
177 case webrtc::H264::kProfileConstrainedBaseline:
178 case webrtc::H264::kProfileBaseline:
179 switch (profile_level_id->level) {
180 case webrtc::H264::kLevel3:
181 return kVTProfileLevel_H264_Baseline_3_0;
182 case webrtc::H264::kLevel3_1:
183 return kVTProfileLevel_H264_Baseline_3_1;
184 case webrtc::H264::kLevel3_2:
185 return kVTProfileLevel_H264_Baseline_3_2;
186 case webrtc::H264::kLevel4:
187 return kVTProfileLevel_H264_Baseline_4_0;
188 case webrtc::H264::kLevel4_1:
189 return kVTProfileLevel_H264_Baseline_4_1;
190 case webrtc::H264::kLevel4_2:
191 return kVTProfileLevel_H264_Baseline_4_2;
192 case webrtc::H264::kLevel5:
193 return kVTProfileLevel_H264_Baseline_5_0;
194 case webrtc::H264::kLevel5_1:
195 return kVTProfileLevel_H264_Baseline_5_1;
196 case webrtc::H264::kLevel5_2:
197 return kVTProfileLevel_H264_Baseline_5_2;
198 case webrtc::H264::kLevel1:
199 case webrtc::H264::kLevel1_b:
200 case webrtc::H264::kLevel1_1:
201 case webrtc::H264::kLevel1_2:
202 case webrtc::H264::kLevel1_3:
203 case webrtc::H264::kLevel2:
204 case webrtc::H264::kLevel2_1:
205 case webrtc::H264::kLevel2_2:
206 return kVTProfileLevel_H264_Baseline_AutoLevel;
207 }
208
209 case webrtc::H264::kProfileMain:
210 switch (profile_level_id->level) {
211 case webrtc::H264::kLevel3:
212 return kVTProfileLevel_H264_Main_3_0;
213 case webrtc::H264::kLevel3_1:
214 return kVTProfileLevel_H264_Main_3_1;
215 case webrtc::H264::kLevel3_2:
216 return kVTProfileLevel_H264_Main_3_2;
217 case webrtc::H264::kLevel4:
218 return kVTProfileLevel_H264_Main_4_0;
219 case webrtc::H264::kLevel4_1:
220 return kVTProfileLevel_H264_Main_4_1;
221 case webrtc::H264::kLevel4_2:
222 return kVTProfileLevel_H264_Main_4_2;
223 case webrtc::H264::kLevel5:
224 return kVTProfileLevel_H264_Main_5_0;
225 case webrtc::H264::kLevel5_1:
226 return kVTProfileLevel_H264_Main_5_1;
227 case webrtc::H264::kLevel5_2:
228 return kVTProfileLevel_H264_Main_5_2;
229 case webrtc::H264::kLevel1:
230 case webrtc::H264::kLevel1_b:
231 case webrtc::H264::kLevel1_1:
232 case webrtc::H264::kLevel1_2:
233 case webrtc::H264::kLevel1_3:
234 case webrtc::H264::kLevel2:
235 case webrtc::H264::kLevel2_1:
236 case webrtc::H264::kLevel2_2:
237 return kVTProfileLevel_H264_Main_AutoLevel;
238 }
239
240 case webrtc::H264::kProfileConstrainedHigh:
241 case webrtc::H264::kProfileHigh:
242 switch (profile_level_id->level) {
243 case webrtc::H264::kLevel3:
244 return kVTProfileLevel_H264_High_3_0;
245 case webrtc::H264::kLevel3_1:
246 return kVTProfileLevel_H264_High_3_1;
247 case webrtc::H264::kLevel3_2:
248 return kVTProfileLevel_H264_High_3_2;
249 case webrtc::H264::kLevel4:
250 return kVTProfileLevel_H264_High_4_0;
251 case webrtc::H264::kLevel4_1:
252 return kVTProfileLevel_H264_High_4_1;
253 case webrtc::H264::kLevel4_2:
254 return kVTProfileLevel_H264_High_4_2;
255 case webrtc::H264::kLevel5:
256 return kVTProfileLevel_H264_High_5_0;
257 case webrtc::H264::kLevel5_1:
258 return kVTProfileLevel_H264_High_5_1;
259 case webrtc::H264::kLevel5_2:
260 return kVTProfileLevel_H264_High_5_2;
261 case webrtc::H264::kLevel1:
262 case webrtc::H264::kLevel1_b:
263 case webrtc::H264::kLevel1_1:
264 case webrtc::H264::kLevel1_2:
265 case webrtc::H264::kLevel1_3:
266 case webrtc::H264::kLevel2:
267 case webrtc::H264::kLevel2_1:
268 case webrtc::H264::kLevel2_2:
269 return kVTProfileLevel_H264_High_AutoLevel;
270 }
271 }
272}
Kári Tristan Helgason0bf60712017-09-25 10:26:42 +0200273} // namespace
magjed73c0eb52017-08-07 06:55:28 -0700274
275@implementation RTCVideoEncoderH264 {
276 RTCVideoCodecInfo *_codecInfo;
277 webrtc::BitrateAdjuster *_bitrateAdjuster;
278 uint32_t _targetBitrateBps;
279 uint32_t _encoderBitrateBps;
280 RTCH264PacketizationMode _packetizationMode;
281 CFStringRef _profile;
282 RTCVideoEncoderCallback _callback;
283 int32_t _width;
284 int32_t _height;
285 VTCompressionSessionRef _compressionSession;
286 RTCVideoCodecMode _mode;
287
288 webrtc::H264BitstreamParser _h264BitstreamParser;
289 std::vector<uint8_t> _nv12ScaleBuffer;
290}
291
292// .5 is set as a mininum to prevent overcompensating for large temporary
293// overshoots. We don't want to degrade video quality too badly.
294// .95 is set to prevent oscillations. When a lower bitrate is set on the
295// encoder than previously set, its output seems to have a brief period of
296// drastically reduced bitrate, so we want to avoid that. In steady state
297// conditions, 0.95 seems to give us better overall bitrate over long periods
298// of time.
299- (instancetype)initWithCodecInfo:(RTCVideoCodecInfo *)codecInfo {
300 if (self = [super init]) {
301 _codecInfo = codecInfo;
302 _bitrateAdjuster = new webrtc::BitrateAdjuster(webrtc::Clock::GetRealTimeClock(), .5, .95);
303 _packetizationMode = RTCH264PacketizationModeNonInterleaved;
304 _profile = ExtractProfile([codecInfo nativeVideoCodec]);
305 LOG(LS_INFO) << "Using profile " << CFStringToString(_profile);
306 RTC_CHECK([codecInfo.name isEqualToString:@"H264"]);
andersc9a85f072017-09-13 07:31:46 -0700307
308#if defined(WEBRTC_IOS)
309 [RTCUIApplicationStatusObserver prepareForUse];
310#endif
magjed73c0eb52017-08-07 06:55:28 -0700311 }
312 return self;
313}
314
315- (void)dealloc {
316 [self destroyCompressionSession];
317}
318
319- (NSInteger)startEncodeWithSettings:(RTCVideoEncoderSettings *)settings
320 numberOfCores:(int)numberOfCores {
321 RTC_DCHECK(settings);
322 RTC_DCHECK([settings.name isEqualToString:@"H264"]);
323
324 _width = settings.width;
325 _height = settings.height;
326 _mode = settings.mode;
327
328 // We can only set average bitrate on the HW encoder.
329 _targetBitrateBps = settings.startBitrate;
330 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
331
332 // TODO(tkchin): Try setting payload size via
333 // kVTCompressionPropertyKey_MaxH264SliceBytes.
334
335 return [self resetCompressionSession];
336}
337
338- (NSInteger)encode:(RTCVideoFrame *)frame
339 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
340 frameTypes:(NSArray<NSNumber *> *)frameTypes {
341 RTC_DCHECK_EQ(frame.width, _width);
342 RTC_DCHECK_EQ(frame.height, _height);
343 if (!_callback || !_compressionSession) {
344 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
345 }
346#if defined(WEBRTC_IOS)
347 if (![[RTCUIApplicationStatusObserver sharedInstance] isApplicationActive]) {
348 // Ignore all encode requests when app isn't active. In this state, the
349 // hardware encoder has been invalidated by the OS.
350 return WEBRTC_VIDEO_CODEC_OK;
351 }
352#endif
353 BOOL isKeyframeRequired = NO;
354
355 // Get a pixel buffer from the pool and copy frame data over.
356 CVPixelBufferPoolRef pixelBufferPool =
357 VTCompressionSessionGetPixelBufferPool(_compressionSession);
358
359#if defined(WEBRTC_IOS)
360 if (!pixelBufferPool) {
361 // Kind of a hack. On backgrounding, the compression session seems to get
362 // invalidated, which causes this pool call to fail when the application
363 // is foregrounded and frames are being sent for encoding again.
364 // Resetting the session when this happens fixes the issue.
365 // In addition we request a keyframe so video can recover quickly.
366 [self resetCompressionSession];
367 pixelBufferPool = VTCompressionSessionGetPixelBufferPool(_compressionSession);
368 isKeyframeRequired = YES;
369 LOG(LS_INFO) << "Resetting compression session due to invalid pool.";
370 }
371#endif
372
373 CVPixelBufferRef pixelBuffer = nullptr;
374 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
375 // Native frame buffer
376 RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer;
377 if (![rtcPixelBuffer requiresCropping]) {
378 // This pixel buffer might have a higher resolution than what the
379 // compression session is configured to. The compression session can
380 // handle that and will output encoded frames in the configured
381 // resolution regardless of the input pixel buffer resolution.
382 pixelBuffer = rtcPixelBuffer.pixelBuffer;
383 CVBufferRetain(pixelBuffer);
384 } else {
385 // Cropping required, we need to crop and scale to a new pixel buffer.
386 pixelBuffer = CreatePixelBuffer(pixelBufferPool);
387 if (!pixelBuffer) {
388 return WEBRTC_VIDEO_CODEC_ERROR;
389 }
390 int dstWidth = CVPixelBufferGetWidth(pixelBuffer);
391 int dstHeight = CVPixelBufferGetHeight(pixelBuffer);
392 if ([rtcPixelBuffer requiresScalingToWidth:dstWidth height:dstHeight]) {
393 int size =
394 [rtcPixelBuffer bufferSizeForCroppingAndScalingToWidth:dstWidth height:dstHeight];
395 _nv12ScaleBuffer.resize(size);
396 } else {
397 _nv12ScaleBuffer.clear();
398 }
399 _nv12ScaleBuffer.shrink_to_fit();
400 if (![rtcPixelBuffer cropAndScaleTo:pixelBuffer withTempBuffer:_nv12ScaleBuffer.data()]) {
401 return WEBRTC_VIDEO_CODEC_ERROR;
402 }
403 }
404 }
405
406 if (!pixelBuffer) {
407 // We did not have a native frame buffer
408 pixelBuffer = CreatePixelBuffer(pixelBufferPool);
409 if (!pixelBuffer) {
410 return WEBRTC_VIDEO_CODEC_ERROR;
411 }
412 RTC_DCHECK(pixelBuffer);
413 if (!CopyVideoFrameToPixelBuffer([frame.buffer toI420], pixelBuffer)) {
414 LOG(LS_ERROR) << "Failed to copy frame data.";
415 CVBufferRelease(pixelBuffer);
416 return WEBRTC_VIDEO_CODEC_ERROR;
417 }
418 }
419
420 // Check if we need a keyframe.
421 if (!isKeyframeRequired && frameTypes) {
422 for (NSNumber *frameType in frameTypes) {
423 if ((RTCFrameType)frameType.intValue == RTCFrameTypeVideoFrameKey) {
424 isKeyframeRequired = YES;
425 break;
426 }
427 }
428 }
429
430 CMTime presentationTimeStamp = CMTimeMake(frame.timeStampNs / rtc::kNumNanosecsPerMillisec, 1000);
431 CFDictionaryRef frameProperties = nullptr;
432 if (isKeyframeRequired) {
433 CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame};
434 CFTypeRef values[] = {kCFBooleanTrue};
435 frameProperties = CreateCFTypeDictionary(keys, values, 1);
436 }
437
438 std::unique_ptr<RTCFrameEncodeParams> encodeParams;
439 encodeParams.reset(new RTCFrameEncodeParams(self,
440 codecSpecificInfo,
441 _width,
442 _height,
443 frame.timeStampNs / rtc::kNumNanosecsPerMillisec,
444 frame.timeStamp,
445 frame.rotation));
446 encodeParams->codecSpecificInfo.packetizationMode = _packetizationMode;
447
448 // Update the bitrate if needed.
449 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()];
450
451 OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession,
452 pixelBuffer,
453 presentationTimeStamp,
454 kCMTimeInvalid,
455 frameProperties,
456 encodeParams.release(),
457 nullptr);
458 if (frameProperties) {
459 CFRelease(frameProperties);
460 }
461 if (pixelBuffer) {
462 CVBufferRelease(pixelBuffer);
463 }
464 if (status != noErr) {
465 LOG(LS_ERROR) << "Failed to encode frame with code: " << status;
466 return WEBRTC_VIDEO_CODEC_ERROR;
467 }
468 return WEBRTC_VIDEO_CODEC_OK;
469}
470
471- (void)setCallback:(RTCVideoEncoderCallback)callback {
472 _callback = callback;
473}
474
475- (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate {
476 _targetBitrateBps = 1000 * bitrateKbit;
477 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
478 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()];
479 return WEBRTC_VIDEO_CODEC_OK;
480}
481
482#pragma mark - Private
483
484- (NSInteger)releaseEncoder {
485 // Need to destroy so that the session is invalidated and won't use the
486 // callback anymore. Do not remove callback until the session is invalidated
487 // since async encoder callbacks can occur until invalidation.
488 [self destroyCompressionSession];
489 _callback = nullptr;
490 return WEBRTC_VIDEO_CODEC_OK;
491}
492
493- (int)resetCompressionSession {
494 [self destroyCompressionSession];
495
496 // Set source image buffer attributes. These attributes will be present on
497 // buffers retrieved from the encoder's pixel buffer pool.
498 const size_t attributesSize = 3;
499 CFTypeRef keys[attributesSize] = {
500#if defined(WEBRTC_IOS)
501 kCVPixelBufferOpenGLESCompatibilityKey,
502#elif defined(WEBRTC_MAC)
503 kCVPixelBufferOpenGLCompatibilityKey,
504#endif
505 kCVPixelBufferIOSurfacePropertiesKey,
506 kCVPixelBufferPixelFormatTypeKey
507 };
508 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
509 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
510 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type);
511 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
512 CFDictionaryRef sourceAttributes = CreateCFTypeDictionary(keys, values, attributesSize);
513 if (ioSurfaceValue) {
514 CFRelease(ioSurfaceValue);
515 ioSurfaceValue = nullptr;
516 }
517 if (pixelFormat) {
518 CFRelease(pixelFormat);
519 pixelFormat = nullptr;
520 }
kthelgasona4955b42017-08-24 04:22:58 -0700521 CFMutableDictionaryRef encoder_specs = nullptr;
522#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
523 // Currently hw accl is supported above 360p on mac, below 360p
524 // the compression session will be created with hw accl disabled.
525 encoder_specs = CFDictionaryCreateMutable(
526 nullptr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
527 CFDictionarySetValue(encoder_specs,
528 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
529 kCFBooleanTrue);
530#endif
531 OSStatus status =
532 VTCompressionSessionCreate(nullptr, // use default allocator
533 _width,
534 _height,
535 kCMVideoCodecType_H264,
536 encoder_specs, // use hardware accelerated encoder if available
537 sourceAttributes,
538 nullptr, // use default compressed data allocator
539 compressionOutputCallback,
540 nullptr,
541 &_compressionSession);
magjed73c0eb52017-08-07 06:55:28 -0700542 if (sourceAttributes) {
543 CFRelease(sourceAttributes);
544 sourceAttributes = nullptr;
545 }
kthelgasona4955b42017-08-24 04:22:58 -0700546 if (encoder_specs) {
547 CFRelease(encoder_specs);
548 encoder_specs = nullptr;
549 }
magjed73c0eb52017-08-07 06:55:28 -0700550 if (status != noErr) {
551 LOG(LS_ERROR) << "Failed to create compression session: " << status;
552 return WEBRTC_VIDEO_CODEC_ERROR;
553 }
kthelgasona4955b42017-08-24 04:22:58 -0700554#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
555 CFBooleanRef hwaccl_enabled = nullptr;
556 status = VTSessionCopyProperty(_compressionSession,
557 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
558 nullptr,
559 &hwaccl_enabled);
560 if (status == noErr && (CFBooleanGetValue(hwaccl_enabled))) {
561 LOG(LS_INFO) << "Compression session created with hw accl enabled";
562 } else {
563 LOG(LS_INFO) << "Compression session created with hw accl disabled";
564 }
565#endif
magjed73c0eb52017-08-07 06:55:28 -0700566 [self configureCompressionSession];
567 return WEBRTC_VIDEO_CODEC_OK;
568}
569
570- (void)configureCompressionSession {
571 RTC_DCHECK(_compressionSession);
572 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, true);
573 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, _profile);
574 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, false);
575 [self setEncoderBitrateBps:_targetBitrateBps];
576 // TODO(tkchin): Look at entropy mode and colorspace matrices.
577 // TODO(tkchin): Investigate to see if there's any way to make this work.
578 // May need it to interop with Android. Currently this call just fails.
579 // On inspecting encoder output on iOS8, this value is set to 6.
580 // internal::SetVTSessionProperty(compression_session_,
581 // kVTCompressionPropertyKey_MaxFrameDelayCount,
582 // 1);
583
584 // Set a relatively large value for keyframe emission (7200 frames or 4 minutes).
585 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, 7200);
586 SetVTSessionProperty(
587 _compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 240);
588}
589
590- (void)destroyCompressionSession {
591 if (_compressionSession) {
592 VTCompressionSessionInvalidate(_compressionSession);
593 CFRelease(_compressionSession);
594 _compressionSession = nullptr;
595 }
596}
597
598- (NSString *)implementationName {
599 return @"VideoToolbox";
600}
601
602- (void)setBitrateBps:(uint32_t)bitrateBps {
603 if (_encoderBitrateBps != bitrateBps) {
604 [self setEncoderBitrateBps:bitrateBps];
605 }
606}
607
608- (void)setEncoderBitrateBps:(uint32_t)bitrateBps {
609 if (_compressionSession) {
610 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitrateBps);
611
612 // TODO(tkchin): Add a helper method to set array value.
613 int64_t dataLimitBytesPerSecondValue =
614 static_cast<int64_t>(bitrateBps * kLimitToAverageBitRateFactor / 8);
615 CFNumberRef bytesPerSecond =
616 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytesPerSecondValue);
617 int64_t oneSecondValue = 1;
618 CFNumberRef oneSecond =
619 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue);
620 const void *nums[2] = {bytesPerSecond, oneSecond};
621 CFArrayRef dataRateLimits = CFArrayCreate(nullptr, nums, 2, &kCFTypeArrayCallBacks);
622 OSStatus status = VTSessionSetProperty(
623 _compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits);
624 if (bytesPerSecond) {
625 CFRelease(bytesPerSecond);
626 }
627 if (oneSecond) {
628 CFRelease(oneSecond);
629 }
630 if (dataRateLimits) {
631 CFRelease(dataRateLimits);
632 }
633 if (status != noErr) {
634 LOG(LS_ERROR) << "Failed to set data rate limit";
635 }
636
637 _encoderBitrateBps = bitrateBps;
638 }
639}
640
641- (void)frameWasEncoded:(OSStatus)status
642 flags:(VTEncodeInfoFlags)infoFlags
643 sampleBuffer:(CMSampleBufferRef)sampleBuffer
644 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
645 width:(int32_t)width
646 height:(int32_t)height
647 renderTimeMs:(int64_t)renderTimeMs
648 timestamp:(uint32_t)timestamp
649 rotation:(RTCVideoRotation)rotation {
650 if (status != noErr) {
651 LOG(LS_ERROR) << "H264 encode failed.";
652 return;
653 }
654 if (infoFlags & kVTEncodeInfo_FrameDropped) {
655 LOG(LS_INFO) << "H264 encode dropped frame.";
656 return;
657 }
658
659 BOOL isKeyframe = NO;
660 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0);
661 if (attachments != nullptr && CFArrayGetCount(attachments)) {
662 CFDictionaryRef attachment =
663 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0));
664 isKeyframe = !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync);
665 }
666
667 if (isKeyframe) {
668 LOG(LS_INFO) << "Generated keyframe";
669 }
670
671 // Convert the sample buffer into a buffer suitable for RTP packetization.
672 // TODO(tkchin): Allocate buffers through a pool.
673 std::unique_ptr<rtc::Buffer> buffer(new rtc::Buffer());
674 RTCRtpFragmentationHeader *header;
675 {
kthelgasonf8084d42017-08-30 04:47:10 -0700676 std::unique_ptr<webrtc::RTPFragmentationHeader> header_cpp;
magjed73c0eb52017-08-07 06:55:28 -0700677 bool result =
678 H264CMSampleBufferToAnnexBBuffer(sampleBuffer, isKeyframe, buffer.get(), &header_cpp);
kthelgasonf8084d42017-08-30 04:47:10 -0700679 header = [[RTCRtpFragmentationHeader alloc] initWithNativeFragmentationHeader:header_cpp.get()];
magjed73c0eb52017-08-07 06:55:28 -0700680 if (!result) {
681 return;
682 }
683 }
684
685 RTCEncodedImage *frame = [[RTCEncodedImage alloc] init];
686 frame.buffer = [NSData dataWithBytesNoCopy:buffer->data() length:buffer->size() freeWhenDone:NO];
687 frame.encodedWidth = width;
688 frame.encodedHeight = height;
689 frame.completeFrame = YES;
690 frame.frameType = isKeyframe ? RTCFrameTypeVideoFrameKey : RTCFrameTypeVideoFrameDelta;
691 frame.captureTimeMs = renderTimeMs;
692 frame.timeStamp = timestamp;
693 frame.rotation = rotation;
694 frame.contentType = (_mode == RTCVideoCodecModeScreensharing) ? RTCVideoContentTypeScreenshare :
695 RTCVideoContentTypeUnspecified;
sprangba050a62017-08-18 02:51:12 -0700696 frame.flags = webrtc::TimingFrameFlags::kInvalid;
magjed73c0eb52017-08-07 06:55:28 -0700697
698 int qp;
699 _h264BitstreamParser.ParseBitstream(buffer->data(), buffer->size());
700 _h264BitstreamParser.GetLastSliceQp(&qp);
701 frame.qp = @(qp);
702
703 BOOL res = _callback(frame, codecSpecificInfo, header);
704 if (!res) {
705 LOG(LS_ERROR) << "Encode callback failed";
706 return;
707 }
708 _bitrateAdjuster->Update(frame.buffer.length);
709}
710
711- (RTCVideoEncoderQpThresholds *)scalingSettings {
712 return [[RTCVideoEncoderQpThresholds alloc] initWithThresholdsLow:kLowH264QpThreshold
713 high:kHighH264QpThreshold];
714}
715
716@end