serve-sim-sjchmiela 0.1.56 → 0.1.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -42,6 +42,7 @@ final class WebRTCPublisher {
|
|
|
42
42
|
private var lastFrameTimestampNs: Int64 = 0
|
|
43
43
|
private let statsStartNs = DispatchTime.now().uptimeNanoseconds
|
|
44
44
|
private var lastFrameSentAtNs: UInt64 = 0
|
|
45
|
+
private let usesCustomSoftwareH264Encoder: Bool
|
|
45
46
|
private var totalI420Ms = 0.0
|
|
46
47
|
private var lastI420Ms = 0.0
|
|
47
48
|
private var maxI420Ms = 0.0
|
|
@@ -53,6 +54,7 @@ final class WebRTCPublisher {
|
|
|
53
54
|
private var selectedCodecName = "H264"
|
|
54
55
|
private let h264PixelBufferConverter = H264WebRTCPixelBufferConverter()
|
|
55
56
|
private let h264WebRTCSupport: WebRTCH264Support
|
|
57
|
+
private let h264FrameModeOverride: H264WebRTCFrameMode?
|
|
56
58
|
private var maxFps = 30
|
|
57
59
|
private var targetBitrate = 6_000_000
|
|
58
60
|
private var convertedFrameCount: Int64 = 0
|
|
@@ -66,7 +68,12 @@ final class WebRTCPublisher {
|
|
|
66
68
|
|
|
67
69
|
init() {
|
|
68
70
|
h264WebRTCSupport = Self.detectH264WebRTCSupport()
|
|
69
|
-
|
|
71
|
+
h264FrameModeOverride = Self.h264FrameModeOverride()
|
|
72
|
+
usesCustomSoftwareH264Encoder = h264WebRTCSupport.allowed && h264WebRTCSupport.usesHardware == false
|
|
73
|
+
let defaultEncoderFactory = LKRTCDefaultVideoEncoderFactory()
|
|
74
|
+
let encoderFactory: LKRTCVideoEncoderFactory = usesCustomSoftwareH264Encoder
|
|
75
|
+
? ServeSimSoftwareH264EncoderFactory(fallback: defaultEncoderFactory)
|
|
76
|
+
: defaultEncoderFactory
|
|
70
77
|
let decoderFactory = LKRTCDefaultVideoDecoderFactory()
|
|
71
78
|
factory = LKRTCPeerConnectionFactory(
|
|
72
79
|
encoderFactory: encoderFactory,
|
|
@@ -81,7 +88,7 @@ final class WebRTCPublisher {
|
|
|
81
88
|
: "disabled(\(h264WebRTCSupport.reason ?? "unsupported runtime"))"
|
|
82
89
|
print(
|
|
83
90
|
"[webrtc] Publisher ready (default codec factory + screen-cast video source) " +
|
|
84
|
-
"h264=\(h264Status) senderCodecs=\(senderCodecSummary())"
|
|
91
|
+
"h264=\(h264Status) h264FrameMode=\(h264FrameModeDescription()) senderCodecs=\(senderCodecSummary())"
|
|
85
92
|
)
|
|
86
93
|
}
|
|
87
94
|
|
|
@@ -147,6 +154,8 @@ final class WebRTCPublisher {
|
|
|
147
154
|
"selectedCodec": selectedCodecName,
|
|
148
155
|
"h264WebRTCEnabled": h264WebRTCSupport.allowed,
|
|
149
156
|
"h264WebRTCProbe": h264WebRTCSupport.probeSummary,
|
|
157
|
+
"h264WebRTCCustomEncoder": usesCustomSoftwareH264Encoder,
|
|
158
|
+
"h264FrameMode": h264FrameModeDescription(),
|
|
150
159
|
"frameMode": lastFrameMode,
|
|
151
160
|
"outputWidth": lastOutputWidth,
|
|
152
161
|
"outputHeight": lastOutputHeight,
|
|
@@ -231,26 +240,45 @@ final class WebRTCPublisher {
|
|
|
231
240
|
var frameMode = usedNativeFrame ? "native" : "i420"
|
|
232
241
|
|
|
233
242
|
if selectedCodecName == "H264" {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
frameMode = "nv12"
|
|
247
|
-
} else {
|
|
248
|
-
conversionFailureCount += 1
|
|
243
|
+
switch h264FrameMode() {
|
|
244
|
+
case .bgra:
|
|
245
|
+
usedNativeFrame = useNativePixelBufferFrames ?? false
|
|
246
|
+
if usedNativeFrame {
|
|
247
|
+
frameMode = "bgra-h264"
|
|
248
|
+
} else {
|
|
249
|
+
let convertStartNs = DispatchTime.now().uptimeNanoseconds
|
|
250
|
+
usedFrame = sourceFrame.newI420()
|
|
251
|
+
convertDurationMs = Double(DispatchTime.now().uptimeNanoseconds - convertStartNs) / 1_000_000.0
|
|
252
|
+
frameMode = "i420-fallback"
|
|
253
|
+
}
|
|
254
|
+
case .i420:
|
|
249
255
|
let convertStartNs = DispatchTime.now().uptimeNanoseconds
|
|
250
256
|
usedFrame = sourceFrame.newI420()
|
|
251
257
|
convertDurationMs = Double(DispatchTime.now().uptimeNanoseconds - convertStartNs) / 1_000_000.0
|
|
252
258
|
usedNativeFrame = false
|
|
253
|
-
frameMode = "i420-
|
|
259
|
+
frameMode = "i420-h264"
|
|
260
|
+
case .nv12:
|
|
261
|
+
if Self.isBiPlanar420(pixelFormat) {
|
|
262
|
+
usedNativeFrame = true
|
|
263
|
+
frameMode = "nv12-input"
|
|
264
|
+
} else if let converted = h264PixelBufferConverter.convert(pixelBuffer) {
|
|
265
|
+
convertDurationMs = h264PixelBufferConverter.lastDurationMs
|
|
266
|
+
forwardedPixelFormat = CVPixelBufferGetPixelFormatType(converted)
|
|
267
|
+
usedFrame = LKRTCVideoFrame(
|
|
268
|
+
buffer: LKRTCCVPixelBuffer(pixelBuffer: converted),
|
|
269
|
+
rotation: ._0,
|
|
270
|
+
timeStampNs: timeNs
|
|
271
|
+
)
|
|
272
|
+
usedNativeFrame = true
|
|
273
|
+
frameMode = "nv12"
|
|
274
|
+
} else {
|
|
275
|
+
conversionFailureCount += 1
|
|
276
|
+
let convertStartNs = DispatchTime.now().uptimeNanoseconds
|
|
277
|
+
usedFrame = sourceFrame.newI420()
|
|
278
|
+
convertDurationMs = Double(DispatchTime.now().uptimeNanoseconds - convertStartNs) / 1_000_000.0
|
|
279
|
+
usedNativeFrame = false
|
|
280
|
+
frameMode = "i420-fallback"
|
|
281
|
+
}
|
|
254
282
|
}
|
|
255
283
|
} else if !usedNativeFrame {
|
|
256
284
|
let convertStartNs = DispatchTime.now().uptimeNanoseconds
|
|
@@ -303,13 +331,8 @@ final class WebRTCPublisher {
|
|
|
303
331
|
config.candidateNetworkPolicy = .all
|
|
304
332
|
config.continualGatheringPolicy = .gatherOnce
|
|
305
333
|
config.iceServers = iceServers(from: request.iceServers)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
print("[webrtc] ICE transport policy: relay")
|
|
309
|
-
} else {
|
|
310
|
-
config.iceTransportPolicy = .all
|
|
311
|
-
print("[webrtc] ICE transport policy: all")
|
|
312
|
-
}
|
|
334
|
+
config.iceTransportPolicy = .all
|
|
335
|
+
print("[webrtc] ICE transport policy: all (TURN as fallback)")
|
|
313
336
|
print("[webrtc] ICE servers: \(iceServerSummary(request.iceServers))")
|
|
314
337
|
|
|
315
338
|
let constraints = LKRTCMediaConstraints(
|
|
@@ -692,6 +715,18 @@ final class WebRTCPublisher {
|
|
|
692
715
|
pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
|
|
693
716
|
}
|
|
694
717
|
|
|
718
|
+
private func h264FrameMode() -> H264WebRTCFrameMode {
|
|
719
|
+
if let h264FrameModeOverride {
|
|
720
|
+
return h264FrameModeOverride
|
|
721
|
+
}
|
|
722
|
+
return h264WebRTCSupport.usesHardware == false ? .bgra : .nv12
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private func h264FrameModeDescription() -> String {
|
|
726
|
+
let source = h264FrameModeOverride == nil ? "auto" : "env"
|
|
727
|
+
return "\(h264FrameMode().rawValue)(\(source))"
|
|
728
|
+
}
|
|
729
|
+
|
|
695
730
|
private static func detectH264WebRTCSupport() -> WebRTCH264Support {
|
|
696
731
|
let environment = ProcessInfo.processInfo.environment
|
|
697
732
|
if envFlagEnabled(environment["SERVE_SIM_DISABLE_WEBRTC_H264"]) {
|
|
@@ -869,6 +904,23 @@ final class WebRTCPublisher {
|
|
|
869
904
|
return pixelBuffer
|
|
870
905
|
}
|
|
871
906
|
|
|
907
|
+
private static func h264FrameModeOverride() -> H264WebRTCFrameMode? {
|
|
908
|
+
guard let raw = ProcessInfo.processInfo.environment["SERVE_SIM_WEBRTC_H264_FRAME_MODE"]?.lowercased() else {
|
|
909
|
+
return nil
|
|
910
|
+
}
|
|
911
|
+
switch raw {
|
|
912
|
+
case "bgra", "native-bgra":
|
|
913
|
+
return .bgra
|
|
914
|
+
case "i420":
|
|
915
|
+
return .i420
|
|
916
|
+
case "nv12", "native", "cvpixelbuffer":
|
|
917
|
+
return .nv12
|
|
918
|
+
default:
|
|
919
|
+
print("[webrtc] Ignoring invalid SERVE_SIM_WEBRTC_H264_FRAME_MODE=\(raw); expected bgra, i420, or nv12")
|
|
920
|
+
return nil
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
872
924
|
private static func vtSessionStringProperty(_ session: VTCompressionSession, key: CFString) -> String? {
|
|
873
925
|
var value: CFTypeRef?
|
|
874
926
|
let status = withUnsafeMutablePointer(to: &value) { pointer in
|
|
@@ -936,6 +988,260 @@ private struct H264VideoToolboxProbe {
|
|
|
936
988
|
let summary: String
|
|
937
989
|
}
|
|
938
990
|
|
|
991
|
+
private enum H264WebRTCFrameMode: String {
|
|
992
|
+
case bgra
|
|
993
|
+
case i420
|
|
994
|
+
case nv12
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
private final class ServeSimSoftwareH264EncoderFactory: NSObject, LKRTCVideoEncoderFactory {
|
|
998
|
+
private let fallback: LKRTCVideoEncoderFactory
|
|
999
|
+
|
|
1000
|
+
init(fallback: LKRTCVideoEncoderFactory) {
|
|
1001
|
+
self.fallback = fallback
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
func createEncoder(_ info: LKRTCVideoCodecInfo) -> (any LKRTCVideoEncoder)? {
|
|
1005
|
+
if info.name.caseInsensitiveCompare("H264") == .orderedSame {
|
|
1006
|
+
return ServeSimSoftwareH264Encoder(codecInfo: info)
|
|
1007
|
+
}
|
|
1008
|
+
return fallback.createEncoder(info)
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
func supportedCodecs() -> [LKRTCVideoCodecInfo] {
|
|
1012
|
+
fallback.supportedCodecs()
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
func implementations() -> [LKRTCVideoCodecInfo] {
|
|
1016
|
+
fallback.implementations?() ?? supportedCodecs()
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
func encoderSelector() -> (any LKRTCVideoEncoderSelector)? {
|
|
1020
|
+
fallback.encoderSelector?()
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
func queryCodecSupport(
|
|
1024
|
+
_ info: LKRTCVideoCodecInfo,
|
|
1025
|
+
scalabilityMode: String?
|
|
1026
|
+
) -> LKRTCVideoEncoderCodecSupport {
|
|
1027
|
+
if let queryCodecSupport = fallback.queryCodecSupport {
|
|
1028
|
+
return queryCodecSupport(info, scalabilityMode)
|
|
1029
|
+
}
|
|
1030
|
+
return LKRTCVideoEncoderCodecSupport(supported: true)
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
private final class ServeSimSoftwareH264Encoder: NSObject, LKRTCVideoEncoder {
|
|
1035
|
+
private let encoder = H264Encoder()
|
|
1036
|
+
private var callback: RTCVideoEncoderCallback?
|
|
1037
|
+
private var parameterSets = Data()
|
|
1038
|
+
private var width: Int32 = 0
|
|
1039
|
+
private var height: Int32 = 0
|
|
1040
|
+
private var fps: UInt32 = 30
|
|
1041
|
+
private var bitrateKbit: UInt32 = 3_000
|
|
1042
|
+
private var encodedCount: Int64 = 0
|
|
1043
|
+
private let stateQueue = DispatchQueue(label: "serve-sim.webrtc.h264.encoder")
|
|
1044
|
+
|
|
1045
|
+
init(codecInfo: LKRTCVideoCodecInfo) {
|
|
1046
|
+
super.init()
|
|
1047
|
+
streamLog("[webrtc:h264] using serve-sim software VideoToolbox encoder parameters=\(codecInfo.parameters)")
|
|
1048
|
+
encoder.onEncoded = { [weak self] encoded in
|
|
1049
|
+
self?.emit(encoded)
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
func setCallback(_ callback: RTCVideoEncoderCallback?) {
|
|
1054
|
+
stateQueue.sync {
|
|
1055
|
+
self.callback = callback
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
func startEncode(
|
|
1060
|
+
with settings: LKRTCVideoEncoderSettings,
|
|
1061
|
+
numberOfCores: Int32
|
|
1062
|
+
) -> Int {
|
|
1063
|
+
stateQueue.sync {
|
|
1064
|
+
width = Int32(settings.width)
|
|
1065
|
+
height = Int32(settings.height)
|
|
1066
|
+
fps = max(1, settings.maxFramerate)
|
|
1067
|
+
bitrateKbit = max(1, settings.startBitrate)
|
|
1068
|
+
parameterSets.removeAll(keepingCapacity: true)
|
|
1069
|
+
encodedCount = 0
|
|
1070
|
+
}
|
|
1071
|
+
encoder.update(fps: Int(max(1, settings.maxFramerate)), bitrate: Int(max(1, settings.startBitrate)) * 1000)
|
|
1072
|
+
streamLog(
|
|
1073
|
+
"[webrtc:h264] start width=\(settings.width) height=\(settings.height) " +
|
|
1074
|
+
"fps=\(settings.maxFramerate) bitrateKbit=\(settings.startBitrate) cores=\(numberOfCores)"
|
|
1075
|
+
)
|
|
1076
|
+
return 0
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
func release() -> Int {
|
|
1080
|
+
encoder.stop()
|
|
1081
|
+
stateQueue.sync {
|
|
1082
|
+
callback = nil
|
|
1083
|
+
parameterSets.removeAll(keepingCapacity: false)
|
|
1084
|
+
encodedCount = 0
|
|
1085
|
+
}
|
|
1086
|
+
streamLog("[webrtc:h264] release")
|
|
1087
|
+
return 0
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
func encode(
|
|
1091
|
+
_ frame: LKRTCVideoFrame,
|
|
1092
|
+
codecSpecificInfo info: (any LKRTCCodecSpecificInfo)?,
|
|
1093
|
+
frameTypes: [NSNumber]
|
|
1094
|
+
) -> Int {
|
|
1095
|
+
guard let pixelBuffer = pixelBuffer(from: frame) else {
|
|
1096
|
+
streamLog("[webrtc:h264] drop frame: unsupported frame buffer \(type(of: frame.buffer))")
|
|
1097
|
+
return -1
|
|
1098
|
+
}
|
|
1099
|
+
let forceKeyframe = frameTypes.contains { $0.intValue == LKRTCFrameType.videoFrameKey.rawValue }
|
|
1100
|
+
stateQueue.sync {
|
|
1101
|
+
width = Int32(frame.width)
|
|
1102
|
+
height = Int32(frame.height)
|
|
1103
|
+
}
|
|
1104
|
+
encoder.encode(pixelBuffer, forceKeyframe: forceKeyframe)
|
|
1105
|
+
return 0
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
func setBitrate(_ bitrateKbit: UInt32, framerate: UInt32) -> Int32 {
|
|
1109
|
+
let normalizedBitrateKbit = max(1, bitrateKbit)
|
|
1110
|
+
let normalizedFps = max(1, framerate)
|
|
1111
|
+
let shouldUpdateEncoder = stateQueue.sync { () -> Bool in
|
|
1112
|
+
let previousBitrateKbit = self.bitrateKbit
|
|
1113
|
+
self.bitrateKbit = normalizedBitrateKbit
|
|
1114
|
+
fps = normalizedFps
|
|
1115
|
+
let delta = abs(Int64(normalizedBitrateKbit) - Int64(previousBitrateKbit))
|
|
1116
|
+
return delta * 100 >= Int64(max(1, previousBitrateKbit)) * 25
|
|
1117
|
+
}
|
|
1118
|
+
if shouldUpdateEncoder {
|
|
1119
|
+
encoder.update(fps: Int(normalizedFps), bitrate: Int(normalizedBitrateKbit) * 1000)
|
|
1120
|
+
streamLog("[webrtc:h264] bitrate bitrateKbit=\(normalizedBitrateKbit) fps=\(normalizedFps)")
|
|
1121
|
+
}
|
|
1122
|
+
return 0
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
func implementationName() -> String {
|
|
1126
|
+
"serve-sim-videotoolbox-h264"
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
func scalingSettings() -> LKRTCVideoEncoderQpThresholds? {
|
|
1130
|
+
nil
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
var resolutionAlignment: Int {
|
|
1134
|
+
2
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
var applyAlignmentToAllSimulcastLayers: Bool {
|
|
1138
|
+
true
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
var supportsNativeHandle: Bool {
|
|
1142
|
+
true
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
private func pixelBuffer(from frame: LKRTCVideoFrame) -> CVPixelBuffer? {
|
|
1146
|
+
if let buffer = frame.buffer as? LKRTCCVPixelBuffer {
|
|
1147
|
+
return buffer.pixelBuffer
|
|
1148
|
+
}
|
|
1149
|
+
streamLog("[webrtc:h264] frame buffer is not CVPixelBuffer-backed: \(type(of: frame.buffer))")
|
|
1150
|
+
return nil
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
private func emit(_ encoded: H264Encoder.Encoded) {
|
|
1154
|
+
let imageAndInfo: (LKRTCEncodedImage, LKRTCCodecSpecificInfoH264, Int64)? = stateQueue.sync {
|
|
1155
|
+
if let description = encoded.description {
|
|
1156
|
+
parameterSets = annexBParameterSets(fromAvcC: description)
|
|
1157
|
+
}
|
|
1158
|
+
var annexB = Data()
|
|
1159
|
+
if encoded.kind == .keyframe {
|
|
1160
|
+
annexB.append(parameterSets)
|
|
1161
|
+
}
|
|
1162
|
+
appendAnnexBNals(fromAvcc: encoded.avcc, to: &annexB)
|
|
1163
|
+
guard !annexB.isEmpty else { return nil }
|
|
1164
|
+
|
|
1165
|
+
encodedCount += 1
|
|
1166
|
+
let image = LKRTCEncodedImage()
|
|
1167
|
+
image.buffer = annexB
|
|
1168
|
+
image.encodedWidth = width
|
|
1169
|
+
image.encodedHeight = height
|
|
1170
|
+
image.timeStamp = UInt32(truncatingIfNeeded: encodedCount * 90_000 / Int64(max(1, fps)))
|
|
1171
|
+
image.captureTimeMs = Int64(DispatchTime.now().uptimeNanoseconds / 1_000_000)
|
|
1172
|
+
image.ntpTimeMs = image.captureTimeMs
|
|
1173
|
+
image.frameType = encoded.kind == .keyframe ? .videoFrameKey : .videoFrameDelta
|
|
1174
|
+
image.rotation = ._0
|
|
1175
|
+
image.contentType = .screenshare
|
|
1176
|
+
|
|
1177
|
+
let h264Info = LKRTCCodecSpecificInfoH264()
|
|
1178
|
+
h264Info.packetizationMode = .nonInterleaved
|
|
1179
|
+
return (image, h264Info, encodedCount)
|
|
1180
|
+
}
|
|
1181
|
+
guard let (image, h264Info, nextCount) = imageAndInfo else { return }
|
|
1182
|
+
let callback = stateQueue.sync { self.callback }
|
|
1183
|
+
guard let callback else { return }
|
|
1184
|
+
_ = callback(image, h264Info)
|
|
1185
|
+
if streamShouldLog(nextCount) || image.frameType == .videoFrameKey {
|
|
1186
|
+
let kind = image.frameType == .videoFrameKey ? "keyframe" : "delta"
|
|
1187
|
+
streamLog("[webrtc:h264] emitted \(kind) #\(nextCount) bytes=\(image.buffer.count)")
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
private func annexBParameterSets(fromAvcC avcC: Data) -> Data {
|
|
1192
|
+
guard avcC.count >= 7 else { return Data() }
|
|
1193
|
+
var offset = 5
|
|
1194
|
+
let spsCount = Int(avcC[offset] & 0x1f)
|
|
1195
|
+
offset += 1
|
|
1196
|
+
var out = Data()
|
|
1197
|
+
for _ in 0..<spsCount {
|
|
1198
|
+
guard offset + 2 <= avcC.count else { return out }
|
|
1199
|
+
let length = (Int(avcC[offset]) << 8) | Int(avcC[offset + 1])
|
|
1200
|
+
offset += 2
|
|
1201
|
+
guard offset + length <= avcC.count else { return out }
|
|
1202
|
+
appendAnnexBStartCode(to: &out)
|
|
1203
|
+
out.append(avcC.subdata(in: offset..<(offset + length)))
|
|
1204
|
+
offset += length
|
|
1205
|
+
}
|
|
1206
|
+
guard offset < avcC.count else { return out }
|
|
1207
|
+
let ppsCount = Int(avcC[offset])
|
|
1208
|
+
offset += 1
|
|
1209
|
+
for _ in 0..<ppsCount {
|
|
1210
|
+
guard offset + 2 <= avcC.count else { return out }
|
|
1211
|
+
let length = (Int(avcC[offset]) << 8) | Int(avcC[offset + 1])
|
|
1212
|
+
offset += 2
|
|
1213
|
+
guard offset + length <= avcC.count else { return out }
|
|
1214
|
+
appendAnnexBStartCode(to: &out)
|
|
1215
|
+
out.append(avcC.subdata(in: offset..<(offset + length)))
|
|
1216
|
+
offset += length
|
|
1217
|
+
}
|
|
1218
|
+
return out
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
private func appendAnnexBNals(fromAvcc avcc: Data, to out: inout Data) {
|
|
1222
|
+
var offset = 0
|
|
1223
|
+
while offset + 4 <= avcc.count {
|
|
1224
|
+
let length =
|
|
1225
|
+
(Int(avcc[offset]) << 24) |
|
|
1226
|
+
(Int(avcc[offset + 1]) << 16) |
|
|
1227
|
+
(Int(avcc[offset + 2]) << 8) |
|
|
1228
|
+
Int(avcc[offset + 3])
|
|
1229
|
+
offset += 4
|
|
1230
|
+
guard length > 0, offset + length <= avcc.count else { return }
|
|
1231
|
+
appendAnnexBStartCode(to: &out)
|
|
1232
|
+
out.append(avcc.subdata(in: offset..<(offset + length)))
|
|
1233
|
+
offset += length
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
private func appendAnnexBStartCode(to out: inout Data) {
|
|
1238
|
+
out.append(0)
|
|
1239
|
+
out.append(0)
|
|
1240
|
+
out.append(0)
|
|
1241
|
+
out.append(1)
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
939
1245
|
private final class H264WebRTCPixelBufferConverter {
|
|
940
1246
|
private var pool: CVPixelBufferPool?
|
|
941
1247
|
private var width = 0
|