blob: 7a6629ed36d0bfb259f752f005020d271db2f885 [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.
Anders Carlsson7e042812017-10-05 16:55:38 +0200172CFStringRef ExtractProfile(webrtc::SdpVideoFormat videoFormat) {
magjed73c0eb52017-08-07 06:55:28 -0700173 const rtc::Optional<webrtc::H264::ProfileLevelId> profile_level_id =
Anders Carlsson7e042812017-10-05 16:55:38 +0200174 webrtc::H264::ParseSdpProfileLevelId(videoFormat.parameters);
magjed73c0eb52017-08-07 06:55:28 -0700175 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;
Danielaf3282822017-09-29 14:14:54 +0200277 std::unique_ptr<webrtc::BitrateAdjuster> _bitrateAdjuster;
magjed73c0eb52017-08-07 06:55:28 -0700278 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;
Danielaf3282822017-09-29 14:14:54 +0200302 _bitrateAdjuster.reset(new webrtc::BitrateAdjuster(
303 webrtc::Clock::GetRealTimeClock(), .5, .95));
magjed73c0eb52017-08-07 06:55:28 -0700304 _packetizationMode = RTCH264PacketizationModeNonInterleaved;
Anders Carlsson7e042812017-10-05 16:55:38 +0200305 _profile = ExtractProfile([codecInfo nativeSdpVideoFormat]);
magjed73c0eb52017-08-07 06:55:28 -0700306 LOG(LS_INFO) << "Using profile " << CFStringToString(_profile);
307 RTC_CHECK([codecInfo.name isEqualToString:@"H264"]);
andersc9a85f072017-09-13 07:31:46 -0700308
309#if defined(WEBRTC_IOS)
310 [RTCUIApplicationStatusObserver prepareForUse];
311#endif
magjed73c0eb52017-08-07 06:55:28 -0700312 }
313 return self;
314}
315
316- (void)dealloc {
317 [self destroyCompressionSession];
318}
319
320- (NSInteger)startEncodeWithSettings:(RTCVideoEncoderSettings *)settings
321 numberOfCores:(int)numberOfCores {
322 RTC_DCHECK(settings);
323 RTC_DCHECK([settings.name isEqualToString:@"H264"]);
324
325 _width = settings.width;
326 _height = settings.height;
327 _mode = settings.mode;
328
329 // We can only set average bitrate on the HW encoder.
330 _targetBitrateBps = settings.startBitrate;
331 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
332
333 // TODO(tkchin): Try setting payload size via
334 // kVTCompressionPropertyKey_MaxH264SliceBytes.
335
336 return [self resetCompressionSession];
337}
338
339- (NSInteger)encode:(RTCVideoFrame *)frame
340 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
341 frameTypes:(NSArray<NSNumber *> *)frameTypes {
342 RTC_DCHECK_EQ(frame.width, _width);
343 RTC_DCHECK_EQ(frame.height, _height);
344 if (!_callback || !_compressionSession) {
345 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
346 }
347#if defined(WEBRTC_IOS)
348 if (![[RTCUIApplicationStatusObserver sharedInstance] isApplicationActive]) {
349 // Ignore all encode requests when app isn't active. In this state, the
350 // hardware encoder has been invalidated by the OS.
351 return WEBRTC_VIDEO_CODEC_OK;
352 }
353#endif
354 BOOL isKeyframeRequired = NO;
355
356 // Get a pixel buffer from the pool and copy frame data over.
357 CVPixelBufferPoolRef pixelBufferPool =
358 VTCompressionSessionGetPixelBufferPool(_compressionSession);
359
360#if defined(WEBRTC_IOS)
361 if (!pixelBufferPool) {
362 // Kind of a hack. On backgrounding, the compression session seems to get
363 // invalidated, which causes this pool call to fail when the application
364 // is foregrounded and frames are being sent for encoding again.
365 // Resetting the session when this happens fixes the issue.
366 // In addition we request a keyframe so video can recover quickly.
367 [self resetCompressionSession];
368 pixelBufferPool = VTCompressionSessionGetPixelBufferPool(_compressionSession);
369 isKeyframeRequired = YES;
370 LOG(LS_INFO) << "Resetting compression session due to invalid pool.";
371 }
372#endif
373
374 CVPixelBufferRef pixelBuffer = nullptr;
375 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
376 // Native frame buffer
377 RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer;
378 if (![rtcPixelBuffer requiresCropping]) {
379 // This pixel buffer might have a higher resolution than what the
380 // compression session is configured to. The compression session can
381 // handle that and will output encoded frames in the configured
382 // resolution regardless of the input pixel buffer resolution.
383 pixelBuffer = rtcPixelBuffer.pixelBuffer;
384 CVBufferRetain(pixelBuffer);
385 } else {
386 // Cropping required, we need to crop and scale to a new pixel buffer.
387 pixelBuffer = CreatePixelBuffer(pixelBufferPool);
388 if (!pixelBuffer) {
389 return WEBRTC_VIDEO_CODEC_ERROR;
390 }
391 int dstWidth = CVPixelBufferGetWidth(pixelBuffer);
392 int dstHeight = CVPixelBufferGetHeight(pixelBuffer);
393 if ([rtcPixelBuffer requiresScalingToWidth:dstWidth height:dstHeight]) {
394 int size =
395 [rtcPixelBuffer bufferSizeForCroppingAndScalingToWidth:dstWidth height:dstHeight];
396 _nv12ScaleBuffer.resize(size);
397 } else {
398 _nv12ScaleBuffer.clear();
399 }
400 _nv12ScaleBuffer.shrink_to_fit();
401 if (![rtcPixelBuffer cropAndScaleTo:pixelBuffer withTempBuffer:_nv12ScaleBuffer.data()]) {
402 return WEBRTC_VIDEO_CODEC_ERROR;
403 }
404 }
405 }
406
407 if (!pixelBuffer) {
408 // We did not have a native frame buffer
409 pixelBuffer = CreatePixelBuffer(pixelBufferPool);
410 if (!pixelBuffer) {
411 return WEBRTC_VIDEO_CODEC_ERROR;
412 }
413 RTC_DCHECK(pixelBuffer);
414 if (!CopyVideoFrameToPixelBuffer([frame.buffer toI420], pixelBuffer)) {
415 LOG(LS_ERROR) << "Failed to copy frame data.";
416 CVBufferRelease(pixelBuffer);
417 return WEBRTC_VIDEO_CODEC_ERROR;
418 }
419 }
420
421 // Check if we need a keyframe.
422 if (!isKeyframeRequired && frameTypes) {
423 for (NSNumber *frameType in frameTypes) {
424 if ((RTCFrameType)frameType.intValue == RTCFrameTypeVideoFrameKey) {
425 isKeyframeRequired = YES;
426 break;
427 }
428 }
429 }
430
431 CMTime presentationTimeStamp = CMTimeMake(frame.timeStampNs / rtc::kNumNanosecsPerMillisec, 1000);
432 CFDictionaryRef frameProperties = nullptr;
433 if (isKeyframeRequired) {
434 CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame};
435 CFTypeRef values[] = {kCFBooleanTrue};
436 frameProperties = CreateCFTypeDictionary(keys, values, 1);
437 }
438
439 std::unique_ptr<RTCFrameEncodeParams> encodeParams;
440 encodeParams.reset(new RTCFrameEncodeParams(self,
441 codecSpecificInfo,
442 _width,
443 _height,
444 frame.timeStampNs / rtc::kNumNanosecsPerMillisec,
445 frame.timeStamp,
446 frame.rotation));
447 encodeParams->codecSpecificInfo.packetizationMode = _packetizationMode;
448
449 // Update the bitrate if needed.
450 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()];
451
452 OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession,
453 pixelBuffer,
454 presentationTimeStamp,
455 kCMTimeInvalid,
456 frameProperties,
457 encodeParams.release(),
458 nullptr);
459 if (frameProperties) {
460 CFRelease(frameProperties);
461 }
462 if (pixelBuffer) {
463 CVBufferRelease(pixelBuffer);
464 }
465 if (status != noErr) {
466 LOG(LS_ERROR) << "Failed to encode frame with code: " << status;
467 return WEBRTC_VIDEO_CODEC_ERROR;
468 }
469 return WEBRTC_VIDEO_CODEC_OK;
470}
471
472- (void)setCallback:(RTCVideoEncoderCallback)callback {
473 _callback = callback;
474}
475
476- (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate {
477 _targetBitrateBps = 1000 * bitrateKbit;
478 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
479 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()];
480 return WEBRTC_VIDEO_CODEC_OK;
481}
482
483#pragma mark - Private
484
485- (NSInteger)releaseEncoder {
486 // Need to destroy so that the session is invalidated and won't use the
487 // callback anymore. Do not remove callback until the session is invalidated
488 // since async encoder callbacks can occur until invalidation.
489 [self destroyCompressionSession];
490 _callback = nullptr;
491 return WEBRTC_VIDEO_CODEC_OK;
492}
493
494- (int)resetCompressionSession {
495 [self destroyCompressionSession];
496
497 // Set source image buffer attributes. These attributes will be present on
498 // buffers retrieved from the encoder's pixel buffer pool.
499 const size_t attributesSize = 3;
500 CFTypeRef keys[attributesSize] = {
501#if defined(WEBRTC_IOS)
502 kCVPixelBufferOpenGLESCompatibilityKey,
503#elif defined(WEBRTC_MAC)
504 kCVPixelBufferOpenGLCompatibilityKey,
505#endif
506 kCVPixelBufferIOSurfacePropertiesKey,
507 kCVPixelBufferPixelFormatTypeKey
508 };
509 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
510 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
511 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type);
512 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
513 CFDictionaryRef sourceAttributes = CreateCFTypeDictionary(keys, values, attributesSize);
514 if (ioSurfaceValue) {
515 CFRelease(ioSurfaceValue);
516 ioSurfaceValue = nullptr;
517 }
518 if (pixelFormat) {
519 CFRelease(pixelFormat);
520 pixelFormat = nullptr;
521 }
kthelgasona4955b42017-08-24 04:22:58 -0700522 CFMutableDictionaryRef encoder_specs = nullptr;
523#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
524 // Currently hw accl is supported above 360p on mac, below 360p
525 // the compression session will be created with hw accl disabled.
526 encoder_specs = CFDictionaryCreateMutable(
527 nullptr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
528 CFDictionarySetValue(encoder_specs,
529 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
530 kCFBooleanTrue);
531#endif
532 OSStatus status =
533 VTCompressionSessionCreate(nullptr, // use default allocator
534 _width,
535 _height,
536 kCMVideoCodecType_H264,
537 encoder_specs, // use hardware accelerated encoder if available
538 sourceAttributes,
539 nullptr, // use default compressed data allocator
540 compressionOutputCallback,
541 nullptr,
542 &_compressionSession);
magjed73c0eb52017-08-07 06:55:28 -0700543 if (sourceAttributes) {
544 CFRelease(sourceAttributes);
545 sourceAttributes = nullptr;
546 }
kthelgasona4955b42017-08-24 04:22:58 -0700547 if (encoder_specs) {
548 CFRelease(encoder_specs);
549 encoder_specs = nullptr;
550 }
magjed73c0eb52017-08-07 06:55:28 -0700551 if (status != noErr) {
552 LOG(LS_ERROR) << "Failed to create compression session: " << status;
553 return WEBRTC_VIDEO_CODEC_ERROR;
554 }
kthelgasona4955b42017-08-24 04:22:58 -0700555#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
556 CFBooleanRef hwaccl_enabled = nullptr;
557 status = VTSessionCopyProperty(_compressionSession,
558 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
559 nullptr,
560 &hwaccl_enabled);
561 if (status == noErr && (CFBooleanGetValue(hwaccl_enabled))) {
562 LOG(LS_INFO) << "Compression session created with hw accl enabled";
563 } else {
564 LOG(LS_INFO) << "Compression session created with hw accl disabled";
565 }
566#endif
magjed73c0eb52017-08-07 06:55:28 -0700567 [self configureCompressionSession];
568 return WEBRTC_VIDEO_CODEC_OK;
569}
570
571- (void)configureCompressionSession {
572 RTC_DCHECK(_compressionSession);
573 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, true);
574 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, _profile);
575 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, false);
576 [self setEncoderBitrateBps:_targetBitrateBps];
577 // TODO(tkchin): Look at entropy mode and colorspace matrices.
578 // TODO(tkchin): Investigate to see if there's any way to make this work.
579 // May need it to interop with Android. Currently this call just fails.
580 // On inspecting encoder output on iOS8, this value is set to 6.
581 // internal::SetVTSessionProperty(compression_session_,
582 // kVTCompressionPropertyKey_MaxFrameDelayCount,
583 // 1);
584
585 // Set a relatively large value for keyframe emission (7200 frames or 4 minutes).
586 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, 7200);
587 SetVTSessionProperty(
588 _compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 240);
589}
590
591- (void)destroyCompressionSession {
592 if (_compressionSession) {
593 VTCompressionSessionInvalidate(_compressionSession);
594 CFRelease(_compressionSession);
595 _compressionSession = nullptr;
596 }
597}
598
599- (NSString *)implementationName {
600 return @"VideoToolbox";
601}
602
603- (void)setBitrateBps:(uint32_t)bitrateBps {
604 if (_encoderBitrateBps != bitrateBps) {
605 [self setEncoderBitrateBps:bitrateBps];
606 }
607}
608
609- (void)setEncoderBitrateBps:(uint32_t)bitrateBps {
610 if (_compressionSession) {
611 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitrateBps);
612
613 // TODO(tkchin): Add a helper method to set array value.
614 int64_t dataLimitBytesPerSecondValue =
615 static_cast<int64_t>(bitrateBps * kLimitToAverageBitRateFactor / 8);
616 CFNumberRef bytesPerSecond =
617 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytesPerSecondValue);
618 int64_t oneSecondValue = 1;
619 CFNumberRef oneSecond =
620 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue);
621 const void *nums[2] = {bytesPerSecond, oneSecond};
622 CFArrayRef dataRateLimits = CFArrayCreate(nullptr, nums, 2, &kCFTypeArrayCallBacks);
623 OSStatus status = VTSessionSetProperty(
624 _compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits);
625 if (bytesPerSecond) {
626 CFRelease(bytesPerSecond);
627 }
628 if (oneSecond) {
629 CFRelease(oneSecond);
630 }
631 if (dataRateLimits) {
632 CFRelease(dataRateLimits);
633 }
634 if (status != noErr) {
635 LOG(LS_ERROR) << "Failed to set data rate limit";
636 }
637
638 _encoderBitrateBps = bitrateBps;
639 }
640}
641
642- (void)frameWasEncoded:(OSStatus)status
643 flags:(VTEncodeInfoFlags)infoFlags
644 sampleBuffer:(CMSampleBufferRef)sampleBuffer
645 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
646 width:(int32_t)width
647 height:(int32_t)height
648 renderTimeMs:(int64_t)renderTimeMs
649 timestamp:(uint32_t)timestamp
650 rotation:(RTCVideoRotation)rotation {
651 if (status != noErr) {
652 LOG(LS_ERROR) << "H264 encode failed.";
653 return;
654 }
655 if (infoFlags & kVTEncodeInfo_FrameDropped) {
656 LOG(LS_INFO) << "H264 encode dropped frame.";
657 return;
658 }
659
660 BOOL isKeyframe = NO;
661 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0);
662 if (attachments != nullptr && CFArrayGetCount(attachments)) {
663 CFDictionaryRef attachment =
664 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0));
665 isKeyframe = !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync);
666 }
667
668 if (isKeyframe) {
669 LOG(LS_INFO) << "Generated keyframe";
670 }
671
672 // Convert the sample buffer into a buffer suitable for RTP packetization.
673 // TODO(tkchin): Allocate buffers through a pool.
674 std::unique_ptr<rtc::Buffer> buffer(new rtc::Buffer());
675 RTCRtpFragmentationHeader *header;
676 {
kthelgasonf8084d42017-08-30 04:47:10 -0700677 std::unique_ptr<webrtc::RTPFragmentationHeader> header_cpp;
magjed73c0eb52017-08-07 06:55:28 -0700678 bool result =
679 H264CMSampleBufferToAnnexBBuffer(sampleBuffer, isKeyframe, buffer.get(), &header_cpp);
kthelgasonf8084d42017-08-30 04:47:10 -0700680 header = [[RTCRtpFragmentationHeader alloc] initWithNativeFragmentationHeader:header_cpp.get()];
magjed73c0eb52017-08-07 06:55:28 -0700681 if (!result) {
682 return;
683 }
684 }
685
686 RTCEncodedImage *frame = [[RTCEncodedImage alloc] init];
687 frame.buffer = [NSData dataWithBytesNoCopy:buffer->data() length:buffer->size() freeWhenDone:NO];
688 frame.encodedWidth = width;
689 frame.encodedHeight = height;
690 frame.completeFrame = YES;
691 frame.frameType = isKeyframe ? RTCFrameTypeVideoFrameKey : RTCFrameTypeVideoFrameDelta;
692 frame.captureTimeMs = renderTimeMs;
693 frame.timeStamp = timestamp;
694 frame.rotation = rotation;
695 frame.contentType = (_mode == RTCVideoCodecModeScreensharing) ? RTCVideoContentTypeScreenshare :
696 RTCVideoContentTypeUnspecified;
sprangba050a62017-08-18 02:51:12 -0700697 frame.flags = webrtc::TimingFrameFlags::kInvalid;
magjed73c0eb52017-08-07 06:55:28 -0700698
699 int qp;
700 _h264BitstreamParser.ParseBitstream(buffer->data(), buffer->size());
701 _h264BitstreamParser.GetLastSliceQp(&qp);
702 frame.qp = @(qp);
703
704 BOOL res = _callback(frame, codecSpecificInfo, header);
705 if (!res) {
706 LOG(LS_ERROR) << "Encode callback failed";
707 return;
708 }
709 _bitrateAdjuster->Update(frame.buffer.length);
710}
711
712- (RTCVideoEncoderQpThresholds *)scalingSettings {
713 return [[RTCVideoEncoderQpThresholds alloc] initWithThresholdsLow:kLowH264QpThreshold
714 high:kHighH264QpThreshold];
715}
716
717@end