mirror of
https://github.com/HaishinKit/HaishinKit.swift.git
synced 2026-05-07 20:12:28 +00:00
230 lines
9.4 KiB
Swift
230 lines
9.4 KiB
Swift
import Foundation
|
|
import VideoToolbox
|
|
|
|
/// Constraints on the video codec compression settings.
|
|
public struct VideoCodecSettings: Codable, Sendable {
|
|
/// The number of frame rate for 30fps.
|
|
public static let frameInterval30 = (1 / 30) - 0.001
|
|
/// The number of frame rate for 10fps.
|
|
public static let frameInterval10 = (1 / 10) - 0.001
|
|
/// The number of frame rate for 5fps.
|
|
public static let frameInterval05 = (1 / 05) - 0.001
|
|
/// The number of frame rate for 1fps.
|
|
public static let frameInterval01 = (1 / 01) - 0.001
|
|
|
|
/// The defulat value.
|
|
public static let `default` = VideoCodecSettings()
|
|
|
|
/// A bitRate mode that affectes how to encode the video source.
|
|
public struct BitRateMode: Sendable, CustomStringConvertible, Codable, Hashable, Equatable {
|
|
public static func == (lhs: VideoCodecSettings.BitRateMode, rhs: VideoCodecSettings.BitRateMode) -> Bool {
|
|
lhs.key == rhs.key
|
|
}
|
|
|
|
/// The average bit rate.
|
|
public static let average = BitRateMode(key: .averageBitRate)
|
|
|
|
/// The constant bit rate.
|
|
@available(iOS 16.0, tvOS 16.0, macOS 13.0, *)
|
|
public static let constant = BitRateMode(key: .constantBitRate)
|
|
|
|
/// The variable bit rate.
|
|
/// - seealso: [kVTCompressionPropertyKey_VariableBitRate](https://developer.apple.com/documentation/videotoolbox/kvtcompressionpropertykey_variablebitrate)
|
|
@available(iOS 26.0, tvOS 26.0, macOS 26.0, *)
|
|
public static let variable = BitRateMode(key: .variableBitRate)
|
|
|
|
let key: VTSessionOptionKey
|
|
|
|
public var description: String {
|
|
key.CFString as String
|
|
}
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
return hasher.combine(description)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The scaling mode.
|
|
* - seealso: https://developer.apple.com/documentation/videotoolbox/kvtpixeltransferpropertykey_scalingmode
|
|
* - seealso: https://developer.apple.com/documentation/videotoolbox/vtpixeltransfersession/pixel_transfer_properties/scaling_mode_constants
|
|
*/
|
|
public enum ScalingMode: String, Codable, Sendable {
|
|
/// kVTScalingMode_Normal
|
|
case normal = "Normal"
|
|
/// kVTScalingMode_Letterbox
|
|
case letterbox = "Letterbox"
|
|
/// kVTScalingMode_CropSourceToCleanAperture
|
|
case cropSourceToCleanAperture = "CropSourceToCleanAperture"
|
|
/// kVTScalingMode_Trim
|
|
case trim = "Trim"
|
|
}
|
|
|
|
/// The type of the VideoCodec supports format.
|
|
package enum Format: Codable, Sendable, CaseIterable {
|
|
case h264
|
|
case hevc
|
|
|
|
#if os(macOS)
|
|
var encoderID: NSString {
|
|
switch self {
|
|
case .h264:
|
|
#if arch(arm64)
|
|
return NSString(string: "com.apple.videotoolbox.videoencoder.ave.avc")
|
|
#else
|
|
return NSString(string: "com.apple.videotoolbox.videoencoder.h264.gva")
|
|
#endif
|
|
case .hevc:
|
|
return NSString(string: "com.apple.videotoolbox.videoencoder.ave.hevc")
|
|
}
|
|
}
|
|
#endif
|
|
|
|
var codecType: UInt32 {
|
|
switch self {
|
|
case .h264:
|
|
return kCMVideoCodecType_H264
|
|
case .hevc:
|
|
return kCMVideoCodecType_HEVC
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Specifies the video size of encoding video.
|
|
public var videoSize: CGSize
|
|
/// Specifies the bitrate.
|
|
public var bitRate: Int
|
|
/// Specifies the H264 profileLevel.
|
|
public var profileLevel: String {
|
|
didSet {
|
|
if profileLevel.contains("HEVC") {
|
|
format = .hevc
|
|
} else {
|
|
format = .h264
|
|
}
|
|
}
|
|
}
|
|
/// Specifies the scalingMode.
|
|
public var scalingMode: ScalingMode
|
|
/// Specifies the bitRateMode.
|
|
public var bitRateMode: BitRateMode
|
|
/// Specifies the keyframeInterval.
|
|
public var maxKeyFrameIntervalDuration: Int32
|
|
/// Specifies the allowFrameRecording.
|
|
public var allowFrameReordering: Bool? // swiftlint:disable:this discouraged_optional_boolean
|
|
/// Specifies the dataRateLimits
|
|
public var dataRateLimits: [Double]?
|
|
/// Specifies the low-latency opretaion for an encoder.
|
|
public var isLowLatencyRateControlEnabled: Bool
|
|
/// Specifies the hardware accelerated encoder is enabled(TRUE), or not(FALSE) for macOS.
|
|
public var isHardwareAcceleratedEnabled: Bool
|
|
/// Specifies the video frame interval.
|
|
public var frameInterval: Double = 0.0
|
|
/// Specifies the expected frame rate for an encoder. It may optimize power consumption.
|
|
public var expectedFrameRate: Double?
|
|
|
|
package var format: Format = .h264
|
|
|
|
/// Creates a new VideoCodecSettings instance.
|
|
public init(
|
|
videoSize: CGSize = .init(width: 854, height: 480),
|
|
bitRate: Int = 640 * 1000,
|
|
profileLevel: String = kVTProfileLevel_H264_Baseline_3_1 as String,
|
|
scalingMode: ScalingMode = .trim,
|
|
bitRateMode: BitRateMode = .average,
|
|
maxKeyFrameIntervalDuration: Int32 = 2,
|
|
// swiftlint:disable discouraged_optional_boolean
|
|
allowFrameReordering: Bool? = nil,
|
|
// swiftlint:enable discouraged_optional_boolean
|
|
dataRateLimits: [Double]? = [0.0, 0.0],
|
|
isLowLatencyRateControlEnabled: Bool = false,
|
|
isHardwareAcceleratedEnabled: Bool = true,
|
|
expectedFrameRate: Double? = nil
|
|
) {
|
|
self.videoSize = videoSize
|
|
self.bitRate = bitRate
|
|
self.profileLevel = profileLevel
|
|
self.scalingMode = scalingMode
|
|
self.bitRateMode = bitRateMode
|
|
self.maxKeyFrameIntervalDuration = maxKeyFrameIntervalDuration
|
|
self.allowFrameReordering = allowFrameReordering
|
|
self.dataRateLimits = dataRateLimits
|
|
self.isLowLatencyRateControlEnabled = isLowLatencyRateControlEnabled
|
|
self.isHardwareAcceleratedEnabled = isHardwareAcceleratedEnabled
|
|
self.expectedFrameRate = expectedFrameRate
|
|
if profileLevel.contains("HEVC") {
|
|
self.format = .hevc
|
|
}
|
|
}
|
|
|
|
func invalidateSession(_ rhs: VideoCodecSettings) -> Bool {
|
|
return !(videoSize == rhs.videoSize &&
|
|
maxKeyFrameIntervalDuration == rhs.maxKeyFrameIntervalDuration &&
|
|
scalingMode == rhs.scalingMode &&
|
|
allowFrameReordering == rhs.allowFrameReordering &&
|
|
bitRateMode == rhs.bitRateMode &&
|
|
profileLevel == rhs.profileLevel &&
|
|
dataRateLimits == rhs.dataRateLimits &&
|
|
isLowLatencyRateControlEnabled == rhs.isLowLatencyRateControlEnabled &&
|
|
isHardwareAcceleratedEnabled == rhs.isHardwareAcceleratedEnabled
|
|
)
|
|
}
|
|
|
|
func apply(_ codec: VideoCodec, rhs: VideoCodecSettings) {
|
|
if bitRate != rhs.bitRate {
|
|
logger.info("bitRate change from ", rhs.bitRate, " to ", bitRate)
|
|
let option = VTSessionOption(key: bitRateMode.key, value: NSNumber(value: bitRate))
|
|
_ = codec.session?.setOption(option)
|
|
}
|
|
if frameInterval != rhs.frameInterval {
|
|
codec.frameInterval = frameInterval
|
|
}
|
|
if expectedFrameRate != rhs.expectedFrameRate {
|
|
let value = if let expectedFrameRate { expectedFrameRate } else { 0.0 }
|
|
let option = VTSessionOption(key: .expectedFrameRate, value: value as CFNumber)
|
|
_ = codec.session?.setOption(option)
|
|
}
|
|
}
|
|
|
|
// https://developer.apple.com/documentation/videotoolbox/encoding_video_for_live_streaming
|
|
func makeOptions() -> Set<VTSessionOption> {
|
|
let isBaseline = profileLevel.contains("Baseline")
|
|
var options = Set<VTSessionOption>([
|
|
.init(key: .realTime, value: kCFBooleanTrue),
|
|
.init(key: .profileLevel, value: profileLevel as NSObject),
|
|
.init(key: bitRateMode.key, value: NSNumber(value: bitRate)),
|
|
.init(key: .maxKeyFrameIntervalDuration, value: NSNumber(value: maxKeyFrameIntervalDuration)),
|
|
.init(key: .allowFrameReordering, value: (allowFrameReordering ?? !isBaseline) as NSObject),
|
|
.init(key: .pixelTransferProperties, value: [
|
|
"ScalingMode": scalingMode.rawValue
|
|
] as NSObject)
|
|
])
|
|
if bitRateMode == .average {
|
|
if let dataRateLimits, dataRateLimits.count == 2 {
|
|
var limits = [Double](repeating: 0.0, count: 2)
|
|
limits[0] = dataRateLimits[0] == 0 ? Double(bitRate) / 8 * 1.5 : dataRateLimits[0]
|
|
limits[1] = dataRateLimits[1] == 0 ? Double(1.0) : dataRateLimits[1]
|
|
options.insert(.init(key: .dataRateLimits, value: limits as NSArray))
|
|
}
|
|
}
|
|
#if os(macOS)
|
|
if isHardwareAcceleratedEnabled {
|
|
options.insert(.init(key: .encoderID, value: format.encoderID))
|
|
options.insert(.init(key: .enableHardwareAcceleratedVideoEncoder, value: kCFBooleanTrue))
|
|
options.insert(.init(key: .requireHardwareAcceleratedVideoEncoder, value: kCFBooleanTrue))
|
|
}
|
|
#endif
|
|
if !isBaseline && profileLevel.contains("H264") {
|
|
options.insert(.init(key: .H264EntropyMode, value: kVTH264EntropyMode_CABAC))
|
|
}
|
|
return options
|
|
}
|
|
|
|
func makeEncoderSpecification() -> CFDictionary? {
|
|
if isLowLatencyRateControlEnabled {
|
|
return [kVTVideoEncoderSpecification_EnableLowLatencyRateControl: true as CFBoolean] as CFDictionary
|
|
}
|
|
return nil
|
|
}
|
|
}
|