blob: 3d4ae71110df0306303ab9c3d5019e39e88c1e11 [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) {
103 LOG(LS_ERROR) << "Failed to lock base address: " << cvRet;
104 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) {
125 LOG(LS_ERROR) << "Error converting I420 VideoFrame to NV12 :" << ret;
126 return false;
127 }
128 return true;
129}
130
131CVPixelBufferRef CreatePixelBuffer(CVPixelBufferPoolRef pixel_buffer_pool) {
132 if (!pixel_buffer_pool) {
133 LOG(LS_ERROR) << "Failed to get pixel buffer pool.";
134 return nullptr;
135 }
136 CVPixelBufferRef pixel_buffer;
137 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(nullptr, pixel_buffer_pool, &pixel_buffer);
138 if (ret != kCVReturnSuccess) {
139 LOG(LS_ERROR) << "Failed to create pixel buffer: " << ret;
140 // 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) {
magjed85d18d42017-09-01 06:32:57 -0700154 RTC_CHECK(params);
magjed73c0eb52017-08-07 06:55:28 -0700155 std::unique_ptr<RTCFrameEncodeParams> encodeParams(
156 reinterpret_cast<RTCFrameEncodeParams *>(params));
magjed85d18d42017-09-01 06:32:57 -0700157 RTC_CHECK(encodeParams->encoder);
magjed73c0eb52017-08-07 06:55:28 -0700158 [encodeParams->encoder frameWasEncoded:status
159 flags:infoFlags
160 sampleBuffer:sampleBuffer
161 codecSpecificInfo:encodeParams->codecSpecificInfo
162 width:encodeParams->width
163 height:encodeParams->height
164 renderTimeMs:encodeParams->render_time_ms
165 timestamp:encodeParams->timestamp
166 rotation:encodeParams->rotation];
167}
168
169// Extract VideoToolbox profile out of the cricket::VideoCodec. If there is no
170// specific VideoToolbox profile for the specified level, AutoLevel will be
171// returned. The user must initialize the encoder with a resolution and
172// framerate conforming to the selected H264 level regardless.
Anders Carlsson7e042812017-10-05 16:55:38 +0200173CFStringRef ExtractProfile(webrtc::SdpVideoFormat videoFormat) {
magjed73c0eb52017-08-07 06:55:28 -0700174 const rtc::Optional<webrtc::H264::ProfileLevelId> profile_level_id =
Anders Carlsson7e042812017-10-05 16:55:38 +0200175 webrtc::H264::ParseSdpProfileLevelId(videoFormat.parameters);
magjed73c0eb52017-08-07 06:55:28 -0700176 RTC_DCHECK(profile_level_id);
177 switch (profile_level_id->profile) {
178 case webrtc::H264::kProfileConstrainedBaseline:
179 case webrtc::H264::kProfileBaseline:
180 switch (profile_level_id->level) {
181 case webrtc::H264::kLevel3:
182 return kVTProfileLevel_H264_Baseline_3_0;
183 case webrtc::H264::kLevel3_1:
184 return kVTProfileLevel_H264_Baseline_3_1;
185 case webrtc::H264::kLevel3_2:
186 return kVTProfileLevel_H264_Baseline_3_2;
187 case webrtc::H264::kLevel4:
188 return kVTProfileLevel_H264_Baseline_4_0;
189 case webrtc::H264::kLevel4_1:
190 return kVTProfileLevel_H264_Baseline_4_1;
191 case webrtc::H264::kLevel4_2:
192 return kVTProfileLevel_H264_Baseline_4_2;
193 case webrtc::H264::kLevel5:
194 return kVTProfileLevel_H264_Baseline_5_0;
195 case webrtc::H264::kLevel5_1:
196 return kVTProfileLevel_H264_Baseline_5_1;
197 case webrtc::H264::kLevel5_2:
198 return kVTProfileLevel_H264_Baseline_5_2;
199 case webrtc::H264::kLevel1:
200 case webrtc::H264::kLevel1_b:
201 case webrtc::H264::kLevel1_1:
202 case webrtc::H264::kLevel1_2:
203 case webrtc::H264::kLevel1_3:
204 case webrtc::H264::kLevel2:
205 case webrtc::H264::kLevel2_1:
206 case webrtc::H264::kLevel2_2:
207 return kVTProfileLevel_H264_Baseline_AutoLevel;
208 }
209
210 case webrtc::H264::kProfileMain:
211 switch (profile_level_id->level) {
212 case webrtc::H264::kLevel3:
213 return kVTProfileLevel_H264_Main_3_0;
214 case webrtc::H264::kLevel3_1:
215 return kVTProfileLevel_H264_Main_3_1;
216 case webrtc::H264::kLevel3_2:
217 return kVTProfileLevel_H264_Main_3_2;
218 case webrtc::H264::kLevel4:
219 return kVTProfileLevel_H264_Main_4_0;
220 case webrtc::H264::kLevel4_1:
221 return kVTProfileLevel_H264_Main_4_1;
222 case webrtc::H264::kLevel4_2:
223 return kVTProfileLevel_H264_Main_4_2;
224 case webrtc::H264::kLevel5:
225 return kVTProfileLevel_H264_Main_5_0;
226 case webrtc::H264::kLevel5_1:
227 return kVTProfileLevel_H264_Main_5_1;
228 case webrtc::H264::kLevel5_2:
229 return kVTProfileLevel_H264_Main_5_2;
230 case webrtc::H264::kLevel1:
231 case webrtc::H264::kLevel1_b:
232 case webrtc::H264::kLevel1_1:
233 case webrtc::H264::kLevel1_2:
234 case webrtc::H264::kLevel1_3:
235 case webrtc::H264::kLevel2:
236 case webrtc::H264::kLevel2_1:
237 case webrtc::H264::kLevel2_2:
238 return kVTProfileLevel_H264_Main_AutoLevel;
239 }
240
241 case webrtc::H264::kProfileConstrainedHigh:
242 case webrtc::H264::kProfileHigh:
243 switch (profile_level_id->level) {
244 case webrtc::H264::kLevel3:
245 return kVTProfileLevel_H264_High_3_0;
246 case webrtc::H264::kLevel3_1:
247 return kVTProfileLevel_H264_High_3_1;
248 case webrtc::H264::kLevel3_2:
249 return kVTProfileLevel_H264_High_3_2;
250 case webrtc::H264::kLevel4:
251 return kVTProfileLevel_H264_High_4_0;
252 case webrtc::H264::kLevel4_1:
253 return kVTProfileLevel_H264_High_4_1;
254 case webrtc::H264::kLevel4_2:
255 return kVTProfileLevel_H264_High_4_2;
256 case webrtc::H264::kLevel5:
257 return kVTProfileLevel_H264_High_5_0;
258 case webrtc::H264::kLevel5_1:
259 return kVTProfileLevel_H264_High_5_1;
260 case webrtc::H264::kLevel5_2:
261 return kVTProfileLevel_H264_High_5_2;
262 case webrtc::H264::kLevel1:
263 case webrtc::H264::kLevel1_b:
264 case webrtc::H264::kLevel1_1:
265 case webrtc::H264::kLevel1_2:
266 case webrtc::H264::kLevel1_3:
267 case webrtc::H264::kLevel2:
268 case webrtc::H264::kLevel2_1:
269 case webrtc::H264::kLevel2_2:
270 return kVTProfileLevel_H264_High_AutoLevel;
271 }
272 }
273}
Kári Tristan Helgason0bf60712017-09-25 10:26:42 +0200274} // namespace
magjed73c0eb52017-08-07 06:55:28 -0700275
276@implementation RTCVideoEncoderH264 {
277 RTCVideoCodecInfo *_codecInfo;
Danielaf3282822017-09-29 14:14:54 +0200278 std::unique_ptr<webrtc::BitrateAdjuster> _bitrateAdjuster;
magjed73c0eb52017-08-07 06:55:28 -0700279 uint32_t _targetBitrateBps;
280 uint32_t _encoderBitrateBps;
281 RTCH264PacketizationMode _packetizationMode;
282 CFStringRef _profile;
283 RTCVideoEncoderCallback _callback;
284 int32_t _width;
285 int32_t _height;
286 VTCompressionSessionRef _compressionSession;
287 RTCVideoCodecMode _mode;
288
289 webrtc::H264BitstreamParser _h264BitstreamParser;
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200290 std::vector<uint8_t> _frameScaleBuffer;
magjed73c0eb52017-08-07 06:55:28 -0700291}
292
293// .5 is set as a mininum to prevent overcompensating for large temporary
294// overshoots. We don't want to degrade video quality too badly.
295// .95 is set to prevent oscillations. When a lower bitrate is set on the
296// encoder than previously set, its output seems to have a brief period of
297// drastically reduced bitrate, so we want to avoid that. In steady state
298// conditions, 0.95 seems to give us better overall bitrate over long periods
299// of time.
300- (instancetype)initWithCodecInfo:(RTCVideoCodecInfo *)codecInfo {
301 if (self = [super init]) {
302 _codecInfo = codecInfo;
Danielaf3282822017-09-29 14:14:54 +0200303 _bitrateAdjuster.reset(new webrtc::BitrateAdjuster(
304 webrtc::Clock::GetRealTimeClock(), .5, .95));
magjed73c0eb52017-08-07 06:55:28 -0700305 _packetizationMode = RTCH264PacketizationModeNonInterleaved;
Anders Carlsson7e042812017-10-05 16:55:38 +0200306 _profile = ExtractProfile([codecInfo nativeSdpVideoFormat]);
magjed73c0eb52017-08-07 06:55:28 -0700307 LOG(LS_INFO) << "Using profile " << CFStringToString(_profile);
Kári Tristan Helgasonfc313dc2017-10-20 11:01:22 +0200308 RTC_CHECK([codecInfo.name isEqualToString:kRTCVideoCodecH264Name]);
andersc9a85f072017-09-13 07:31:46 -0700309
310#if defined(WEBRTC_IOS)
311 [RTCUIApplicationStatusObserver prepareForUse];
312#endif
magjed73c0eb52017-08-07 06:55:28 -0700313 }
314 return self;
315}
316
317- (void)dealloc {
318 [self destroyCompressionSession];
319}
320
321- (NSInteger)startEncodeWithSettings:(RTCVideoEncoderSettings *)settings
322 numberOfCores:(int)numberOfCores {
323 RTC_DCHECK(settings);
Kári Tristan Helgasonfc313dc2017-10-20 11:01:22 +0200324 RTC_DCHECK([settings.name isEqualToString:kRTCVideoCodecH264Name]);
magjed73c0eb52017-08-07 06:55:28 -0700325
326 _width = settings.width;
327 _height = settings.height;
328 _mode = settings.mode;
329
330 // We can only set average bitrate on the HW encoder.
331 _targetBitrateBps = settings.startBitrate;
332 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
333
334 // TODO(tkchin): Try setting payload size via
335 // kVTCompressionPropertyKey_MaxH264SliceBytes.
336
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200337 return [self resetCompressionSessionWithPixelFormat:kNV12PixelFormat];
magjed73c0eb52017-08-07 06:55:28 -0700338}
339
340- (NSInteger)encode:(RTCVideoFrame *)frame
341 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
342 frameTypes:(NSArray<NSNumber *> *)frameTypes {
343 RTC_DCHECK_EQ(frame.width, _width);
344 RTC_DCHECK_EQ(frame.height, _height);
345 if (!_callback || !_compressionSession) {
346 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
347 }
348#if defined(WEBRTC_IOS)
349 if (![[RTCUIApplicationStatusObserver sharedInstance] isApplicationActive]) {
350 // Ignore all encode requests when app isn't active. In this state, the
351 // hardware encoder has been invalidated by the OS.
352 return WEBRTC_VIDEO_CODEC_OK;
353 }
354#endif
355 BOOL isKeyframeRequired = NO;
356
357 // Get a pixel buffer from the pool and copy frame data over.
358 CVPixelBufferPoolRef pixelBufferPool =
359 VTCompressionSessionGetPixelBufferPool(_compressionSession);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200360 if ([self resetCompressionSessionIfNeededForPool:pixelBufferPool withFrame:frame]) {
magjed73c0eb52017-08-07 06:55:28 -0700361 pixelBufferPool = VTCompressionSessionGetPixelBufferPool(_compressionSession);
362 isKeyframeRequired = YES;
magjed73c0eb52017-08-07 06:55:28 -0700363 }
magjed73c0eb52017-08-07 06:55:28 -0700364
365 CVPixelBufferRef pixelBuffer = nullptr;
366 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
367 // Native frame buffer
368 RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer;
369 if (![rtcPixelBuffer requiresCropping]) {
370 // This pixel buffer might have a higher resolution than what the
371 // compression session is configured to. The compression session can
372 // handle that and will output encoded frames in the configured
373 // resolution regardless of the input pixel buffer resolution.
374 pixelBuffer = rtcPixelBuffer.pixelBuffer;
375 CVBufferRetain(pixelBuffer);
376 } else {
377 // Cropping required, we need to crop and scale to a new pixel buffer.
378 pixelBuffer = CreatePixelBuffer(pixelBufferPool);
379 if (!pixelBuffer) {
380 return WEBRTC_VIDEO_CODEC_ERROR;
381 }
382 int dstWidth = CVPixelBufferGetWidth(pixelBuffer);
383 int dstHeight = CVPixelBufferGetHeight(pixelBuffer);
384 if ([rtcPixelBuffer requiresScalingToWidth:dstWidth height:dstHeight]) {
385 int size =
386 [rtcPixelBuffer bufferSizeForCroppingAndScalingToWidth:dstWidth height:dstHeight];
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200387 _frameScaleBuffer.resize(size);
magjed73c0eb52017-08-07 06:55:28 -0700388 } else {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200389 _frameScaleBuffer.clear();
magjed73c0eb52017-08-07 06:55:28 -0700390 }
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200391 _frameScaleBuffer.shrink_to_fit();
392 if (![rtcPixelBuffer cropAndScaleTo:pixelBuffer withTempBuffer:_frameScaleBuffer.data()]) {
magjed73c0eb52017-08-07 06:55:28 -0700393 return WEBRTC_VIDEO_CODEC_ERROR;
394 }
395 }
396 }
397
398 if (!pixelBuffer) {
399 // We did not have a native frame buffer
400 pixelBuffer = CreatePixelBuffer(pixelBufferPool);
401 if (!pixelBuffer) {
402 return WEBRTC_VIDEO_CODEC_ERROR;
403 }
404 RTC_DCHECK(pixelBuffer);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200405 if (!CopyVideoFrameToNV12PixelBuffer([frame.buffer toI420], pixelBuffer)) {
magjed73c0eb52017-08-07 06:55:28 -0700406 LOG(LS_ERROR) << "Failed to copy frame data.";
407 CVBufferRelease(pixelBuffer);
408 return WEBRTC_VIDEO_CODEC_ERROR;
409 }
410 }
411
412 // Check if we need a keyframe.
413 if (!isKeyframeRequired && frameTypes) {
414 for (NSNumber *frameType in frameTypes) {
415 if ((RTCFrameType)frameType.intValue == RTCFrameTypeVideoFrameKey) {
416 isKeyframeRequired = YES;
417 break;
418 }
419 }
420 }
421
422 CMTime presentationTimeStamp = CMTimeMake(frame.timeStampNs / rtc::kNumNanosecsPerMillisec, 1000);
423 CFDictionaryRef frameProperties = nullptr;
424 if (isKeyframeRequired) {
425 CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame};
426 CFTypeRef values[] = {kCFBooleanTrue};
427 frameProperties = CreateCFTypeDictionary(keys, values, 1);
428 }
429
430 std::unique_ptr<RTCFrameEncodeParams> encodeParams;
431 encodeParams.reset(new RTCFrameEncodeParams(self,
432 codecSpecificInfo,
433 _width,
434 _height,
435 frame.timeStampNs / rtc::kNumNanosecsPerMillisec,
436 frame.timeStamp,
437 frame.rotation));
438 encodeParams->codecSpecificInfo.packetizationMode = _packetizationMode;
439
440 // Update the bitrate if needed.
441 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()];
442
443 OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession,
444 pixelBuffer,
445 presentationTimeStamp,
446 kCMTimeInvalid,
447 frameProperties,
448 encodeParams.release(),
449 nullptr);
450 if (frameProperties) {
451 CFRelease(frameProperties);
452 }
453 if (pixelBuffer) {
454 CVBufferRelease(pixelBuffer);
455 }
456 if (status != noErr) {
457 LOG(LS_ERROR) << "Failed to encode frame with code: " << status;
458 return WEBRTC_VIDEO_CODEC_ERROR;
459 }
460 return WEBRTC_VIDEO_CODEC_OK;
461}
462
463- (void)setCallback:(RTCVideoEncoderCallback)callback {
464 _callback = callback;
465}
466
467- (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate {
468 _targetBitrateBps = 1000 * bitrateKbit;
469 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
470 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()];
471 return WEBRTC_VIDEO_CODEC_OK;
472}
473
474#pragma mark - Private
475
476- (NSInteger)releaseEncoder {
477 // Need to destroy so that the session is invalidated and won't use the
478 // callback anymore. Do not remove callback until the session is invalidated
479 // since async encoder callbacks can occur until invalidation.
480 [self destroyCompressionSession];
481 _callback = nullptr;
482 return WEBRTC_VIDEO_CODEC_OK;
483}
484
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200485- (BOOL)resetCompressionSessionIfNeededForPool:(CVPixelBufferPoolRef)pixelBufferPool
486 withFrame:(RTCVideoFrame *)frame {
487 BOOL resetCompressionSession = NO;
488
489#if defined(WEBRTC_IOS)
490 if (!pixelBufferPool) {
491 // Kind of a hack. On backgrounding, the compression session seems to get
492 // invalidated, which causes this pool call to fail when the application
493 // is foregrounded and frames are being sent for encoding again.
494 // Resetting the session when this happens fixes the issue.
495 // In addition we request a keyframe so video can recover quickly.
496 resetCompressionSession = YES;
497 LOG(LS_INFO) << "Resetting compression session due to invalid pool.";
498 }
499#endif
500
501 // If we're capturing native frames in another pixel format than the compression session is
502 // configured with, make sure the compression session is reset using the correct pixel format.
503 OSType framePixelFormat = kNV12PixelFormat;
504 if (pixelBufferPool && [frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) {
505 RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer;
506 framePixelFormat = CVPixelBufferGetPixelFormatType(rtcPixelBuffer.pixelBuffer);
507
508 // The pool attribute `kCVPixelBufferPixelFormatTypeKey` can contain either an array of pixel
509 // formats or a single pixel format.
510 NSDictionary *poolAttributes =
511 (__bridge NSDictionary *)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPool);
512 id pixelFormats =
513 [poolAttributes objectForKey:(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey];
514 NSArray<NSNumber *> *compressionSessionPixelFormats = nil;
515 if ([pixelFormats isKindOfClass:[NSArray class]]) {
516 compressionSessionPixelFormats = (NSArray *)pixelFormats;
517 } else {
518 compressionSessionPixelFormats = @[ (NSNumber *)pixelFormats ];
519 }
520
521 if (![compressionSessionPixelFormats
522 containsObject:[NSNumber numberWithLong:framePixelFormat]]) {
523 resetCompressionSession = YES;
524 LOG(LS_INFO) << "Resetting compression session due to non-matching pixel format.";
525 }
526 }
527
528 if (resetCompressionSession) {
529 [self resetCompressionSessionWithPixelFormat:framePixelFormat];
530 }
531 return resetCompressionSession;
532}
533
534- (int)resetCompressionSessionWithPixelFormat:(OSType)framePixelFormat {
magjed73c0eb52017-08-07 06:55:28 -0700535 [self destroyCompressionSession];
536
537 // Set source image buffer attributes. These attributes will be present on
538 // buffers retrieved from the encoder's pixel buffer pool.
539 const size_t attributesSize = 3;
540 CFTypeRef keys[attributesSize] = {
541#if defined(WEBRTC_IOS)
542 kCVPixelBufferOpenGLESCompatibilityKey,
543#elif defined(WEBRTC_MAC)
544 kCVPixelBufferOpenGLCompatibilityKey,
545#endif
546 kCVPixelBufferIOSurfacePropertiesKey,
547 kCVPixelBufferPixelFormatTypeKey
548 };
549 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200550 int64_t pixelFormatType = framePixelFormat;
551 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &pixelFormatType);
magjed73c0eb52017-08-07 06:55:28 -0700552 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
553 CFDictionaryRef sourceAttributes = CreateCFTypeDictionary(keys, values, attributesSize);
554 if (ioSurfaceValue) {
555 CFRelease(ioSurfaceValue);
556 ioSurfaceValue = nullptr;
557 }
558 if (pixelFormat) {
559 CFRelease(pixelFormat);
560 pixelFormat = nullptr;
561 }
kthelgasona4955b42017-08-24 04:22:58 -0700562 CFMutableDictionaryRef encoder_specs = nullptr;
563#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
564 // Currently hw accl is supported above 360p on mac, below 360p
565 // the compression session will be created with hw accl disabled.
566 encoder_specs = CFDictionaryCreateMutable(
567 nullptr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
568 CFDictionarySetValue(encoder_specs,
569 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
570 kCFBooleanTrue);
571#endif
572 OSStatus status =
573 VTCompressionSessionCreate(nullptr, // use default allocator
574 _width,
575 _height,
576 kCMVideoCodecType_H264,
577 encoder_specs, // use hardware accelerated encoder if available
578 sourceAttributes,
579 nullptr, // use default compressed data allocator
580 compressionOutputCallback,
581 nullptr,
582 &_compressionSession);
magjed73c0eb52017-08-07 06:55:28 -0700583 if (sourceAttributes) {
584 CFRelease(sourceAttributes);
585 sourceAttributes = nullptr;
586 }
kthelgasona4955b42017-08-24 04:22:58 -0700587 if (encoder_specs) {
588 CFRelease(encoder_specs);
589 encoder_specs = nullptr;
590 }
magjed73c0eb52017-08-07 06:55:28 -0700591 if (status != noErr) {
592 LOG(LS_ERROR) << "Failed to create compression session: " << status;
593 return WEBRTC_VIDEO_CODEC_ERROR;
594 }
kthelgasona4955b42017-08-24 04:22:58 -0700595#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
596 CFBooleanRef hwaccl_enabled = nullptr;
597 status = VTSessionCopyProperty(_compressionSession,
598 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
599 nullptr,
600 &hwaccl_enabled);
601 if (status == noErr && (CFBooleanGetValue(hwaccl_enabled))) {
602 LOG(LS_INFO) << "Compression session created with hw accl enabled";
603 } else {
604 LOG(LS_INFO) << "Compression session created with hw accl disabled";
605 }
606#endif
magjed73c0eb52017-08-07 06:55:28 -0700607 [self configureCompressionSession];
608 return WEBRTC_VIDEO_CODEC_OK;
609}
610
611- (void)configureCompressionSession {
612 RTC_DCHECK(_compressionSession);
613 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, true);
614 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, _profile);
615 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, false);
616 [self setEncoderBitrateBps:_targetBitrateBps];
617 // TODO(tkchin): Look at entropy mode and colorspace matrices.
618 // TODO(tkchin): Investigate to see if there's any way to make this work.
619 // May need it to interop with Android. Currently this call just fails.
620 // On inspecting encoder output on iOS8, this value is set to 6.
621 // internal::SetVTSessionProperty(compression_session_,
622 // kVTCompressionPropertyKey_MaxFrameDelayCount,
623 // 1);
624
625 // Set a relatively large value for keyframe emission (7200 frames or 4 minutes).
626 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, 7200);
627 SetVTSessionProperty(
628 _compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 240);
629}
630
631- (void)destroyCompressionSession {
632 if (_compressionSession) {
633 VTCompressionSessionInvalidate(_compressionSession);
634 CFRelease(_compressionSession);
635 _compressionSession = nullptr;
636 }
637}
638
639- (NSString *)implementationName {
640 return @"VideoToolbox";
641}
642
643- (void)setBitrateBps:(uint32_t)bitrateBps {
644 if (_encoderBitrateBps != bitrateBps) {
645 [self setEncoderBitrateBps:bitrateBps];
646 }
647}
648
649- (void)setEncoderBitrateBps:(uint32_t)bitrateBps {
650 if (_compressionSession) {
651 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitrateBps);
652
653 // TODO(tkchin): Add a helper method to set array value.
654 int64_t dataLimitBytesPerSecondValue =
655 static_cast<int64_t>(bitrateBps * kLimitToAverageBitRateFactor / 8);
656 CFNumberRef bytesPerSecond =
657 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytesPerSecondValue);
658 int64_t oneSecondValue = 1;
659 CFNumberRef oneSecond =
660 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue);
661 const void *nums[2] = {bytesPerSecond, oneSecond};
662 CFArrayRef dataRateLimits = CFArrayCreate(nullptr, nums, 2, &kCFTypeArrayCallBacks);
663 OSStatus status = VTSessionSetProperty(
664 _compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits);
665 if (bytesPerSecond) {
666 CFRelease(bytesPerSecond);
667 }
668 if (oneSecond) {
669 CFRelease(oneSecond);
670 }
671 if (dataRateLimits) {
672 CFRelease(dataRateLimits);
673 }
674 if (status != noErr) {
675 LOG(LS_ERROR) << "Failed to set data rate limit";
676 }
677
678 _encoderBitrateBps = bitrateBps;
679 }
680}
681
682- (void)frameWasEncoded:(OSStatus)status
683 flags:(VTEncodeInfoFlags)infoFlags
684 sampleBuffer:(CMSampleBufferRef)sampleBuffer
685 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo
686 width:(int32_t)width
687 height:(int32_t)height
688 renderTimeMs:(int64_t)renderTimeMs
689 timestamp:(uint32_t)timestamp
690 rotation:(RTCVideoRotation)rotation {
691 if (status != noErr) {
692 LOG(LS_ERROR) << "H264 encode failed.";
693 return;
694 }
695 if (infoFlags & kVTEncodeInfo_FrameDropped) {
696 LOG(LS_INFO) << "H264 encode dropped frame.";
697 return;
698 }
699
700 BOOL isKeyframe = NO;
701 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0);
702 if (attachments != nullptr && CFArrayGetCount(attachments)) {
703 CFDictionaryRef attachment =
704 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0));
705 isKeyframe = !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync);
706 }
707
708 if (isKeyframe) {
709 LOG(LS_INFO) << "Generated keyframe";
710 }
711
712 // Convert the sample buffer into a buffer suitable for RTP packetization.
713 // TODO(tkchin): Allocate buffers through a pool.
714 std::unique_ptr<rtc::Buffer> buffer(new rtc::Buffer());
715 RTCRtpFragmentationHeader *header;
716 {
kthelgasonf8084d42017-08-30 04:47:10 -0700717 std::unique_ptr<webrtc::RTPFragmentationHeader> header_cpp;
magjed73c0eb52017-08-07 06:55:28 -0700718 bool result =
719 H264CMSampleBufferToAnnexBBuffer(sampleBuffer, isKeyframe, buffer.get(), &header_cpp);
kthelgasonf8084d42017-08-30 04:47:10 -0700720 header = [[RTCRtpFragmentationHeader alloc] initWithNativeFragmentationHeader:header_cpp.get()];
magjed73c0eb52017-08-07 06:55:28 -0700721 if (!result) {
722 return;
723 }
724 }
725
726 RTCEncodedImage *frame = [[RTCEncodedImage alloc] init];
727 frame.buffer = [NSData dataWithBytesNoCopy:buffer->data() length:buffer->size() freeWhenDone:NO];
728 frame.encodedWidth = width;
729 frame.encodedHeight = height;
730 frame.completeFrame = YES;
731 frame.frameType = isKeyframe ? RTCFrameTypeVideoFrameKey : RTCFrameTypeVideoFrameDelta;
732 frame.captureTimeMs = renderTimeMs;
733 frame.timeStamp = timestamp;
734 frame.rotation = rotation;
735 frame.contentType = (_mode == RTCVideoCodecModeScreensharing) ? RTCVideoContentTypeScreenshare :
736 RTCVideoContentTypeUnspecified;
sprangba050a62017-08-18 02:51:12 -0700737 frame.flags = webrtc::TimingFrameFlags::kInvalid;
magjed73c0eb52017-08-07 06:55:28 -0700738
739 int qp;
740 _h264BitstreamParser.ParseBitstream(buffer->data(), buffer->size());
741 _h264BitstreamParser.GetLastSliceQp(&qp);
742 frame.qp = @(qp);
743
744 BOOL res = _callback(frame, codecSpecificInfo, header);
745 if (!res) {
746 LOG(LS_ERROR) << "Encode callback failed";
747 return;
748 }
749 _bitrateAdjuster->Update(frame.buffer.length);
750}
751
752- (RTCVideoEncoderQpThresholds *)scalingSettings {
753 return [[RTCVideoEncoderQpThresholds alloc] initWithThresholdsLow:kLowH264QpThreshold
754 high:kHighH264QpThreshold];
755}
756
757@end