blob: 7c0d029ae7515bde1e28b1a32a3e27db24220e44 [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
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020012#import "RTCVideoEncoderH264.h"
magjed73c0eb52017-08-07 06:55:28 -070013
14#import <VideoToolbox/VideoToolbox.h>
15#include <vector>
16
17#if defined(WEBRTC_IOS)
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020018#import "helpers/UIDevice+RTCDevice.h"
magjed73c0eb52017-08-07 06:55:28 -070019#endif
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020020#import "RTCCodecSpecificInfoH264.h"
21#import "RTCH264ProfileLevelId.h"
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020022#import "api/peerconnection/RTCVideoCodecInfo+Private.h"
23#import "base/RTCCodecSpecificInfo.h"
24#import "base/RTCI420Buffer.h"
25#import "base/RTCVideoEncoder.h"
26#import "base/RTCVideoFrame.h"
27#import "base/RTCVideoFrameBuffer.h"
28#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
29#import "helpers.h"
30
Johannes Kronc3fcee72021-04-19 09:09:26 +020031#include "api/video_codecs/h264_profile_level_id.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020032#include "common_video/h264/h264_bitstream_parser.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020033#include "common_video/include/bitrate_adjuster.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020034#include "modules/video_coding/include/video_error_codes.h"
35#include "rtc_base/buffer.h"
36#include "rtc_base/logging.h"
Steve Anton10542f22019-01-11 09:11:00 -080037#include "rtc_base/time_utils.h"
Anders Carlsson7bca8ca2018-08-30 09:30:29 +020038#include "sdk/objc/components/video_codec/nalu_rewriter.h"
Mirko Bonadei65432062017-12-11 09:32:13 +010039#include "third_party/libyuv/include/libyuv/convert_from.h"
magjed73c0eb52017-08-07 06:55:28 -070040
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020041@interface RTC_OBJC_TYPE (RTCVideoEncoderH264)
42()
magjed73c0eb52017-08-07 06:55:28 -070043
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020044 - (void)frameWasEncoded : (OSStatus)status flags : (VTEncodeInfoFlags)infoFlags sampleBuffer
45 : (CMSampleBufferRef)sampleBuffer codecSpecificInfo
46 : (id<RTC_OBJC_TYPE(RTCCodecSpecificInfo)>)codecSpecificInfo width : (int32_t)width height
47 : (int32_t)height renderTimeMs : (int64_t)renderTimeMs timestamp : (uint32_t)timestamp rotation
48 : (RTCVideoRotation)rotation;
magjed73c0eb52017-08-07 06:55:28 -070049
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 {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020068 RTCFrameEncodeParams(RTC_OBJC_TYPE(RTCVideoEncoderH264) * e,
69 RTC_OBJC_TYPE(RTCCodecSpecificInfoH264) * csi,
magjed73c0eb52017-08-07 06:55:28 -070070 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 {
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020079 codecSpecificInfo = [[RTC_OBJC_TYPE(RTCCodecSpecificInfoH264) alloc] init];
magjed73c0eb52017-08-07 06:55:28 -070080 }
81 }
82
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020083 RTC_OBJC_TYPE(RTCVideoEncoderH264) * encoder;
84 RTC_OBJC_TYPE(RTCCodecSpecificInfoH264) * codecSpecificInfo;
magjed73c0eb52017-08-07 06:55:28 -070085 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.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +020095bool CopyVideoFrameToNV12PixelBuffer(id<RTC_OBJC_TYPE(RTCI420Buffer)> frameBuffer,
96 CVPixelBufferRef pixelBuffer) {
magjed73c0eb52017-08-07 06:55:28 -070097 RTC_DCHECK(pixelBuffer);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +020098 RTC_DCHECK_EQ(CVPixelBufferGetPixelFormatType(pixelBuffer), kNV12PixelFormat);
magjed73c0eb52017-08-07 06:55:28 -070099 RTC_DCHECK_EQ(CVPixelBufferGetHeightOfPlane(pixelBuffer, 0), frameBuffer.height);
100 RTC_DCHECK_EQ(CVPixelBufferGetWidthOfPlane(pixelBuffer, 0), frameBuffer.width);
101
102 CVReturn cvRet = CVPixelBufferLockBaseAddress(pixelBuffer, 0);
103 if (cvRet != kCVReturnSuccess) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100104 RTC_LOG(LS_ERROR) << "Failed to lock base address: " << cvRet;
magjed73c0eb52017-08-07 06:55:28 -0700105 return false;
106 }
107 uint8_t *dstY = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0));
108 int dstStrideY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
109 uint8_t *dstUV = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1));
110 int dstStrideUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
111 // Convert I420 to NV12.
112 int ret = libyuv::I420ToNV12(frameBuffer.dataY,
113 frameBuffer.strideY,
114 frameBuffer.dataU,
115 frameBuffer.strideU,
116 frameBuffer.dataV,
117 frameBuffer.strideV,
118 dstY,
119 dstStrideY,
120 dstUV,
121 dstStrideUV,
122 frameBuffer.width,
123 frameBuffer.height);
124 CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
125 if (ret) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100126 RTC_LOG(LS_ERROR) << "Error converting I420 VideoFrame to NV12 :" << ret;
magjed73c0eb52017-08-07 06:55:28 -0700127 return false;
128 }
129 return true;
130}
131
132CVPixelBufferRef CreatePixelBuffer(CVPixelBufferPoolRef pixel_buffer_pool) {
133 if (!pixel_buffer_pool) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100134 RTC_LOG(LS_ERROR) << "Failed to get pixel buffer pool.";
magjed73c0eb52017-08-07 06:55:28 -0700135 return nullptr;
136 }
137 CVPixelBufferRef pixel_buffer;
138 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(nullptr, pixel_buffer_pool, &pixel_buffer);
139 if (ret != kCVReturnSuccess) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100140 RTC_LOG(LS_ERROR) << "Failed to create pixel buffer: " << ret;
magjed73c0eb52017-08-07 06:55:28 -0700141 // We probably want to drop frames here, since failure probably means
142 // that the pool is empty.
143 return nullptr;
144 }
145 return pixel_buffer;
146}
147
148// This is the callback function that VideoToolbox calls when encode is
149// complete. From inspection this happens on its own queue.
150void compressionOutputCallback(void *encoder,
151 void *params,
152 OSStatus status,
153 VTEncodeInfoFlags infoFlags,
154 CMSampleBufferRef sampleBuffer) {
Anders Carlssoned2b1c92017-11-02 13:15:15 +0100155 if (!params) {
156 // If there are pending callbacks when the encoder is destroyed, this can happen.
157 return;
158 }
magjed73c0eb52017-08-07 06:55:28 -0700159 std::unique_ptr<RTCFrameEncodeParams> encodeParams(
160 reinterpret_cast<RTCFrameEncodeParams *>(params));
161 [encodeParams->encoder frameWasEncoded:status
162 flags:infoFlags
163 sampleBuffer:sampleBuffer
164 codecSpecificInfo:encodeParams->codecSpecificInfo
165 width:encodeParams->width
166 height:encodeParams->height
167 renderTimeMs:encodeParams->render_time_ms
168 timestamp:encodeParams->timestamp
169 rotation:encodeParams->rotation];
170}
171
Magnus Jedvert8b4e92d2018-04-13 15:36:43 +0200172// Extract VideoToolbox profile out of the webrtc::SdpVideoFormat. If there is
173// no specific VideoToolbox profile for the specified level, AutoLevel will be
magjed73c0eb52017-08-07 06:55:28 -0700174// returned. The user must initialize the encoder with a resolution and
175// framerate conforming to the selected H264 level regardless.
Johannes Kronc3fcee72021-04-19 09:09:26 +0200176CFStringRef ExtractProfile(const webrtc::H264ProfileLevelId &profile_level_id) {
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800177 switch (profile_level_id.profile) {
Johannes Kronc3fcee72021-04-19 09:09:26 +0200178 case webrtc::H264Profile::kProfileConstrainedBaseline:
179 case webrtc::H264Profile::kProfileBaseline:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800180 switch (profile_level_id.level) {
Johannes Kronc3fcee72021-04-19 09:09:26 +0200181 case webrtc::H264Level::kLevel3:
magjed73c0eb52017-08-07 06:55:28 -0700182 return kVTProfileLevel_H264_Baseline_3_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200183 case webrtc::H264Level::kLevel3_1:
magjed73c0eb52017-08-07 06:55:28 -0700184 return kVTProfileLevel_H264_Baseline_3_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200185 case webrtc::H264Level::kLevel3_2:
magjed73c0eb52017-08-07 06:55:28 -0700186 return kVTProfileLevel_H264_Baseline_3_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200187 case webrtc::H264Level::kLevel4:
magjed73c0eb52017-08-07 06:55:28 -0700188 return kVTProfileLevel_H264_Baseline_4_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200189 case webrtc::H264Level::kLevel4_1:
magjed73c0eb52017-08-07 06:55:28 -0700190 return kVTProfileLevel_H264_Baseline_4_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200191 case webrtc::H264Level::kLevel4_2:
magjed73c0eb52017-08-07 06:55:28 -0700192 return kVTProfileLevel_H264_Baseline_4_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200193 case webrtc::H264Level::kLevel5:
magjed73c0eb52017-08-07 06:55:28 -0700194 return kVTProfileLevel_H264_Baseline_5_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200195 case webrtc::H264Level::kLevel5_1:
magjed73c0eb52017-08-07 06:55:28 -0700196 return kVTProfileLevel_H264_Baseline_5_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200197 case webrtc::H264Level::kLevel5_2:
magjed73c0eb52017-08-07 06:55:28 -0700198 return kVTProfileLevel_H264_Baseline_5_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200199 case webrtc::H264Level::kLevel1:
200 case webrtc::H264Level::kLevel1_b:
201 case webrtc::H264Level::kLevel1_1:
202 case webrtc::H264Level::kLevel1_2:
203 case webrtc::H264Level::kLevel1_3:
204 case webrtc::H264Level::kLevel2:
205 case webrtc::H264Level::kLevel2_1:
206 case webrtc::H264Level::kLevel2_2:
magjed73c0eb52017-08-07 06:55:28 -0700207 return kVTProfileLevel_H264_Baseline_AutoLevel;
208 }
209
Johannes Kronc3fcee72021-04-19 09:09:26 +0200210 case webrtc::H264Profile::kProfileMain:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800211 switch (profile_level_id.level) {
Johannes Kronc3fcee72021-04-19 09:09:26 +0200212 case webrtc::H264Level::kLevel3:
magjed73c0eb52017-08-07 06:55:28 -0700213 return kVTProfileLevel_H264_Main_3_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200214 case webrtc::H264Level::kLevel3_1:
magjed73c0eb52017-08-07 06:55:28 -0700215 return kVTProfileLevel_H264_Main_3_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200216 case webrtc::H264Level::kLevel3_2:
magjed73c0eb52017-08-07 06:55:28 -0700217 return kVTProfileLevel_H264_Main_3_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200218 case webrtc::H264Level::kLevel4:
magjed73c0eb52017-08-07 06:55:28 -0700219 return kVTProfileLevel_H264_Main_4_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200220 case webrtc::H264Level::kLevel4_1:
magjed73c0eb52017-08-07 06:55:28 -0700221 return kVTProfileLevel_H264_Main_4_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200222 case webrtc::H264Level::kLevel4_2:
magjed73c0eb52017-08-07 06:55:28 -0700223 return kVTProfileLevel_H264_Main_4_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200224 case webrtc::H264Level::kLevel5:
magjed73c0eb52017-08-07 06:55:28 -0700225 return kVTProfileLevel_H264_Main_5_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200226 case webrtc::H264Level::kLevel5_1:
magjed73c0eb52017-08-07 06:55:28 -0700227 return kVTProfileLevel_H264_Main_5_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200228 case webrtc::H264Level::kLevel5_2:
magjed73c0eb52017-08-07 06:55:28 -0700229 return kVTProfileLevel_H264_Main_5_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200230 case webrtc::H264Level::kLevel1:
231 case webrtc::H264Level::kLevel1_b:
232 case webrtc::H264Level::kLevel1_1:
233 case webrtc::H264Level::kLevel1_2:
234 case webrtc::H264Level::kLevel1_3:
235 case webrtc::H264Level::kLevel2:
236 case webrtc::H264Level::kLevel2_1:
237 case webrtc::H264Level::kLevel2_2:
magjed73c0eb52017-08-07 06:55:28 -0700238 return kVTProfileLevel_H264_Main_AutoLevel;
239 }
240
Johannes Kronc3fcee72021-04-19 09:09:26 +0200241 case webrtc::H264Profile::kProfileConstrainedHigh:
242 case webrtc::H264Profile::kProfileHigh:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800243 switch (profile_level_id.level) {
Johannes Kronc3fcee72021-04-19 09:09:26 +0200244 case webrtc::H264Level::kLevel3:
magjed73c0eb52017-08-07 06:55:28 -0700245 return kVTProfileLevel_H264_High_3_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200246 case webrtc::H264Level::kLevel3_1:
magjed73c0eb52017-08-07 06:55:28 -0700247 return kVTProfileLevel_H264_High_3_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200248 case webrtc::H264Level::kLevel3_2:
magjed73c0eb52017-08-07 06:55:28 -0700249 return kVTProfileLevel_H264_High_3_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200250 case webrtc::H264Level::kLevel4:
magjed73c0eb52017-08-07 06:55:28 -0700251 return kVTProfileLevel_H264_High_4_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200252 case webrtc::H264Level::kLevel4_1:
magjed73c0eb52017-08-07 06:55:28 -0700253 return kVTProfileLevel_H264_High_4_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200254 case webrtc::H264Level::kLevel4_2:
magjed73c0eb52017-08-07 06:55:28 -0700255 return kVTProfileLevel_H264_High_4_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200256 case webrtc::H264Level::kLevel5:
magjed73c0eb52017-08-07 06:55:28 -0700257 return kVTProfileLevel_H264_High_5_0;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200258 case webrtc::H264Level::kLevel5_1:
magjed73c0eb52017-08-07 06:55:28 -0700259 return kVTProfileLevel_H264_High_5_1;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200260 case webrtc::H264Level::kLevel5_2:
magjed73c0eb52017-08-07 06:55:28 -0700261 return kVTProfileLevel_H264_High_5_2;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200262 case webrtc::H264Level::kLevel1:
263 case webrtc::H264Level::kLevel1_b:
264 case webrtc::H264Level::kLevel1_1:
265 case webrtc::H264Level::kLevel1_2:
266 case webrtc::H264Level::kLevel1_3:
267 case webrtc::H264Level::kLevel2:
268 case webrtc::H264Level::kLevel2_1:
269 case webrtc::H264Level::kLevel2_2:
magjed73c0eb52017-08-07 06:55:28 -0700270 return kVTProfileLevel_H264_High_AutoLevel;
271 }
272 }
273}
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800274
275// The function returns the max allowed sample rate (pixels per second) that
276// can be processed by given encoder with |profile_level_id|.
277// See https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-H.264-201610-S!!PDF-E&type=items
278// for details.
Johannes Kronc3fcee72021-04-19 09:09:26 +0200279NSUInteger GetMaxSampleRate(const webrtc::H264ProfileLevelId &profile_level_id) {
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800280 switch (profile_level_id.level) {
Johannes Kronc3fcee72021-04-19 09:09:26 +0200281 case webrtc::H264Level::kLevel3:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800282 return 10368000;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200283 case webrtc::H264Level::kLevel3_1:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800284 return 27648000;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200285 case webrtc::H264Level::kLevel3_2:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800286 return 55296000;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200287 case webrtc::H264Level::kLevel4:
288 case webrtc::H264Level::kLevel4_1:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800289 return 62914560;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200290 case webrtc::H264Level::kLevel4_2:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800291 return 133693440;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200292 case webrtc::H264Level::kLevel5:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800293 return 150994944;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200294 case webrtc::H264Level::kLevel5_1:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800295 return 251658240;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200296 case webrtc::H264Level::kLevel5_2:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800297 return 530841600;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200298 case webrtc::H264Level::kLevel1:
299 case webrtc::H264Level::kLevel1_b:
300 case webrtc::H264Level::kLevel1_1:
301 case webrtc::H264Level::kLevel1_2:
302 case webrtc::H264Level::kLevel1_3:
303 case webrtc::H264Level::kLevel2:
304 case webrtc::H264Level::kLevel2_1:
305 case webrtc::H264Level::kLevel2_2:
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800306 // Zero means auto rate setting.
307 return 0;
308 }
309}
Kári Tristan Helgason0bf60712017-09-25 10:26:42 +0200310} // namespace
magjed73c0eb52017-08-07 06:55:28 -0700311
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200312@implementation RTC_OBJC_TYPE (RTCVideoEncoderH264) {
313 RTC_OBJC_TYPE(RTCVideoCodecInfo) * _codecInfo;
Danielaf3282822017-09-29 14:14:54 +0200314 std::unique_ptr<webrtc::BitrateAdjuster> _bitrateAdjuster;
magjed73c0eb52017-08-07 06:55:28 -0700315 uint32_t _targetBitrateBps;
316 uint32_t _encoderBitrateBps;
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800317 uint32_t _encoderFrameRate;
318 uint32_t _maxAllowedFrameRate;
magjed73c0eb52017-08-07 06:55:28 -0700319 RTCH264PacketizationMode _packetizationMode;
Johannes Kronc3fcee72021-04-19 09:09:26 +0200320 absl::optional<webrtc::H264ProfileLevelId> _profile_level_id;
magjed73c0eb52017-08-07 06:55:28 -0700321 RTCVideoEncoderCallback _callback;
322 int32_t _width;
323 int32_t _height;
324 VTCompressionSessionRef _compressionSession;
Peter Hanspersf9052862018-07-26 10:41:35 +0200325 CVPixelBufferPoolRef _pixelBufferPool;
magjed73c0eb52017-08-07 06:55:28 -0700326 RTCVideoCodecMode _mode;
327
328 webrtc::H264BitstreamParser _h264BitstreamParser;
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200329 std::vector<uint8_t> _frameScaleBuffer;
magjed73c0eb52017-08-07 06:55:28 -0700330}
331
332// .5 is set as a mininum to prevent overcompensating for large temporary
333// overshoots. We don't want to degrade video quality too badly.
334// .95 is set to prevent oscillations. When a lower bitrate is set on the
335// encoder than previously set, its output seems to have a brief period of
336// drastically reduced bitrate, so we want to avoid that. In steady state
337// conditions, 0.95 seems to give us better overall bitrate over long periods
338// of time.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200339- (instancetype)initWithCodecInfo:(RTC_OBJC_TYPE(RTCVideoCodecInfo) *)codecInfo {
magjed73c0eb52017-08-07 06:55:28 -0700340 if (self = [super init]) {
341 _codecInfo = codecInfo;
Niels Möller2cb7b5e2018-04-19 10:02:26 +0200342 _bitrateAdjuster.reset(new webrtc::BitrateAdjuster(.5, .95));
magjed73c0eb52017-08-07 06:55:28 -0700343 _packetizationMode = RTCH264PacketizationModeNonInterleaved;
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800344 _profile_level_id =
Johannes Kronc3fcee72021-04-19 09:09:26 +0200345 webrtc::ParseSdpForH264ProfileLevelId([codecInfo nativeSdpVideoFormat].parameters);
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800346 RTC_DCHECK(_profile_level_id);
347 RTC_LOG(LS_INFO) << "Using profile " << CFStringToString(ExtractProfile(*_profile_level_id));
Kári Tristan Helgasonfc313dc2017-10-20 11:01:22 +0200348 RTC_CHECK([codecInfo.name isEqualToString:kRTCVideoCodecH264Name]);
magjed73c0eb52017-08-07 06:55:28 -0700349 }
350 return self;
351}
352
353- (void)dealloc {
354 [self destroyCompressionSession];
355}
356
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200357- (NSInteger)startEncodeWithSettings:(RTC_OBJC_TYPE(RTCVideoEncoderSettings) *)settings
magjed73c0eb52017-08-07 06:55:28 -0700358 numberOfCores:(int)numberOfCores {
359 RTC_DCHECK(settings);
Kári Tristan Helgasonfc313dc2017-10-20 11:01:22 +0200360 RTC_DCHECK([settings.name isEqualToString:kRTCVideoCodecH264Name]);
magjed73c0eb52017-08-07 06:55:28 -0700361
362 _width = settings.width;
363 _height = settings.height;
364 _mode = settings.mode;
365
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800366 uint32_t aligned_width = (((_width + 15) >> 4) << 4);
367 uint32_t aligned_height = (((_height + 15) >> 4) << 4);
368 _maxAllowedFrameRate = static_cast<uint32_t>(GetMaxSampleRate(*_profile_level_id) /
369 (aligned_width * aligned_height));
370
magjed73c0eb52017-08-07 06:55:28 -0700371 // We can only set average bitrate on the HW encoder.
Kári Tristan Helgason87c54632018-04-05 09:56:14 +0200372 _targetBitrateBps = settings.startBitrate * 1000; // startBitrate is in kbps.
magjed73c0eb52017-08-07 06:55:28 -0700373 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800374 _encoderFrameRate = MIN(settings.maxFramerate, _maxAllowedFrameRate);
375 if (settings.maxFramerate > _maxAllowedFrameRate && _maxAllowedFrameRate > 0) {
376 RTC_LOG(LS_WARNING) << "Initial encoder frame rate setting " << settings.maxFramerate
377 << " is larger than the "
378 << "maximal allowed frame rate " << _maxAllowedFrameRate << ".";
379 }
magjed73c0eb52017-08-07 06:55:28 -0700380
381 // TODO(tkchin): Try setting payload size via
382 // kVTCompressionPropertyKey_MaxH264SliceBytes.
383
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200384 return [self resetCompressionSessionWithPixelFormat:kNV12PixelFormat];
magjed73c0eb52017-08-07 06:55:28 -0700385}
386
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200387- (NSInteger)encode:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame
388 codecSpecificInfo:(nullable id<RTC_OBJC_TYPE(RTCCodecSpecificInfo)>)codecSpecificInfo
magjed73c0eb52017-08-07 06:55:28 -0700389 frameTypes:(NSArray<NSNumber *> *)frameTypes {
390 RTC_DCHECK_EQ(frame.width, _width);
391 RTC_DCHECK_EQ(frame.height, _height);
392 if (!_callback || !_compressionSession) {
393 return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
394 }
magjed73c0eb52017-08-07 06:55:28 -0700395 BOOL isKeyframeRequired = NO;
396
397 // Get a pixel buffer from the pool and copy frame data over.
Peter Hanspersf9052862018-07-26 10:41:35 +0200398 if ([self resetCompressionSessionIfNeededWithFrame:frame]) {
magjed73c0eb52017-08-07 06:55:28 -0700399 isKeyframeRequired = YES;
magjed73c0eb52017-08-07 06:55:28 -0700400 }
magjed73c0eb52017-08-07 06:55:28 -0700401
402 CVPixelBufferRef pixelBuffer = nullptr;
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200403 if ([frame.buffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]) {
magjed73c0eb52017-08-07 06:55:28 -0700404 // Native frame buffer
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200405 RTC_OBJC_TYPE(RTCCVPixelBuffer) *rtcPixelBuffer =
406 (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)frame.buffer;
magjed73c0eb52017-08-07 06:55:28 -0700407 if (![rtcPixelBuffer requiresCropping]) {
408 // This pixel buffer might have a higher resolution than what the
409 // compression session is configured to. The compression session can
410 // handle that and will output encoded frames in the configured
411 // resolution regardless of the input pixel buffer resolution.
412 pixelBuffer = rtcPixelBuffer.pixelBuffer;
413 CVBufferRetain(pixelBuffer);
414 } else {
415 // Cropping required, we need to crop and scale to a new pixel buffer.
Peter Hanspersf9052862018-07-26 10:41:35 +0200416 pixelBuffer = CreatePixelBuffer(_pixelBufferPool);
magjed73c0eb52017-08-07 06:55:28 -0700417 if (!pixelBuffer) {
418 return WEBRTC_VIDEO_CODEC_ERROR;
419 }
420 int dstWidth = CVPixelBufferGetWidth(pixelBuffer);
421 int dstHeight = CVPixelBufferGetHeight(pixelBuffer);
422 if ([rtcPixelBuffer requiresScalingToWidth:dstWidth height:dstHeight]) {
423 int size =
424 [rtcPixelBuffer bufferSizeForCroppingAndScalingToWidth:dstWidth height:dstHeight];
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200425 _frameScaleBuffer.resize(size);
magjed73c0eb52017-08-07 06:55:28 -0700426 } else {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200427 _frameScaleBuffer.clear();
magjed73c0eb52017-08-07 06:55:28 -0700428 }
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200429 _frameScaleBuffer.shrink_to_fit();
430 if (![rtcPixelBuffer cropAndScaleTo:pixelBuffer withTempBuffer:_frameScaleBuffer.data()]) {
Peter Hanspers56df67b2018-06-01 14:21:10 +0200431 CVBufferRelease(pixelBuffer);
magjed73c0eb52017-08-07 06:55:28 -0700432 return WEBRTC_VIDEO_CODEC_ERROR;
433 }
434 }
435 }
436
437 if (!pixelBuffer) {
438 // We did not have a native frame buffer
Peter Hanspersf9052862018-07-26 10:41:35 +0200439 pixelBuffer = CreatePixelBuffer(_pixelBufferPool);
magjed73c0eb52017-08-07 06:55:28 -0700440 if (!pixelBuffer) {
441 return WEBRTC_VIDEO_CODEC_ERROR;
442 }
443 RTC_DCHECK(pixelBuffer);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200444 if (!CopyVideoFrameToNV12PixelBuffer([frame.buffer toI420], pixelBuffer)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100445 RTC_LOG(LS_ERROR) << "Failed to copy frame data.";
magjed73c0eb52017-08-07 06:55:28 -0700446 CVBufferRelease(pixelBuffer);
447 return WEBRTC_VIDEO_CODEC_ERROR;
448 }
449 }
450
451 // Check if we need a keyframe.
452 if (!isKeyframeRequired && frameTypes) {
453 for (NSNumber *frameType in frameTypes) {
454 if ((RTCFrameType)frameType.intValue == RTCFrameTypeVideoFrameKey) {
455 isKeyframeRequired = YES;
456 break;
457 }
458 }
459 }
460
461 CMTime presentationTimeStamp = CMTimeMake(frame.timeStampNs / rtc::kNumNanosecsPerMillisec, 1000);
462 CFDictionaryRef frameProperties = nullptr;
463 if (isKeyframeRequired) {
464 CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame};
465 CFTypeRef values[] = {kCFBooleanTrue};
466 frameProperties = CreateCFTypeDictionary(keys, values, 1);
467 }
468
469 std::unique_ptr<RTCFrameEncodeParams> encodeParams;
470 encodeParams.reset(new RTCFrameEncodeParams(self,
471 codecSpecificInfo,
472 _width,
473 _height,
474 frame.timeStampNs / rtc::kNumNanosecsPerMillisec,
475 frame.timeStamp,
476 frame.rotation));
477 encodeParams->codecSpecificInfo.packetizationMode = _packetizationMode;
478
479 // Update the bitrate if needed.
Qiang Chen59a01b02018-11-19 10:30:04 -0800480 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps() frameRate:_encoderFrameRate];
magjed73c0eb52017-08-07 06:55:28 -0700481
482 OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession,
483 pixelBuffer,
484 presentationTimeStamp,
485 kCMTimeInvalid,
486 frameProperties,
487 encodeParams.release(),
488 nullptr);
489 if (frameProperties) {
490 CFRelease(frameProperties);
491 }
492 if (pixelBuffer) {
493 CVBufferRelease(pixelBuffer);
494 }
Peter Hanspersf9052862018-07-26 10:41:35 +0200495
496 if (status == kVTInvalidSessionErr) {
497 // This error occurs when entering foreground after backgrounding the app.
498 RTC_LOG(LS_ERROR) << "Invalid compression session, resetting.";
499 [self resetCompressionSessionWithPixelFormat:[self pixelFormatOfFrame:frame]];
500
501 return WEBRTC_VIDEO_CODEC_NO_OUTPUT;
Yura Yaroshevich4b070592020-01-10 12:25:24 +0300502 } else if (status == kVTVideoEncoderMalfunctionErr) {
503 // Sometimes the encoder malfunctions and needs to be restarted.
504 RTC_LOG(LS_ERROR)
505 << "Encountered video encoder malfunction error. Resetting compression session.";
506 [self resetCompressionSessionWithPixelFormat:[self pixelFormatOfFrame:frame]];
507
508 return WEBRTC_VIDEO_CODEC_NO_OUTPUT;
Peter Hanspersf9052862018-07-26 10:41:35 +0200509 } else if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100510 RTC_LOG(LS_ERROR) << "Failed to encode frame with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700511 return WEBRTC_VIDEO_CODEC_ERROR;
512 }
513 return WEBRTC_VIDEO_CODEC_OK;
514}
515
516- (void)setCallback:(RTCVideoEncoderCallback)callback {
517 _callback = callback;
518}
519
520- (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate {
521 _targetBitrateBps = 1000 * bitrateKbit;
522 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps);
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800523 if (framerate > _maxAllowedFrameRate && _maxAllowedFrameRate > 0) {
524 RTC_LOG(LS_WARNING) << "Encoder frame rate setting " << framerate << " is larger than the "
525 << "maximal allowed frame rate " << _maxAllowedFrameRate << ".";
526 }
527 framerate = MIN(framerate, _maxAllowedFrameRate);
Qiang Chen59a01b02018-11-19 10:30:04 -0800528 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps() frameRate:framerate];
magjed73c0eb52017-08-07 06:55:28 -0700529 return WEBRTC_VIDEO_CODEC_OK;
530}
531
Peter Hanspers5981bf22021-06-01 07:31:00 +0200532- (NSInteger)resolutionAlignment {
533 return 1;
534}
535
536- (BOOL)applyAlignmentToAllSimulcastLayers {
537 return NO;
538}
539
magjed73c0eb52017-08-07 06:55:28 -0700540#pragma mark - Private
541
542- (NSInteger)releaseEncoder {
543 // Need to destroy so that the session is invalidated and won't use the
544 // callback anymore. Do not remove callback until the session is invalidated
545 // since async encoder callbacks can occur until invalidation.
546 [self destroyCompressionSession];
547 _callback = nullptr;
548 return WEBRTC_VIDEO_CODEC_OK;
549}
550
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200551- (OSType)pixelFormatOfFrame:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame {
Peter Hanspersf9052862018-07-26 10:41:35 +0200552 // Use NV12 for non-native frames.
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200553 if ([frame.buffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]) {
554 RTC_OBJC_TYPE(RTCCVPixelBuffer) *rtcPixelBuffer =
555 (RTC_OBJC_TYPE(RTCCVPixelBuffer) *)frame.buffer;
Peter Hanspersf9052862018-07-26 10:41:35 +0200556 return CVPixelBufferGetPixelFormatType(rtcPixelBuffer.pixelBuffer);
557 }
558
559 return kNV12PixelFormat;
560}
561
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200562- (BOOL)resetCompressionSessionIfNeededWithFrame:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200563 BOOL resetCompressionSession = NO;
564
Anders Carlsson5b07c242018-04-13 14:12:22 +0200565 // If we're capturing native frames in another pixel format than the compression session is
566 // configured with, make sure the compression session is reset using the correct pixel format.
Peter Hanspersf9052862018-07-26 10:41:35 +0200567 OSType framePixelFormat = [self pixelFormatOfFrame:frame];
Anders Carlsson5b07c242018-04-13 14:12:22 +0200568
Peter Hanspersf9052862018-07-26 10:41:35 +0200569 if (_compressionSession) {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200570 // The pool attribute `kCVPixelBufferPixelFormatTypeKey` can contain either an array of pixel
571 // formats or a single pixel format.
572 NSDictionary *poolAttributes =
Peter Hanspersf9052862018-07-26 10:41:35 +0200573 (__bridge NSDictionary *)CVPixelBufferPoolGetPixelBufferAttributes(_pixelBufferPool);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200574 id pixelFormats =
575 [poolAttributes objectForKey:(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey];
576 NSArray<NSNumber *> *compressionSessionPixelFormats = nil;
577 if ([pixelFormats isKindOfClass:[NSArray class]]) {
578 compressionSessionPixelFormats = (NSArray *)pixelFormats;
Peter Hanspersf9052862018-07-26 10:41:35 +0200579 } else if ([pixelFormats isKindOfClass:[NSNumber class]]) {
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200580 compressionSessionPixelFormats = @[ (NSNumber *)pixelFormats ];
581 }
582
583 if (![compressionSessionPixelFormats
584 containsObject:[NSNumber numberWithLong:framePixelFormat]]) {
585 resetCompressionSession = YES;
Mirko Bonadei675513b2017-11-09 11:09:25 +0100586 RTC_LOG(LS_INFO) << "Resetting compression session due to non-matching pixel format.";
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200587 }
Peter Hanspersf9052862018-07-26 10:41:35 +0200588 } else {
589 resetCompressionSession = YES;
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200590 }
591
592 if (resetCompressionSession) {
593 [self resetCompressionSessionWithPixelFormat:framePixelFormat];
594 }
595 return resetCompressionSession;
596}
597
598- (int)resetCompressionSessionWithPixelFormat:(OSType)framePixelFormat {
magjed73c0eb52017-08-07 06:55:28 -0700599 [self destroyCompressionSession];
600
601 // Set source image buffer attributes. These attributes will be present on
602 // buffers retrieved from the encoder's pixel buffer pool.
603 const size_t attributesSize = 3;
604 CFTypeRef keys[attributesSize] = {
Jordan Rose53d3fc92021-07-06 12:16:41 -0700605#if defined(WEBRTC_IOS) && TARGET_OS_MACCATALYST
606 kCVPixelBufferMetalCompatibilityKey,
607#elif defined(WEBRTC_IOS)
608 kCVPixelBufferOpenGLESCompatibilityKey,
magjed73c0eb52017-08-07 06:55:28 -0700609#elif defined(WEBRTC_MAC)
Jordan Rose53d3fc92021-07-06 12:16:41 -0700610 kCVPixelBufferOpenGLCompatibilityKey,
magjed73c0eb52017-08-07 06:55:28 -0700611#endif
Jordan Rose53d3fc92021-07-06 12:16:41 -0700612 kCVPixelBufferIOSurfacePropertiesKey,
613 kCVPixelBufferPixelFormatTypeKey};
magjed73c0eb52017-08-07 06:55:28 -0700614 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0);
Anders Carlssonf3ee3b72017-10-23 15:23:00 +0200615 int64_t pixelFormatType = framePixelFormat;
616 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &pixelFormatType);
magjed73c0eb52017-08-07 06:55:28 -0700617 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat};
618 CFDictionaryRef sourceAttributes = CreateCFTypeDictionary(keys, values, attributesSize);
619 if (ioSurfaceValue) {
620 CFRelease(ioSurfaceValue);
621 ioSurfaceValue = nullptr;
622 }
623 if (pixelFormat) {
624 CFRelease(pixelFormat);
625 pixelFormat = nullptr;
626 }
kthelgasona4955b42017-08-24 04:22:58 -0700627 CFMutableDictionaryRef encoder_specs = nullptr;
628#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
629 // Currently hw accl is supported above 360p on mac, below 360p
630 // the compression session will be created with hw accl disabled.
631 encoder_specs = CFDictionaryCreateMutable(
632 nullptr, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
633 CFDictionarySetValue(encoder_specs,
634 kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
635 kCFBooleanTrue);
636#endif
637 OSStatus status =
638 VTCompressionSessionCreate(nullptr, // use default allocator
639 _width,
640 _height,
641 kCMVideoCodecType_H264,
642 encoder_specs, // use hardware accelerated encoder if available
643 sourceAttributes,
644 nullptr, // use default compressed data allocator
645 compressionOutputCallback,
646 nullptr,
647 &_compressionSession);
magjed73c0eb52017-08-07 06:55:28 -0700648 if (sourceAttributes) {
649 CFRelease(sourceAttributes);
650 sourceAttributes = nullptr;
651 }
kthelgasona4955b42017-08-24 04:22:58 -0700652 if (encoder_specs) {
653 CFRelease(encoder_specs);
654 encoder_specs = nullptr;
655 }
magjed73c0eb52017-08-07 06:55:28 -0700656 if (status != noErr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100657 RTC_LOG(LS_ERROR) << "Failed to create compression session: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700658 return WEBRTC_VIDEO_CODEC_ERROR;
659 }
kthelgasona4955b42017-08-24 04:22:58 -0700660#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
661 CFBooleanRef hwaccl_enabled = nullptr;
662 status = VTSessionCopyProperty(_compressionSession,
663 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
664 nullptr,
665 &hwaccl_enabled);
666 if (status == noErr && (CFBooleanGetValue(hwaccl_enabled))) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100667 RTC_LOG(LS_INFO) << "Compression session created with hw accl enabled";
kthelgasona4955b42017-08-24 04:22:58 -0700668 } else {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100669 RTC_LOG(LS_INFO) << "Compression session created with hw accl disabled";
kthelgasona4955b42017-08-24 04:22:58 -0700670 }
671#endif
magjed73c0eb52017-08-07 06:55:28 -0700672 [self configureCompressionSession];
Peter Hanspersf9052862018-07-26 10:41:35 +0200673
674 // The pixel buffer pool is dependent on the compression session so if the session is reset, the
675 // pool should be reset as well.
676 _pixelBufferPool = VTCompressionSessionGetPixelBufferPool(_compressionSession);
677
magjed73c0eb52017-08-07 06:55:28 -0700678 return WEBRTC_VIDEO_CODEC_OK;
679}
680
681- (void)configureCompressionSession {
682 RTC_DCHECK(_compressionSession);
683 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, true);
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800684 SetVTSessionProperty(_compressionSession,
685 kVTCompressionPropertyKey_ProfileLevel,
686 ExtractProfile(*_profile_level_id));
magjed73c0eb52017-08-07 06:55:28 -0700687 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, false);
Qiang Chen59a01b02018-11-19 10:30:04 -0800688 [self setEncoderBitrateBps:_targetBitrateBps frameRate:_encoderFrameRate];
magjed73c0eb52017-08-07 06:55:28 -0700689 // TODO(tkchin): Look at entropy mode and colorspace matrices.
690 // TODO(tkchin): Investigate to see if there's any way to make this work.
691 // May need it to interop with Android. Currently this call just fails.
692 // On inspecting encoder output on iOS8, this value is set to 6.
693 // internal::SetVTSessionProperty(compression_session_,
694 // kVTCompressionPropertyKey_MaxFrameDelayCount,
695 // 1);
696
697 // Set a relatively large value for keyframe emission (7200 frames or 4 minutes).
698 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, 7200);
699 SetVTSessionProperty(
700 _compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 240);
701}
702
703- (void)destroyCompressionSession {
704 if (_compressionSession) {
705 VTCompressionSessionInvalidate(_compressionSession);
706 CFRelease(_compressionSession);
707 _compressionSession = nullptr;
Peter Hanspersf9052862018-07-26 10:41:35 +0200708 _pixelBufferPool = nullptr;
magjed73c0eb52017-08-07 06:55:28 -0700709 }
710}
711
712- (NSString *)implementationName {
713 return @"VideoToolbox";
714}
715
Qiang Chen59a01b02018-11-19 10:30:04 -0800716- (void)setBitrateBps:(uint32_t)bitrateBps frameRate:(uint32_t)frameRate {
717 if (_encoderBitrateBps != bitrateBps || _encoderFrameRate != frameRate) {
718 [self setEncoderBitrateBps:bitrateBps frameRate:frameRate];
magjed73c0eb52017-08-07 06:55:28 -0700719 }
720}
721
Qiang Chen59a01b02018-11-19 10:30:04 -0800722- (void)setEncoderBitrateBps:(uint32_t)bitrateBps frameRate:(uint32_t)frameRate {
magjed73c0eb52017-08-07 06:55:28 -0700723 if (_compressionSession) {
724 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitrateBps);
Qiang Chenfa1ca1e2019-01-14 10:03:26 -0800725
726 // With zero |_maxAllowedFrameRate|, we fall back to automatic frame rate detection.
727 if (_maxAllowedFrameRate > 0) {
728 SetVTSessionProperty(
729 _compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, frameRate);
730 }
magjed73c0eb52017-08-07 06:55:28 -0700731
732 // TODO(tkchin): Add a helper method to set array value.
733 int64_t dataLimitBytesPerSecondValue =
734 static_cast<int64_t>(bitrateBps * kLimitToAverageBitRateFactor / 8);
735 CFNumberRef bytesPerSecond =
736 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytesPerSecondValue);
737 int64_t oneSecondValue = 1;
738 CFNumberRef oneSecond =
739 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue);
740 const void *nums[2] = {bytesPerSecond, oneSecond};
741 CFArrayRef dataRateLimits = CFArrayCreate(nullptr, nums, 2, &kCFTypeArrayCallBacks);
742 OSStatus status = VTSessionSetProperty(
743 _compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits);
744 if (bytesPerSecond) {
745 CFRelease(bytesPerSecond);
746 }
747 if (oneSecond) {
748 CFRelease(oneSecond);
749 }
750 if (dataRateLimits) {
751 CFRelease(dataRateLimits);
752 }
753 if (status != noErr) {
Yura Yaroshevich27af5db2018-04-10 19:43:20 +0300754 RTC_LOG(LS_ERROR) << "Failed to set data rate limit with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700755 }
756
757 _encoderBitrateBps = bitrateBps;
Qiang Chen59a01b02018-11-19 10:30:04 -0800758 _encoderFrameRate = frameRate;
magjed73c0eb52017-08-07 06:55:28 -0700759 }
760}
761
762- (void)frameWasEncoded:(OSStatus)status
763 flags:(VTEncodeInfoFlags)infoFlags
764 sampleBuffer:(CMSampleBufferRef)sampleBuffer
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200765 codecSpecificInfo:(id<RTC_OBJC_TYPE(RTCCodecSpecificInfo)>)codecSpecificInfo
magjed73c0eb52017-08-07 06:55:28 -0700766 width:(int32_t)width
767 height:(int32_t)height
768 renderTimeMs:(int64_t)renderTimeMs
769 timestamp:(uint32_t)timestamp
770 rotation:(RTCVideoRotation)rotation {
Peter Hanspers74cc9ea2021-06-11 15:52:57 +0200771 RTCVideoEncoderCallback callback = _callback;
772 if (!callback) {
773 return;
774 }
magjed73c0eb52017-08-07 06:55:28 -0700775 if (status != noErr) {
Yura Yaroshevich27af5db2018-04-10 19:43:20 +0300776 RTC_LOG(LS_ERROR) << "H264 encode failed with code: " << status;
magjed73c0eb52017-08-07 06:55:28 -0700777 return;
778 }
779 if (infoFlags & kVTEncodeInfo_FrameDropped) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100780 RTC_LOG(LS_INFO) << "H264 encode dropped frame.";
magjed73c0eb52017-08-07 06:55:28 -0700781 return;
782 }
783
784 BOOL isKeyframe = NO;
785 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0);
786 if (attachments != nullptr && CFArrayGetCount(attachments)) {
787 CFDictionaryRef attachment =
788 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0));
789 isKeyframe = !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync);
790 }
791
792 if (isKeyframe) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100793 RTC_LOG(LS_INFO) << "Generated keyframe";
magjed73c0eb52017-08-07 06:55:28 -0700794 }
795
Kári Tristan Helgason589b41e2020-03-04 13:45:38 +0100796 __block std::unique_ptr<rtc::Buffer> buffer = std::make_unique<rtc::Buffer>();
Danil Chapovalov090049c2020-08-25 15:49:53 +0200797 if (!webrtc::H264CMSampleBufferToAnnexBBuffer(sampleBuffer, isKeyframe, buffer.get())) {
798 return;
magjed73c0eb52017-08-07 06:55:28 -0700799 }
800
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200801 RTC_OBJC_TYPE(RTCEncodedImage) *frame = [[RTC_OBJC_TYPE(RTCEncodedImage) alloc] init];
Kári Tristan Helgason589b41e2020-03-04 13:45:38 +0100802 // This assumes ownership of `buffer` and is responsible for freeing it when done.
803 frame.buffer = [[NSData alloc] initWithBytesNoCopy:buffer->data()
804 length:buffer->size()
805 deallocator:^(void *bytes, NSUInteger size) {
806 buffer.reset();
807 }];
magjed73c0eb52017-08-07 06:55:28 -0700808 frame.encodedWidth = width;
809 frame.encodedHeight = height;
magjed73c0eb52017-08-07 06:55:28 -0700810 frame.frameType = isKeyframe ? RTCFrameTypeVideoFrameKey : RTCFrameTypeVideoFrameDelta;
811 frame.captureTimeMs = renderTimeMs;
812 frame.timeStamp = timestamp;
813 frame.rotation = rotation;
814 frame.contentType = (_mode == RTCVideoCodecModeScreensharing) ? RTCVideoContentTypeScreenshare :
815 RTCVideoContentTypeUnspecified;
Ilya Nikolaevskiyb6c462d2018-06-05 15:21:32 +0200816 frame.flags = webrtc::VideoSendTiming::kInvalid;
magjed73c0eb52017-08-07 06:55:28 -0700817
Niels Möller6afa7942020-12-17 14:31:26 +0100818 _h264BitstreamParser.ParseBitstream(*buffer);
819 frame.qp = @(_h264BitstreamParser.GetLastSliceQp().value_or(-1));
magjed73c0eb52017-08-07 06:55:28 -0700820
Peter Hanspers74cc9ea2021-06-11 15:52:57 +0200821 BOOL res = callback(frame, codecSpecificInfo);
magjed73c0eb52017-08-07 06:55:28 -0700822 if (!res) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100823 RTC_LOG(LS_ERROR) << "Encode callback failed";
magjed73c0eb52017-08-07 06:55:28 -0700824 return;
825 }
826 _bitrateAdjuster->Update(frame.buffer.length);
827}
828
Mirko Bonadeia81e9c82020-05-04 16:14:32 +0200829- (nullable RTC_OBJC_TYPE(RTCVideoEncoderQpThresholds) *)scalingSettings {
830 return [[RTC_OBJC_TYPE(RTCVideoEncoderQpThresholds) alloc]
831 initWithThresholdsLow:kLowH264QpThreshold
832 high:kHighH264QpThreshold];
magjed73c0eb52017-08-07 06:55:28 -0700833}
834
835@end