blob: a0b6d78179838de6d41cc33fd1131ef45db1beff [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"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020025#include "common_video/h264/h264_bitstream_parser.h"
26#include "common_video/h264/profile_level_id.h"
27#include "common_video/include/bitrate_adjuster.h"
Mirko Bonadei65432062017-12-11 09:32:13 +010028#import "helpers.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020029#include "modules/include/module_common_types.h"
30#include "modules/video_coding/include/video_error_codes.h"
31#include "rtc_base/buffer.h"
32#include "rtc_base/logging.h"
33#include "rtc_base/timeutils.h"
34#include "sdk/objc/Framework/Classes/VideoToolbox/nalu_rewriter.h"
35#include "system_wrappers/include/clock.h"
Mirko Bonadei65432062017-12-11 09:32:13 +010036#include "third_party/libyuv/include/libyuv/convert_from.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
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020063const OSType kNV12PixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
64
magjed73c0eb52017-08-07 06:55:28 -070065// Struct that we pass to the encoder per frame to encode. We receive it again
66// in the encoder callback.
67struct RTCFrameEncodeParams {
68 RTCFrameEncodeParams(RTCVideoEncoderH264 *e,
69 RTCCodecSpecificInfoH264 *csi,
70 int32_t w,
71 int32_t h,
72 int64_t rtms,
73 uint32_t ts,
74 RTCVideoRotation r)
75 : encoder(e), width(w), height(h), render_time_ms(rtms), timestamp(ts), rotation(r) {
76 if (csi) {
77 codecSpecificInfo = csi;
78 } else {
79 codecSpecificInfo = [[RTCCodecSpecificInfoH264 alloc] init];
80 }
81 }
82
83 RTCVideoEncoderH264 *encoder;
84 RTCCodecSpecificInfoH264 *codecSpecificInfo;
85 int32_t width;
86 int32_t height;
87 int64_t render_time_ms;
88 uint32_t timestamp;
89 RTCVideoRotation rotation;
90};
91
92// We receive I420Frames as input, but we need to feed CVPixelBuffers into the
93// encoder. This performs the copy and format conversion.
94// TODO(tkchin): See if encoder will accept i420 frames and compare performance.
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020095bool CopyVideoFrameToNV12PixelBuffer(id<RTCI420Buffer> frameBuffer, CVPixelBufferRef pixelBuffer) {
magjed73c0eb52017-08-07 06:55:28 -070096 RTC_DCHECK(pixelBuffer);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020097 RTC_DCHECK_EQ(CVPixelBufferGetPixelFormatType(pixelBuffer), kNV12PixelFormat);
magjed73c0eb52017-08-07 06:55:28 -070098 RTC_DCHECK_EQ(CVPixelBufferGetHeightOfPlane(pixelBuffer, 0), frameBuffer.height);
99 RTC_DCHECK_EQ(CVPixelBufferGetWidthOfPlane(pixelBuffer, 0), frameBuffer.width);
100
101 CVReturn cvRet = CVPixelBufferLockBaseAddress(pixelBuffer, 0);
102 if (cvRet != kCVReturnSuccess) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100103 RTC_LOG(LS_ERROR) << "Failed to lock base address: " << cvRet;
magjed73c0eb52017-08-07 06:55:28 -0700104 return false;
105 }
106 uint8_t *dstY = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0));
107 int dstStrideY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
108 uint8_t *dstUV = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1));
109 int dstStrideUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
110 // Convert I420 to NV12.
111 int ret = libyuv::I420ToNV12(frameBuffer.dataY,
112 frameBuffer.strideY,
113 frameBuffer.dataU,
114 frameBuffer.strideU,
115 frameBuffer.dataV,
116 frameBuffer.strideV,
117 dstY,
118 dstStrideY,
119 dstUV,
120 dstStrideUV,
121 frameBuffer.width,
122 frameBuffer.height);
123 CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
124 if (ret) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100125 RTC_LOG(LS_ERROR) << "Error converting I420 VideoFrame to NV12 :" << ret;
magjed73c0eb52017-08-07 06:55:28 -0700126 return false;
127 }
128 return true;
129}
130
131CVPixelBufferRef CreatePixelBuffer(CVPixelBufferPoolRef pixel_buffer_pool) {
132 if (!pixel_buffer_pool) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100133 RTC_LOG(LS_ERROR) << "Failed to get pixel buffer pool.";
magjed73c0eb52017-08-07 06:55:28 -0700134 return nullptr;
135 }
136 CVPixelBufferRef pixel_buffer;
137 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(nullptr, pixel_buffer_pool, &pixel_buffer);
138 if (ret != kCVReturnSuccess) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100139 RTC_LOG(LS_ERROR) << "Failed to create pixel buffer: " << ret;
magjed73c0eb52017-08-07 06:55:28 -0700140 // We probably want to drop frames here, since failure probably means
141 // that the pool is empty.
142 return nullptr;
143 }
144 return pixel_buffer;
145}
146
147// This is the callback function that VideoToolbox calls when encode is
148// complete. From inspection this happens on its own queue.
149void compressionOutputCallback(void *encoder,
150 void *params,
151 OSStatus status,
152 VTEncodeInfoFlags infoFlags,
153 CMSampleBufferRef sampleBuffer) {
Anders Carlssoned2b1c92017-11-02 13:15:15 +0100154 if (!params) {
155 // If there are pending callbacks when the encoder is destroyed, this can happen.
156 return;
157 }
magjed73c0eb52017-08-07 06:55:28 -0700158 std::unique_ptr<RTCFrameEncodeParams> encodeParams(
159 reinterpret_cast<RTCFrameEncodeParams *>(params));
160 [encodeParams->encoder frameWasEncoded:status
161 flags:infoFlags
162 sampleBuffer:sampleBuffer
163 codecSpecificInfo:encodeParams->codecSpecificInfo
164 width:encodeParams->width
165 height:encodeParams->height
166 renderTimeMs:encodeParams->render_time_ms
167 timestamp:encodeParams->timestamp
168 rotation:encodeParams->rotation];
169}
170
171// Extract VideoToolbox profile out of the cricket::VideoCodec. If there is no
172// specific VideoToolbox profile for the specified level, AutoLevel will be
173// returned. The user must initialize the encoder with a resolution and
174// framerate conforming to the selected H264 level regardless.
Anders Carlsson7e042812017-10-05 16:55:38 +0200175CFStringRef ExtractProfile(webrtc::SdpVideoFormat videoFormat) {
magjed73c0eb52017-08-07 06:55:28 -0700176 const rtc::Optional<webrtc::H264::ProfileLevelId> profile_level_id =
Anders Carlsson7e042812017-10-05 16:55:38 +0200177 webrtc::H264::ParseSdpProfileLevelId(videoFormat.parameters);
magjed73c0eb52017-08-07 06:55:28 -0700178 RTC_DCHECK(profile_level_id);
179 switch (profile_level_id->profile) {
180 case webrtc::H264::kProfileConstrainedBaseline:
181 case webrtc::H264::kProfileBaseline:
182 switch (profile_level_id->level) {
183 case webrtc::H264::kLevel3:
184 return kVTProfileLevel_H264_Baseline_3_0;
185 case webrtc::H264::kLevel3_1:
186 return kVTProfileLevel_H264_Baseline_3_1;
187 case webrtc::H264::kLevel3_2:
188 return kVTProfileLevel_H264_Baseline_3_2;
189 case webrtc::H264::kLevel4:
190 return kVTProfileLevel_H264_Baseline_4_0;
191 case webrtc::H264::kLevel4_1:
192 return kVTProfileLevel_H264_Baseline_4_1;
193 case webrtc::H264::kLevel4_2:
194 return kVTProfileLevel_H264_Baseline_4_2;
195 case webrtc::H264::kLevel5:
196 return kVTProfileLevel_H264_Baseline_5_0;
197 case webrtc::H264::kLevel5_1:
198 return kVTProfileLevel_H264_Baseline_5_1;
199 case webrtc::H264::kLevel5_2:
200 return kVTProfileLevel_H264_Baseline_5_2;
201 case webrtc::H264::kLevel1:
202 case webrtc::H264::kLevel1_b:
203 case webrtc::H264::kLevel1_1:
204 case webrtc::H264::kLevel1_2:
205 case webrtc::H264::kLevel1_3:
206 case webrtc::H264::kLevel2:
207 case webrtc::H264::kLevel2_1:
208 case webrtc::H264::kLevel2_2:
209 return kVTProfileLevel_H264_Baseline_AutoLevel;
210 }
211
212 case webrtc::H264::kProfileMain:
213 switch (profile_level_id->level) {
214 case webrtc::H264::kLevel3:
215 return kVTProfileLevel_H264_Main_3_0;
216 case webrtc::H264::kLevel3_1:
217 return kVTProfileLevel_H264_Main_3_1;
218 case webrtc::H264::kLevel3_2:
219 return kVTProfileLevel_H264_Main_3_2;
220 case webrtc::H264::kLevel4:
221 return kVTProfileLevel_H264_Main_4_0;
222 case webrtc::H264::kLevel4_1:
223 return kVTProfileLevel_H264_Main_4_1;
224 case webrtc::H264::kLevel4_2:
225 return kVTProfileLevel_H264_Main_4_2;
226 case webrtc::H264::kLevel5:
227 return kVTProfileLevel_H264_Main_5_0;
228 case webrtc::H264::kLevel5_1:
229 return kVTProfileLevel_H264_Main_5_1;
230 case webrtc::H264::kLevel5_2:
231 return kVTProfileLevel_H264_Main_5_2;
232 case webrtc::H264::kLevel1:
233 case webrtc::H264::kLevel1_b:
234 case webrtc::H264::kLevel1_1:
235 case webrtc::H264::kLevel1_2:
236 case webrtc::H264::kLevel1_3:
237 case webrtc::H264::kLevel2:
238 case webrtc::H264::kLevel2_1:
239 case webrtc::H264::kLevel2_2:
240 return kVTProfileLevel_H264_Main_AutoLevel;
241 }
242
243 case webrtc::H264::kProfileConstrainedHigh:
244 case webrtc::H264::kProfileHigh:
245 switch (profile_level_id->level) {
246 case webrtc::H264::kLevel3:
247 return kVTProfileLevel_H264_High_3_0;
248 case webrtc::H264::kLevel3_1:
249 return kVTProfileLevel_H264_High_3_1;
250 case webrtc::H264::kLevel3_2:
251 return kVTProfileLevel_H264_High_3_2;
252 case webrtc::H264::kLevel4:
253 return kVTProfileLevel_H264_High_4_0;
254 case webrtc::H264::kLevel4_1:
255 return kVTProfileLevel_H264_High_4_1;
256 case webrtc::H264::kLevel4_2:
257 return kVTProfileLevel_H264_High_4_2;
258 case webrtc::H264::kLevel5:
259 return kVTProfileLevel_H264_High_5_0;
260 case webrtc::H264::kLevel5_1:
261 return kVTProfileLevel_H264_High_5_1;
262 case webrtc::H264::kLevel5_2:
263 return kVTProfileLevel_H264_High_5_2;
264 case webrtc::H264::kLevel1:
265 case webrtc::H264::kLevel1_b:
266 case webrtc::H264::kLevel1_1:
267 case webrtc::H264::kLevel1_2:
268 case webrtc::H264::kLevel1_3:
269 case webrtc::H264::kLevel2:
270 case webrtc::H264::kLevel2_1:
271 case webrtc::H264::kLevel2_2:
272 return kVTProfileLevel_H264_High_AutoLevel;
273 }
274 }
275}
Kári Tristan Helgason0bf60712017-09-25 10:26:42 +0200276} // namespace
magjed73c0eb52017-08-07 06:55:28 -0700277
278@implementation RTCVideoEncoderH264 {
279 RTCVideoCodecInfo *_codecInfo;
Danielaf3282822017-09-29 14:14:54 +0200280 std::unique_ptr<webrtc::BitrateAdjuster> _bitrateAdjuster;
magjed73c0eb52017-08-07 06:55:28 -0700281 uint32_t _targetBitrateBps;
282 uint32_t _encoderBitrateBps;
283 RTCH264PacketizationMode _packetizationMode;
284 CFStringRef _profile;
285 RTCVideoEncoderCallback _callback;
286 int32_t _width;
287 int32_t _height;
288 VTCompressionSessionRef _compressionSession;
289 RTCVideoCodecMode _mode;
290
291 webrtc::H264BitstreamParser _h264BitstreamParser;
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200292 std::vector<uint8_t> _frameScaleBuffer;
magjed73c0eb52017-08-07 06:55:28 -0700293}
294
295// .5 is set as a mininum to prevent overcompensating for large temporary
296// overshoots. We don't want to degrade video quality too badly.
297// .95 is set to prevent oscillations. When a lower bitrate is set on the
298// encoder than previously set, its output seems to have a brief period of
299// drastically reduced bitrate, so we want to avoid that. In steady state
300// conditions, 0.95 seems to give us better overall bitrate over long periods
301// of time.
302- (instancetype)initWithCodecInfo:(RTCVideoCodecInfo *)codecInfo {
303 if (self = [super init]) {
304 _codecInfo = codecInfo;
Danielaf3282822017-09-29 14:14:54 +0200305 _bitrateAdjuster.reset(new webrtc::BitrateAdjuster(
306 webrtc::Clock::GetRealTimeClock(), .5, .95));
magjed73c0eb52017-08-07 06:55:28 -0700307 _packetizationMode = RTCH264PacketizationModeNonInterleaved;
Anders Carlsson7e042812017-10-05 16:55:38 +0200308 _profile = ExtractProfile([codecInfo nativeSdpVideoFormat]);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100309 RTC_LOG(LS_INFO) << "Using profile " << CFStringToString(_profile);
Kári Tristan Helgasonfc313dc2017-10-20 11:01:22 +0200310 RTC_CHECK([codecInfo.name isEqualToString:kRTCVideoCodecH264Name]);
andersc9a85f072017-09-13 07:31:46 -0700311
312#if defined(WEBRTC_IOS)
313 [RTCUIApplicationStatusObserver prepareForUse];
314#endif
magjed73c0eb52017-08-07 06:55:28 -0700315 }
316 return self;
317}
318
319- (void)dealloc {
320 [self destroyCompressionSession];
321}
322
323- (NSInteger)startEncodeWithSettings:(RTCVideoEncoderSettings *)settings
324 numberOfCores:(int)numberOfCores {
325 RTC_DCHECK(settings);
Kári Tristan Helgasonfc313dc2017-10-20 11:01:22 +0200326 RTC_DCHECK([settings.name isEqualToString:kRTCVideoCodecH264Name]);
magjed73c0eb52017-08-07 06:55:28 -0700327
328 _width = settings.width;
329 _height = settings.height;
330 _mode = settings.mode;
331
332 // We can only set average bitrate on the HW encoder.
Kári Tristan Helgason87c54632018-04-05 09:56:14 +0200333 _targetBitrateBps = settings.startBitrate * 1000; // startBitrate is in kbps.
magjed73c0eb52017-08-07 06:55:28 -0700334 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
335
336 // TODO(tkchin): Try setting payload size via
337 // kVTCompressionPropertyKey_MaxH264SliceBytes.
338
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200339 return [self resetCompressionSessionWithPixelFormat:kNV12PixelFormat];
magjed73c0eb52017-08-07 06:55:28 -0700340}
341
342- (NSInteger)encode:(RTCVideoFrame *)frame
Peter Hanspersd9b64cd2018-01-12 16:16:18 +0100343 codecSpecificInfo:(nullable id<RTCCodecSpecificInfo>)codecSpecificInfo
magjed73c0eb52017-08-07 06:55:28 -0700344 frameTypes:(NSArray<NSNumber *> *)frameTypes {
345 RTC_DCHECK_EQ(frame.width, _width);
346 RTC_DCHECK_EQ(frame.height, _height);
347 if (!_callback || !_compressionSession) {
348 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
349 }
350#if defined(WEBRTC_IOS)
351 if (![[RTCUIApplicationStatusObserver sharedInstance] isApplicationActive]) {
352 // Ignore all encode requests when app isn't active. In this state, the
353 // hardware encoder has been invalidated by the OS.
354 return WEBRTC_VIDEO_CODEC_OK;
355 }
356#endif
357 BOOL isKeyframeRequired = NO;
358
359 // Get a pixel buffer from the pool and copy frame data over.
360 CVPixelBufferPoolRef pixelBufferPool =
361 VTCompressionSessionGetPixelBufferPool(_compressionSession);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200362 if ([self resetCompressionSessionIfNeededForPool:pixelBufferPool withFrame:frame]) {
magjed73c0eb52017-08-07 06:55:28 -0700363 pixelBufferPool = VTCompressionSessionGetPixelBufferPool(_compressionSession);
364 isKeyframeRequired = YES;
magjed73c0eb52017-08-07 06:55:28 -0700365 }
magjed73c0eb52017-08-07 06:55:28 -0700366
367 CVPixelBufferRef pixelBuffer = nullptr;
368 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
369 // Native frame buffer
370 RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer;
371 if (![rtcPixelBuffer requiresCropping]) {
372 // This pixel buffer might have a higher resolution than what the
373 // compression session is configured to. The compression session can
374 // handle that and will output encoded frames in the configured
375 // resolution regardless of the input pixel buffer resolution.
376 pixelBuffer = rtcPixelBuffer.pixelBuffer;
377 CVBufferRetain(pixelBuffer);
378 } else {
379 // Cropping required, we need to crop and scale to a new pixel buffer.
380 pixelBuffer = CreatePixelBuffer(pixelBufferPool);
381 if (!pixelBuffer) {
382 return WEBRTC_VIDEO_CODEC_ERROR;
383 }
384 int dstWidth = CVPixelBufferGetWidth(pixelBuffer);
385 int dstHeight = CVPixelBufferGetHeight(pixelBuffer);
386 if ([rtcPixelBuffer requiresScalingToWidth:dstWidth height:dstHeight]) {
387 int size =
388 [rtcPixelBuffer bufferSizeForCroppingAndScalingToWidth:dstWidth height:dstHeight];
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200389 _frameScaleBuffer.resize(size);
magjed73c0eb52017-08-07 06:55:28 -0700390 } else {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200391 _frameScaleBuffer.clear();
magjed73c0eb52017-08-07 06:55:28 -0700392 }
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200393 _frameScaleBuffer.shrink_to_fit();
394 if (![rtcPixelBuffer cropAndScaleTo:pixelBuffer withTempBuffer:_frameScaleBuffer.data()]) {
magjed73c0eb52017-08-07 06:55:28 -0700395 return WEBRTC_VIDEO_CODEC_ERROR;
396 }
397 }
398 }
399
400 if (!pixelBuffer) {
401 // We did not have a native frame buffer
402 pixelBuffer = CreatePixelBuffer(pixelBufferPool);
403 if (!pixelBuffer) {
404 return WEBRTC_VIDEO_CODEC_ERROR;
405 }
406 RTC_DCHECK(pixelBuffer);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200407 if (!CopyVideoFrameToNV12PixelBuffer([frame.buffer toI420], pixelBuffer)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100408 RTC_LOG(LS_ERROR) << "Failed to copy frame data.";
magjed73c0eb52017-08-07 06:55:28 -0700409 CVBufferRelease(pixelBuffer);
410 return WEBRTC_VIDEO_CODEC_ERROR;
411 }
412 }
413
414 // Check if we need a keyframe.
415 if (!isKeyframeRequired && frameTypes) {
416 for (NSNumber *frameType in frameTypes) {
417 if ((RTCFrameType)frameType.intValue == RTCFrameTypeVideoFrameKey) {
418 isKeyframeRequired = YES;
419 break;
420 }
421 }
422 }
423
424 CMTime presentationTimeStamp = CMTimeMake(frame.timeStampNs / rtc::kNumNanosecsPerMillisec, 1000);
425 CFDictionaryRef frameProperties = nullptr;
426 if (isKeyframeRequired) {
427 CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame};
428 CFTypeRef values[] = {kCFBooleanTrue};
429 frameProperties = CreateCFTypeDictionary(keys, values, 1);
430 }
431
432 std::unique_ptr<RTCFrameEncodeParams> encodeParams;
433 encodeParams.reset(new RTCFrameEncodeParams(self,
434 codecSpecificInfo,
435 _width,
436 _height,
437 frame.timeStampNs / rtc::kNumNanosecsPerMillisec,
438 frame.timeStamp,
439 frame.rotation));
440 encodeParams->codecSpecificInfo.packetizationMode = _packetizationMode;
441
442 // Update the bitrate if needed.
443 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()];
444
445 OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession,
446 pixelBuffer,
447 presentationTimeStamp,
448 kCMTimeInvalid,
449 frameProperties,
450 encodeParams.release(),
451 nullptr);
452 if (frameProperties) {
453 CFRelease(frameProperties);
454 }
455 if (pixelBuffer) {
456 CVBufferRelease(pixelBuffer);
457 }
458 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100459 RTC_LOG(LS_ERROR) << "Failed to encode frame with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700460 return WEBRTC_VIDEO_CODEC_ERROR;
461 }
462 return WEBRTC_VIDEO_CODEC_OK;
463}
464
465- (void)setCallback:(RTCVideoEncoderCallback)callback {
466 _callback = callback;
467}
468
469- (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate {
470 _targetBitrateBps = 1000 * bitrateKbit;
471 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
472 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()];
473 return WEBRTC_VIDEO_CODEC_OK;
474}
475
476#pragma mark - Private
477
478- (NSInteger)releaseEncoder {
479 // Need to destroy so that the session is invalidated and won't use the
480 // callback anymore. Do not remove callback until the session is invalidated
481 // since async encoder callbacks can occur until invalidation.
482 [self destroyCompressionSession];
483 _callback = nullptr;
484 return WEBRTC_VIDEO_CODEC_OK;
485}
486
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200487- (BOOL)resetCompressionSessionIfNeededForPool:(CVPixelBufferPoolRef)pixelBufferPool
488 withFrame:(RTCVideoFrame *)frame {
489 BOOL resetCompressionSession = NO;
490
Anders Carlsson5b07c242018-04-13 14:12:22 +0200491 // If we're capturing native frames in another pixel format than the compression session is
492 // configured with, make sure the compression session is reset using the correct pixel format.
493 // If we're capturing non-native frames and the compression session is configured with a non-NV12
494 // format, reset it to NV12.
495 OSType framePixelFormat = kNV12PixelFormat;
496 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
497 RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer;
498 framePixelFormat = CVPixelBufferGetPixelFormatType(rtcPixelBuffer.pixelBuffer);
499 }
500
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200501#if defined(WEBRTC_IOS)
502 if (!pixelBufferPool) {
503 // Kind of a hack. On backgrounding, the compression session seems to get
504 // invalidated, which causes this pool call to fail when the application
505 // is foregrounded and frames are being sent for encoding again.
506 // Resetting the session when this happens fixes the issue.
507 // In addition we request a keyframe so video can recover quickly.
508 resetCompressionSession = YES;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100509 RTC_LOG(LS_INFO) << "Resetting compression session due to invalid pool.";
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200510 }
511#endif
512
Anders Carlsson4df8e1a2017-12-15 10:57:57 +0100513 if (pixelBufferPool) {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200514 // The pool attribute `kCVPixelBufferPixelFormatTypeKey` can contain either an array of pixel
515 // formats or a single pixel format.
516 NSDictionary *poolAttributes =
517 (__bridge NSDictionary *)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPool);
518 id pixelFormats =
519 [poolAttributes objectForKey:(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey];
520 NSArray<NSNumber *> *compressionSessionPixelFormats = nil;
521 if ([pixelFormats isKindOfClass:[NSArray class]]) {
522 compressionSessionPixelFormats = (NSArray *)pixelFormats;
523 } else {
524 compressionSessionPixelFormats = @[ (NSNumber *)pixelFormats ];
525 }
526
527 if (![compressionSessionPixelFormats
528 containsObject:[NSNumber numberWithLong:framePixelFormat]]) {
529 resetCompressionSession = YES;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100530 RTC_LOG(LS_INFO) << "Resetting compression session due to non-matching pixel format.";
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200531 }
532 }
533
534 if (resetCompressionSession) {
535 [self resetCompressionSessionWithPixelFormat:framePixelFormat];
536 }
537 return resetCompressionSession;
538}
539
540- (int)resetCompressionSessionWithPixelFormat:(OSType)framePixelFormat {
magjed73c0eb52017-08-07 06:55:28 -0700541 [self destroyCompressionSession];
542
543 // Set source image buffer attributes. These attributes will be present on
544 // buffers retrieved from the encoder's pixel buffer pool.
545 const size_t attributesSize = 3;
546 CFTypeRef keys[attributesSize] = {
547#if defined(WEBRTC_IOS)
548 kCVPixelBufferOpenGLESCompatibilityKey,
549#elif defined(WEBRTC_MAC)
550 kCVPixelBufferOpenGLCompatibilityKey,
551#endif
552 kCVPixelBufferIOSurfacePropertiesKey,
553 kCVPixelBufferPixelFormatTypeKey
554 };
555 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200556 int64_t pixelFormatType = framePixelFormat;
557 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &pixelFormatType);
magjed73c0eb52017-08-07 06:55:28 -0700558 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
559 CFDictionaryRef sourceAttributes = CreateCFTypeDictionary(keys, values, attributesSize);
560 if (ioSurfaceValue) {
561 CFRelease(ioSurfaceValue);
562 ioSurfaceValue = nullptr;
563 }
564 if (pixelFormat) {
565 CFRelease(pixelFormat);
566 pixelFormat = nullptr;
567 }
kthelgasona4955b42017-08-24 04:22:58 -0700568 CFMutableDictionaryRef encoder_specs = nullptr;
569#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
570 // Currently hw accl is supported above 360p on mac, below 360p
571 // the compression session will be created with hw accl disabled.
572 encoder_specs = CFDictionaryCreateMutable(
573 nullptr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
574 CFDictionarySetValue(encoder_specs,
575 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
576 kCFBooleanTrue);
577#endif
578 OSStatus status =
579 VTCompressionSessionCreate(nullptr, // use default allocator
580 _width,
581 _height,
582 kCMVideoCodecType_H264,
583 encoder_specs, // use hardware accelerated encoder if available
584 sourceAttributes,
585 nullptr, // use default compressed data allocator
586 compressionOutputCallback,
587 nullptr,
588 &_compressionSession);
magjed73c0eb52017-08-07 06:55:28 -0700589 if (sourceAttributes) {
590 CFRelease(sourceAttributes);
591 sourceAttributes = nullptr;
592 }
kthelgasona4955b42017-08-24 04:22:58 -0700593 if (encoder_specs) {
594 CFRelease(encoder_specs);
595 encoder_specs = nullptr;
596 }
magjed73c0eb52017-08-07 06:55:28 -0700597 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100598 RTC_LOG(LS_ERROR) << "Failed to create compression session: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700599 return WEBRTC_VIDEO_CODEC_ERROR;
600 }
kthelgasona4955b42017-08-24 04:22:58 -0700601#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
602 CFBooleanRef hwaccl_enabled = nullptr;
603 status = VTSessionCopyProperty(_compressionSession,
604 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
605 nullptr,
606 &hwaccl_enabled);
607 if (status == noErr && (CFBooleanGetValue(hwaccl_enabled))) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100608 RTC_LOG(LS_INFO) << "Compression session created with hw accl enabled";
kthelgasona4955b42017-08-24 04:22:58 -0700609 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100610 RTC_LOG(LS_INFO) << "Compression session created with hw accl disabled";
kthelgasona4955b42017-08-24 04:22:58 -0700611 }
612#endif
magjed73c0eb52017-08-07 06:55:28 -0700613 [self configureCompressionSession];
614 return WEBRTC_VIDEO_CODEC_OK;
615}
616
617- (void)configureCompressionSession {
618 RTC_DCHECK(_compressionSession);
619 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, true);
620 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, _profile);
621 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, false);
622 [self setEncoderBitrateBps:_targetBitrateBps];
623 // TODO(tkchin): Look at entropy mode and colorspace matrices.
624 // TODO(tkchin): Investigate to see if there's any way to make this work.
625 // May need it to interop with Android. Currently this call just fails.
626 // On inspecting encoder output on iOS8, this value is set to 6.
627 // internal::SetVTSessionProperty(compression_session_,
628 // kVTCompressionPropertyKey_MaxFrameDelayCount,
629 // 1);
630
631 // Set a relatively large value for keyframe emission (7200 frames or 4 minutes).
632 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, 7200);
633 SetVTSessionProperty(
634 _compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 240);
635}
636
637- (void)destroyCompressionSession {
638 if (_compressionSession) {
639 VTCompressionSessionInvalidate(_compressionSession);
640 CFRelease(_compressionSession);
641 _compressionSession = nullptr;
642 }
643}
644
645- (NSString *)implementationName {
646 return @"VideoToolbox";
647}
648
649- (void)setBitrateBps:(uint32_t)bitrateBps {
650 if (_encoderBitrateBps != bitrateBps) {
651 [self setEncoderBitrateBps:bitrateBps];
652 }
653}
654
655- (void)setEncoderBitrateBps:(uint32_t)bitrateBps {
656 if (_compressionSession) {
657 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitrateBps);
658
659 // TODO(tkchin): Add a helper method to set array value.
660 int64_t dataLimitBytesPerSecondValue =
661 static_cast<int64_t>(bitrateBps * kLimitToAverageBitRateFactor / 8);
662 CFNumberRef bytesPerSecond =
663 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytesPerSecondValue);
664 int64_t oneSecondValue = 1;
665 CFNumberRef oneSecond =
666 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue);
667 const void *nums[2] = {bytesPerSecond, oneSecond};
668 CFArrayRef dataRateLimits = CFArrayCreate(nullptr, nums, 2, &kCFTypeArrayCallBacks);
669 OSStatus status = VTSessionSetProperty(
670 _compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits);
671 if (bytesPerSecond) {
672 CFRelease(bytesPerSecond);
673 }
674 if (oneSecond) {
675 CFRelease(oneSecond);
676 }
677 if (dataRateLimits) {
678 CFRelease(dataRateLimits);
679 }
680 if (status != noErr) {
Yura Yaroshevich27af5db2018-04-10 19:43:20 +0300681 RTC_LOG(LS_ERROR) << "Failed to set data rate limit with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700682 }
683
684 _encoderBitrateBps = bitrateBps;
685 }
686}
687
688- (void)frameWasEncoded:(OSStatus)status
689 flags:(VTEncodeInfoFlags)infoFlags
690 sampleBuffer:(CMSampleBufferRef)sampleBuffer
691 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
692 width:(int32_t)width
693 height:(int32_t)height
694 renderTimeMs:(int64_t)renderTimeMs
695 timestamp:(uint32_t)timestamp
696 rotation:(RTCVideoRotation)rotation {
697 if (status != noErr) {
Yura Yaroshevich27af5db2018-04-10 19:43:20 +0300698 RTC_LOG(LS_ERROR) << "H264 encode failed with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700699 return;
700 }
701 if (infoFlags & kVTEncodeInfo_FrameDropped) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100702 RTC_LOG(LS_INFO) << "H264 encode dropped frame.";
magjed73c0eb52017-08-07 06:55:28 -0700703 return;
704 }
705
706 BOOL isKeyframe = NO;
707 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0);
708 if (attachments != nullptr && CFArrayGetCount(attachments)) {
709 CFDictionaryRef attachment =
710 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0));
711 isKeyframe = !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync);
712 }
713
714 if (isKeyframe) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100715 RTC_LOG(LS_INFO) << "Generated keyframe";
magjed73c0eb52017-08-07 06:55:28 -0700716 }
717
718 // Convert the sample buffer into a buffer suitable for RTP packetization.
719 // TODO(tkchin): Allocate buffers through a pool.
720 std::unique_ptr<rtc::Buffer> buffer(new rtc::Buffer());
721 RTCRtpFragmentationHeader *header;
722 {
kthelgasonf8084d42017-08-30 04:47:10 -0700723 std::unique_ptr<webrtc::RTPFragmentationHeader> header_cpp;
magjed73c0eb52017-08-07 06:55:28 -0700724 bool result =
725 H264CMSampleBufferToAnnexBBuffer(sampleBuffer, isKeyframe, buffer.get(), &header_cpp);
kthelgasonf8084d42017-08-30 04:47:10 -0700726 header = [[RTCRtpFragmentationHeader alloc] initWithNativeFragmentationHeader:header_cpp.get()];
magjed73c0eb52017-08-07 06:55:28 -0700727 if (!result) {
728 return;
729 }
730 }
731
732 RTCEncodedImage *frame = [[RTCEncodedImage alloc] init];
733 frame.buffer = [NSData dataWithBytesNoCopy:buffer->data() length:buffer->size() freeWhenDone:NO];
734 frame.encodedWidth = width;
735 frame.encodedHeight = height;
736 frame.completeFrame = YES;
737 frame.frameType = isKeyframe ? RTCFrameTypeVideoFrameKey : RTCFrameTypeVideoFrameDelta;
738 frame.captureTimeMs = renderTimeMs;
739 frame.timeStamp = timestamp;
740 frame.rotation = rotation;
741 frame.contentType = (_mode == RTCVideoCodecModeScreensharing) ? RTCVideoContentTypeScreenshare :
742 RTCVideoContentTypeUnspecified;
sprangba050a62017-08-18 02:51:12 -0700743 frame.flags = webrtc::TimingFrameFlags::kInvalid;
magjed73c0eb52017-08-07 06:55:28 -0700744
745 int qp;
746 _h264BitstreamParser.ParseBitstream(buffer->data(), buffer->size());
747 _h264BitstreamParser.GetLastSliceQp(&qp);
748 frame.qp = @(qp);
749
750 BOOL res = _callback(frame, codecSpecificInfo, header);
751 if (!res) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100752 RTC_LOG(LS_ERROR) << "Encode callback failed";
magjed73c0eb52017-08-07 06:55:28 -0700753 return;
754 }
755 _bitrateAdjuster->Update(frame.buffer.length);
756}
757
758- (RTCVideoEncoderQpThresholds *)scalingSettings {
759 return [[RTCVideoEncoderQpThresholds alloc] initWithThresholdsLow:kLowH264QpThreshold
760 high:kHighH264QpThreshold];
761}
762
763@end