From 11b7c796f8520595ec84bf70e26acaff9c04b9f6 Mon Sep 17 00:00:00 2001
From: ThibaultBee <37510686+ThibaultBee@users.noreply.github.com>
Date: Fri, 9 Feb 2024 16:15:38 +0100
Subject: [PATCH] feat(*): make resolution clearer
---
.../ExampleUIkit/Settings.bundle/Root.plist | 12 +-
.../ExampleUIkit/SettingsManager.swift | 15 ++-
.../ApiVideoLiveStream.swift | 42 +++---
.../models/Configuration.swift | 62 ++++++---
.../models/Resolution.swift | 125 ++++++++++++------
5 files changed, 163 insertions(+), 93 deletions(-)
diff --git a/Examples/ExampleUIKit/ExampleUIkit/Settings.bundle/Root.plist b/Examples/ExampleUIKit/ExampleUIkit/Settings.bundle/Root.plist
index 437d731..230a07e 100644
--- a/Examples/ExampleUIKit/ExampleUIkit/Settings.bundle/Root.plist
+++ b/Examples/ExampleUIKit/ExampleUIkit/Settings.bundle/Root.plist
@@ -48,24 +48,22 @@
Key
Resolution
DefaultValue
- 858x480
+ 854x480
Values
- 352x240
+ 426x240
480x360
- 858x480
+ 854x480
1280x720
1920x1080
- 3840x2160
Titles
- 352x240
+ 426x240
480x360
- 858x480
+ 854x480
1280x720
1920x1080
- 3840x2160
diff --git a/Examples/ExampleUIKit/ExampleUIkit/SettingsManager.swift b/Examples/ExampleUIKit/ExampleUIkit/SettingsManager.swift
index 0c8a38c..d607d77 100644
--- a/Examples/ExampleUIKit/ExampleUIkit/SettingsManager.swift
+++ b/Examples/ExampleUIKit/ExampleUIkit/SettingsManager.swift
@@ -20,10 +20,10 @@ enum SettingsManager {
private static var resolution: Resolution {
do {
- return try UserDefaults.standard.string(forKey: "Resolution")?.toResolution() ?? Resolution.RESOLUTION_720
+ return try UserDefaults.standard.string(forKey: "Resolution")?.toResolution() ?? Resolution
+ .RESOLUTION_16_9_720P
} catch {
- print("Can't get resolution from user defaults")
- return Resolution.RESOLUTION_720
+ fatalError("Can't get resolution from user defaults")
}
}
@@ -57,13 +57,18 @@ extension String {
guard let height = Int(resolutionArray[1]) else {
throw ParameterError.Invalid("Height is invalid")
}
- return try Resolution.getResolution(width: width, height: height)
+ let resolution = Resolution(rawValue: CGSize(width: width, height: height))
+ if let resolution {
+ return resolution
+ } else {
+ throw ParameterError.Invalid("Resolution is invalid for \(width)x\(height)")
+ }
}
}
extension Resolution {
func toString() -> String {
- "\(size.width)x\(size.height)"
+ "\(rawValue.width)x\(rawValue.height)"
}
}
diff --git a/Sources/ApiVideoLiveStream/ApiVideoLiveStream.swift b/Sources/ApiVideoLiveStream/ApiVideoLiveStream.swift
index 05403cb..884100b 100644
--- a/Sources/ApiVideoLiveStream/ApiVideoLiveStream.swift
+++ b/Sources/ApiVideoLiveStream/ApiVideoLiveStream.swift
@@ -23,7 +23,6 @@ public class ApiVideoLiveStream {
/// The delegate of the ApiVideoLiveStream
public weak var delegate: ApiVideoLiveStreamDelegate?
- // swiftlint:disable force_cast
/// Getter and Setter for an AudioConfig
public var audioConfig: AudioConfig {
get {
@@ -34,13 +33,12 @@ public class ApiVideoLiveStream {
}
}
- // swiftlint:disable force_cast force_try
/// Getter and Setter for a VideoConfig
public var videoConfig: VideoConfig {
get {
- try! VideoConfig(
+ VideoConfig(
bitrate: Int(self.rtmpStream.videoSettings.bitRate),
- resolution: Resolution.getResolution(
+ resolution: CGSize(
width: Int(self.rtmpStream.videoSettings.videoSize.width),
height: Int(self.rtmpStream.videoSettings.videoSize.height)
),
@@ -53,7 +51,6 @@ public class ApiVideoLiveStream {
}
}
- // swiftlint:disable force_cast
/// Getter and Setter for the Bitrate number for the video
public var videoBitrate: Int {
get {
@@ -100,7 +97,6 @@ public class ApiVideoLiveStream {
}
#if os(iOS)
- // swiftlint:disable implicit_return
/// Zoom on the video capture
public var zoomRatio: CGFloat {
get {
@@ -322,10 +318,10 @@ public class ApiVideoLiveStream {
self.rtmpStream.frameRate = videoConfig.fps
self.rtmpStream.sessionPreset = AVCaptureSession.Preset.high
- let width = self.rtmpStream.videoOrientation.isLandscape ? videoConfig.resolution.size.width : videoConfig
- .resolution.size.height
- let height = self.rtmpStream.videoOrientation.isLandscape ? videoConfig.resolution.size.height : videoConfig
- .resolution.size.width
+ let width = self.rtmpStream.videoOrientation.isLandscape ? videoConfig.resolution.width : videoConfig
+ .resolution.height
+ let height = self.rtmpStream.videoOrientation.isLandscape ? videoConfig.resolution.height : videoConfig
+ .resolution.width
self.rtmpStream.videoSettings = VideoCodecSettings(
videoSize: CGSize(width: width, height: height),
@@ -444,22 +440,16 @@ public class ApiVideoLiveStream {
self.rtmpStream.lockQueue.async {
self.rtmpStream.videoOrientation = orientation
- do {
- let resolution = try Resolution.getResolution(
- width: Int(self.rtmpStream.videoSettings.videoSize.width),
- height: Int(self.rtmpStream.videoSettings.videoSize.height)
- )
- self.rtmpStream.videoSettings.videoSize = CGSize(
- width:
- self.rtmpStream.videoOrientation.isLandscape ?
- resolution.size.width : resolution.size.height,
- height:
- self.rtmpStream.videoOrientation.isLandscape ?
- resolution.size.height : resolution.size.width
- )
- } catch {
- print("Failed to set resolution to orientation \(orientation)")
- }
+
+ let videoSize = self.rtmpStream.videoSettings.videoSize
+ self.rtmpStream.videoSettings.videoSize = CGSize(
+ width:
+ self.rtmpStream.videoOrientation.isLandscape ?
+ videoSize.width : videoSize.height,
+ height:
+ self.rtmpStream.videoOrientation.isLandscape ?
+ videoSize.height : videoSize.width
+ )
}
}
#endif
diff --git a/Sources/ApiVideoLiveStream/models/Configuration.swift b/Sources/ApiVideoLiveStream/models/Configuration.swift
index e78526e..03300bf 100644
--- a/Sources/ApiVideoLiveStream/models/Configuration.swift
+++ b/Sources/ApiVideoLiveStream/models/Configuration.swift
@@ -1,3 +1,4 @@
+import CoreGraphics
import Foundation
import Network
@@ -14,51 +15,80 @@ public struct AudioConfig {
public struct VideoConfig {
public let bitrate: Int
- public let resolution: Resolution
+ public let resolution: CGSize
public let fps: Float64
public let gopDuration: TimeInterval
- /// Creates a video configuration object with default video bitrate
+ /// Creates a video configuration object with explicit video bitrate and CGSize resolution
/// - Parameters:
+ /// - bitrate: The video bitrate in bits per second
/// - resolution: The video resolution
/// - fps: The video framerate
/// - gopDuration: The time interval between two key frames
public init(
- resolution: Resolution = Resolution.RESOLUTION_720,
+ bitrate: Int,
+ resolution: CGSize = CGSize(width: 1_280, height: 720),
fps: Float64 = 30,
gopDuration: TimeInterval = 1.0
) {
- self.bitrate = VideoConfig.getDefaultBitrate(resolution: resolution)
+ self.bitrate = bitrate
self.resolution = resolution
self.fps = fps
self.gopDuration = gopDuration
}
- /// Creates a video configuration object with explicit video bitrate
+ /// Creates a video configuration object with default video bitrate and CGSize resolution
/// - Parameters:
- /// - bitrate: The video bitrate in bits per second
/// - resolution: The video resolution
/// - fps: The video framerate
/// - gopDuration: The time interval between two key frames
public init(
- bitrate: Int,
- resolution: Resolution = Resolution.RESOLUTION_720,
+ resolution: CGSize = CGSize(width: 1_280, height: 720),
fps: Float64 = 30,
gopDuration: TimeInterval = 1.0
) {
- self.bitrate = bitrate
+ self.bitrate = VideoConfig.getDefaultBitrate(resolution)
self.resolution = resolution
self.fps = fps
self.gopDuration = gopDuration
}
- static func getDefaultBitrate(resolution: Resolution) -> Int {
- switch resolution {
- case Resolution.RESOLUTION_240: return 800_000
- case Resolution.RESOLUTION_360: return 1_000_000
- case Resolution.RESOLUTION_480: return 1_300_000
- case Resolution.RESOLUTION_720: return 2_000_000
- case Resolution.RESOLUTION_1080: return 3_500_000
+ /// Creates a video configuration object with default video bitrate
+ /// - Parameters:
+ /// - resolution: The video resolution
+ /// - fps: The video framerate
+ /// - gopDuration: The time interval between two key frames
+ public init(
+ resolution: Resolution,
+ fps: Float64 = 30,
+ gopDuration: TimeInterval = 1.0
+ ) {
+ self.init(resolution: resolution.rawValue, fps: fps, gopDuration: gopDuration)
+ }
+
+ /// Creates a video configuration object with explicit video bitrate
+ /// - Parameters:
+ /// - bitrate: The video bitrate in bits per second
+ /// - resolution: The video resolution
+ /// - fps: The video framerate
+ /// - gopDuration: The time interval between two key frames
+ public init(
+ bitrate: Int,
+ resolution: Resolution,
+ fps: Float64 = 30,
+ gopDuration: TimeInterval = 1.0
+ ) {
+ self.init(bitrate: bitrate, resolution: resolution.rawValue, fps: fps, gopDuration: gopDuration)
+ }
+
+ static func getDefaultBitrate(_ size: CGSize) -> Int {
+ let numOfPixels = size.width * size.height
+ switch numOfPixels {
+ case 0 ... 102_240: return 800_000 // for 4/3 and 16/9 240p
+ case 102_241 ... 230_400: return 1_000_000 // for 16/9 360p
+ case 230_401 ... 409_920: return 1_300_000 // for 4/3 and 16/9 480p
+ case 409_921 ... 921_600: return 2_000_000 // for 4/3 600p, 4/3 768p and 16/9 720p
+ default: return 3_000_000 // for 16/9 1080p
}
}
}
diff --git a/Sources/ApiVideoLiveStream/models/Resolution.swift b/Sources/ApiVideoLiveStream/models/Resolution.swift
index efc8cb3..a1f3c60 100644
--- a/Sources/ApiVideoLiveStream/models/Resolution.swift
+++ b/Sources/ApiVideoLiveStream/models/Resolution.swift
@@ -1,60 +1,107 @@
import Foundation
+/// Resolution of the video
public enum Resolution {
- case RESOLUTION_240
- case RESOLUTION_360
- case RESOLUTION_480
- case RESOLUTION_720
- case RESOLUTION_1080
+ // 16:9
+ /// 426x240
+ case RESOLUTION_16_9_240P
+ /// 640x360: nHD
+ case RESOLUTION_16_9_360P
+ /// 854x480: FWVGA
+ case RESOLUTION_16_9_480P
+ /// 1280x720: WXGA
+ case RESOLUTION_16_9_720P
+ /// 1920x1080: FHD
+ case RESOLUTION_16_9_1080P
- public var size: CGSize {
- switch self {
- case .RESOLUTION_240:
- return CGSize(width: 352, height: 240)
-
- case .RESOLUTION_360:
- return CGSize(width: 480, height: 360)
+ // 4:3
+ /// 320x240: QVGA
+ case RESOLUTION_4_3_240P
+ /// 640x480: VGA
+ case RESOLUTION_4_3_480P
+ /// 800x600: SVGA
+ case RESOLUTION_4_3_600P
+ /// 1024x768: XGA
+ case RESOLUTION_4_3_768P
+ /// 1440x1080
+ case RESOLUTION_4_3_1080P
+}
- case .RESOLUTION_480:
- return CGSize(width: 858, height: 480)
+// MARK: RawRepresentable
- case .RESOLUTION_720:
- return CGSize(width: 1_280, height: 720)
-
- case .RESOLUTION_1080:
- return CGSize(width: 1_920, height: 1_080)
- }
- }
+extension Resolution: RawRepresentable {
+ public typealias RawValue = CGSize
- public static func getResolution(width: Int, height: Int) throws -> Resolution {
- let widerWidth = max(width, height)
- let widerHeight = min(width, height)
+ public init?(rawValue: RawValue) {
+ let widerWidth = max(rawValue.width, rawValue.height)
+ let widerHeight = min(rawValue.width, rawValue.height)
switch (widerWidth, widerHeight) {
- case (352, 240):
- return .RESOLUTION_240
+ case (426, 240):
+ self = .RESOLUTION_16_9_240P
- case (480, 360):
- return .RESOLUTION_360
+ case (640, 360):
+ self = .RESOLUTION_16_9_360P
- case (858, 480):
- return .RESOLUTION_480
+ case (854, 480):
+ self = .RESOLUTION_16_9_480P
case (1_280, 720):
- return .RESOLUTION_720
+ self = .RESOLUTION_16_9_720P
case (1_920, 1_080):
- return .RESOLUTION_1080
+ self = .RESOLUTION_16_9_1080P
+
+ case (320, 240):
+ self = .RESOLUTION_4_3_240P
+
+ case (640, 480):
+ self = .RESOLUTION_4_3_480P
+
+ case (800, 600):
+ self = .RESOLUTION_4_3_600P
- default:
- throw ConfigurationError.invalidParameter("Resolution \(width)x\(height) is not supported")
+ case (1_024, 768):
+ self = .RESOLUTION_4_3_768P
+
+ case (1_440, 1_080):
+ self = .RESOLUTION_4_3_1080P
+
+ default: return nil
}
}
-}
-public extension CGSize {
- var resolution: Resolution {
- get throws {
- try Resolution.getResolution(width: Int(width), height: Int(height))
+ public var rawValue: RawValue {
+ switch self {
+ case .RESOLUTION_16_9_240P:
+ return CGSize(width: 426, height: 240)
+
+ case .RESOLUTION_16_9_360P:
+ return CGSize(width: 640, height: 360)
+
+ case .RESOLUTION_16_9_480P:
+ return CGSize(width: 854, height: 480)
+
+ case .RESOLUTION_16_9_720P:
+ return CGSize(width: 1_280, height: 720)
+
+ case .RESOLUTION_16_9_1080P:
+ return CGSize(width: 1_920, height: 1_080)
+
+ case .RESOLUTION_4_3_240P:
+ return CGSize(width: 320, height: 240)
+
+ case .RESOLUTION_4_3_480P:
+ return CGSize(width: 640, height: 480)
+
+ case .RESOLUTION_4_3_600P:
+ return CGSize(width: 800, height: 600)
+
+ case .RESOLUTION_4_3_768P:
+ return CGSize(width: 1_024, height: 768)
+
+ case .RESOLUTION_4_3_1080P:
+ return CGSize(width: 1_440, height: 1_080)
}
}
+
}