blob: a72ce09b569a7bf01a2b688d9cd50bcf36dba27a [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
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.
333 _targetBitrateBps = settings.startBitrate;
334 _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
343 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
344 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
491#if defined(WEBRTC_IOS)
492 if (!pixelBufferPool) {
493 // Kind of a hack. On backgrounding, the compression session seems to get
494 // invalidated, which causes this pool call to fail when the application
495 // is foregrounded and frames are being sent for encoding again.
496 // Resetting the session when this happens fixes the issue.
497 // In addition we request a keyframe so video can recover quickly.
498 resetCompressionSession = YES;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100499 RTC_LOG(LS_INFO) << "Resetting compression session due to invalid pool.";
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200500 }
501#endif
502
503 // If we're capturing native frames in another pixel format than the compression session is
504 // configured with, make sure the compression session is reset using the correct pixel format.
505 OSType framePixelFormat = kNV12PixelFormat;
506 if (pixelBufferPool && [frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
507 RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer;
508 framePixelFormat = CVPixelBufferGetPixelFormatType(rtcPixelBuffer.pixelBuffer);
509
510 // The pool attribute `kCVPixelBufferPixelFormatTypeKey` can contain either an array of pixel
511 // formats or a single pixel format.
512 NSDictionary *poolAttributes =
513 (__bridge NSDictionary *)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPool);
514 id pixelFormats =
515 [poolAttributes objectForKey:(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey];
516 NSArray<NSNumber *> *compressionSessionPixelFormats = nil;
517 if ([pixelFormats isKindOfClass:[NSArray class]]) {
518 compressionSessionPixelFormats = (NSArray *)pixelFormats;
519 } else {
520 compressionSessionPixelFormats = @[ (NSNumber *)pixelFormats ];
521 }
522
523 if (![compressionSessionPixelFormats
524 containsObject:[NSNumber numberWithLong:framePixelFormat]]) {
525 resetCompressionSession = YES;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100526 RTC_LOG(LS_INFO) << "Resetting compression session due to non-matching pixel format.";
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200527 }
528 }
529
530 if (resetCompressionSession) {
531 [self resetCompressionSessionWithPixelFormat:framePixelFormat];
532 }
533 return resetCompressionSession;
534}
535
536- (int)resetCompressionSessionWithPixelFormat:(OSType)framePixelFormat {
magjed73c0eb52017-08-07 06:55:28 -0700537 [self destroyCompressionSession];
538
539 // Set source image buffer attributes. These attributes will be present on
540 // buffers retrieved from the encoder's pixel buffer pool.
541 const size_t attributesSize = 3;
542 CFTypeRef keys[attributesSize] = {
543#if defined(WEBRTC_IOS)
544 kCVPixelBufferOpenGLESCompatibilityKey,
545#elif defined(WEBRTC_MAC)
546 kCVPixelBufferOpenGLCompatibilityKey,
547#endif
548 kCVPixelBufferIOSurfacePropertiesKey,
549 kCVPixelBufferPixelFormatTypeKey
550 };
551 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200552 int64_t pixelFormatType = framePixelFormat;
553 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &pixelFormatType);
magjed73c0eb52017-08-07 06:55:28 -0700554 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
555 CFDictionaryRef sourceAttributes = CreateCFTypeDictionary(keys, values, attributesSize);
556 if (ioSurfaceValue) {
557 CFRelease(ioSurfaceValue);
558 ioSurfaceValue = nullptr;
559 }
560 if (pixelFormat) {
561 CFRelease(pixelFormat);
562 pixelFormat = nullptr;
563 }
kthelgasona4955b42017-08-24 04:22:58 -0700564 CFMutableDictionaryRef encoder_specs = nullptr;
565#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
566 // Currently hw accl is supported above 360p on mac, below 360p
567 // the compression session will be created with hw accl disabled.
568 encoder_specs = CFDictionaryCreateMutable(
569 nullptr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
570 CFDictionarySetValue(encoder_specs,
571 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
572 kCFBooleanTrue);
573#endif
574 OSStatus status =
575 VTCompressionSessionCreate(nullptr, // use default allocator
576 _width,
577 _height,
578 kCMVideoCodecType_H264,
579 encoder_specs, // use hardware accelerated encoder if available
580 sourceAttributes,
581 nullptr, // use default compressed data allocator
582 compressionOutputCallback,
583 nullptr,
584 &_compressionSession);
magjed73c0eb52017-08-07 06:55:28 -0700585 if (sourceAttributes) {
586 CFRelease(sourceAttributes);
587 sourceAttributes = nullptr;
588 }
kthelgasona4955b42017-08-24 04:22:58 -0700589 if (encoder_specs) {
590 CFRelease(encoder_specs);
591 encoder_specs = nullptr;
592 }
magjed73c0eb52017-08-07 06:55:28 -0700593 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100594 RTC_LOG(LS_ERROR) << "Failed to create compression session: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700595 return WEBRTC_VIDEO_CODEC_ERROR;
596 }
kthelgasona4955b42017-08-24 04:22:58 -0700597#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
598 CFBooleanRef hwaccl_enabled = nullptr;
599 status = VTSessionCopyProperty(_compressionSession,
600 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
601 nullptr,
602 &hwaccl_enabled);
603 if (status == noErr && (CFBooleanGetValue(hwaccl_enabled))) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100604 RTC_LOG(LS_INFO) << "Compression session created with hw accl enabled";
kthelgasona4955b42017-08-24 04:22:58 -0700605 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100606 RTC_LOG(LS_INFO) << "Compression session created with hw accl disabled";
kthelgasona4955b42017-08-24 04:22:58 -0700607 }
608#endif
magjed73c0eb52017-08-07 06:55:28 -0700609 [self configureCompressionSession];
610 return WEBRTC_VIDEO_CODEC_OK;
611}
612
613- (void)configureCompressionSession {
614 RTC_DCHECK(_compressionSession);
615 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, true);
616 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, _profile);
617 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, false);
618 [self setEncoderBitrateBps:_targetBitrateBps];
619 // TODO(tkchin): Look at entropy mode and colorspace matrices.
620 // TODO(tkchin): Investigate to see if there's any way to make this work.
621 // May need it to interop with Android. Currently this call just fails.
622 // On inspecting encoder output on iOS8, this value is set to 6.
623 // internal::SetVTSessionProperty(compression_session_,
624 // kVTCompressionPropertyKey_MaxFrameDelayCount,
625 // 1);
626
627 // Set a relatively large value for keyframe emission (7200 frames or 4 minutes).
628 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, 7200);
629 SetVTSessionProperty(
630 _compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 240);
631}
632
633- (void)destroyCompressionSession {
634 if (_compressionSession) {
635 VTCompressionSessionInvalidate(_compressionSession);
636 CFRelease(_compressionSession);
637 _compressionSession = nullptr;
638 }
639}
640
641- (NSString *)implementationName {
642 return @"VideoToolbox";
643}
644
645- (void)setBitrateBps:(uint32_t)bitrateBps {
646 if (_encoderBitrateBps != bitrateBps) {
647 [self setEncoderBitrateBps:bitrateBps];
648 }
649}
650
651- (void)setEncoderBitrateBps:(uint32_t)bitrateBps {
652 if (_compressionSession) {
653 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitrateBps);
654
655 // TODO(tkchin): Add a helper method to set array value.
656 int64_t dataLimitBytesPerSecondValue =
657 static_cast<int64_t>(bitrateBps * kLimitToAverageBitRateFactor / 8);
658 CFNumberRef bytesPerSecond =
659 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytesPerSecondValue);
660 int64_t oneSecondValue = 1;
661 CFNumberRef oneSecond =
662 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue);
663 const void *nums[2] = {bytesPerSecond, oneSecond};
664 CFArrayRef dataRateLimits = CFArrayCreate(nullptr, nums, 2, &kCFTypeArrayCallBacks);
665 OSStatus status = VTSessionSetProperty(
666 _compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits);
667 if (bytesPerSecond) {
668 CFRelease(bytesPerSecond);
669 }
670 if (oneSecond) {
671 CFRelease(oneSecond);
672 }
673 if (dataRateLimits) {
674 CFRelease(dataRateLimits);
675 }
676 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100677 RTC_LOG(LS_ERROR) << "Failed to set data rate limit";
magjed73c0eb52017-08-07 06:55:28 -0700678 }
679
680 _encoderBitrateBps = bitrateBps;
681 }
682}
683
684- (void)frameWasEncoded:(OSStatus)status
685 flags:(VTEncodeInfoFlags)infoFlags
686 sampleBuffer:(CMSampleBufferRef)sampleBuffer
687 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
688 width:(int32_t)width
689 height:(int32_t)height
690 renderTimeMs:(int64_t)renderTimeMs
691 timestamp:(uint32_t)timestamp
692 rotation:(RTCVideoRotation)rotation {
693 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100694 RTC_LOG(LS_ERROR) << "H264 encode failed.";
magjed73c0eb52017-08-07 06:55:28 -0700695 return;
696 }
697 if (infoFlags & kVTEncodeInfo_FrameDropped) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100698 RTC_LOG(LS_INFO) << "H264 encode dropped frame.";
magjed73c0eb52017-08-07 06:55:28 -0700699 return;
700 }
701
702 BOOL isKeyframe = NO;
703 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0);
704 if (attachments != nullptr && CFArrayGetCount(attachments)) {
705 CFDictionaryRef attachment =
706 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0));
707 isKeyframe = !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync);
708 }
709
710 if (isKeyframe) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100711 RTC_LOG(LS_INFO) << "Generated keyframe";
magjed73c0eb52017-08-07 06:55:28 -0700712 }
713
714 // Convert the sample buffer into a buffer suitable for RTP packetization.
715 // TODO(tkchin): Allocate buffers through a pool.
716 std::unique_ptr<rtc::Buffer> buffer(new rtc::Buffer());
717 RTCRtpFragmentationHeader *header;
718 {
kthelgasonf8084d42017-08-30 04:47:10 -0700719 std::unique_ptr<webrtc::RTPFragmentationHeader> header_cpp;
magjed73c0eb52017-08-07 06:55:28 -0700720 bool result =
721 H264CMSampleBufferToAnnexBBuffer(sampleBuffer, isKeyframe, buffer.get(), &header_cpp);
kthelgasonf8084d42017-08-30 04:47:10 -0700722 header = [[RTCRtpFragmentationHeader alloc] initWithNativeFragmentationHeader:header_cpp.get()];
magjed73c0eb52017-08-07 06:55:28 -0700723 if (!result) {
724 return;
725 }
726 }
727
728 RTCEncodedImage *frame = [[RTCEncodedImage alloc] init];
729 frame.buffer = [NSData dataWithBytesNoCopy:buffer->data() length:buffer->size() freeWhenDone:NO];
730 frame.encodedWidth = width;
731 frame.encodedHeight = height;
732 frame.completeFrame = YES;
733 frame.frameType = isKeyframe ? RTCFrameTypeVideoFrameKey : RTCFrameTypeVideoFrameDelta;
734 frame.captureTimeMs = renderTimeMs;
735 frame.timeStamp = timestamp;
736 frame.rotation = rotation;
737 frame.contentType = (_mode == RTCVideoCodecModeScreensharing) ? RTCVideoContentTypeScreenshare :
738 RTCVideoContentTypeUnspecified;
sprangba050a62017-08-18 02:51:12 -0700739 frame.flags = webrtc::TimingFrameFlags::kInvalid;
magjed73c0eb52017-08-07 06:55:28 -0700740
741 int qp;
742 _h264BitstreamParser.ParseBitstream(buffer->data(), buffer->size());
743 _h264BitstreamParser.GetLastSliceQp(&qp);
744 frame.qp = @(qp);
745
746 BOOL res = _callback(frame, codecSpecificInfo, header);
747 if (!res) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100748 RTC_LOG(LS_ERROR) << "Encode callback failed";
magjed73c0eb52017-08-07 06:55:28 -0700749 return;
750 }
751 _bitrateAdjuster->Update(frame.buffer.length);
752}
753
754- (RTCVideoEncoderQpThresholds *)scalingSettings {
755 return [[RTCVideoEncoderQpThresholds alloc] initWithThresholdsLow:kLowH264QpThreshold
756 high:kHighH264QpThreshold];
757}
758
759@end