mirror of
https://github.com/linsa-io/linsa.git
synced 2026-04-27 02:38:45 +02:00
Update stream capture frame rate to 60 FPS and adjust encoding settings; increase audio bitrate to 256k for better quality.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"last_commit": {
|
"last_commit": {
|
||||||
"timestamp": "2025-12-21T23:15:16.584895+00:00",
|
"timestamp": "2025-12-21T23:20:32.869030+00:00",
|
||||||
"session_id": "60ef8f57-a5e4-4edb-a61d-0a4778a6de32",
|
"session_id": "019b4336-8118-7961-95cb-f63fd5f8c638",
|
||||||
"last_entry_timestamp": "2025-12-21T23:15:15.232Z"
|
"last_entry_timestamp": "2025-12-21T23:20:28.851Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,9 +21,9 @@ if [ -z "$STREAM_KEY" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec ffmpeg -f avfoundation -capture_cursor 1 -framerate 30 -i "2:1" \
|
exec ffmpeg -f avfoundation -capture_cursor 1 -framerate 60 -i "2:1" \
|
||||||
-c:v h264_videotoolbox -b:v 4500k -maxrate 4500k -bufsize 9000k \
|
-c:v h264_videotoolbox -b:v 30000k -maxrate 45000k -bufsize 90000k \
|
||||||
-profile:v high -pix_fmt yuv420p \
|
-profile:v high -pix_fmt yuv420p \
|
||||||
-g 60 -keyint_min 60 \
|
-g 120 -keyint_min 120 \
|
||||||
-c:a aac -b:a 128k -ar 48000 -ac 2 \
|
-c:a aac -b:a 256k -ar 48000 -ac 2 \
|
||||||
-f flv "${RTMPS_URL}${STREAM_KEY}"
|
-f flv "${RTMPS_URL}${STREAM_KEY}"
|
||||||
|
|||||||
@@ -173,8 +173,9 @@ actor ZeroCPUCapturer: NSObject, SCStreamDelegate, SCStreamOutput {
|
|||||||
config.width = normalizedSize.width
|
config.width = normalizedSize.width
|
||||||
config.height = normalizedSize.height
|
config.height = normalizedSize.height
|
||||||
|
|
||||||
// 30 FPS for streaming
|
let targetFrameRate: Int32 = 60
|
||||||
config.minimumFrameInterval = CMTime(value: 1, timescale: 30)
|
// 60 FPS for streaming
|
||||||
|
config.minimumFrameInterval = CMTime(value: 1, timescale: targetFrameRate)
|
||||||
|
|
||||||
// Queue depth for smooth delivery (like OBS)
|
// Queue depth for smooth delivery (like OBS)
|
||||||
config.queueDepth = 8
|
config.queueDepth = 8
|
||||||
@@ -210,7 +211,7 @@ actor ZeroCPUCapturer: NSObject, SCStreamDelegate, SCStreamOutput {
|
|||||||
|
|
||||||
// Start capture
|
// Start capture
|
||||||
try await stream?.startCapture()
|
try await stream?.startCapture()
|
||||||
print("Capture started: \(config.width)x\(config.height) @ 30fps")
|
print("Capture started: \(config.width)x\(config.height) @ \(targetFrameRate)fps")
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopCapture() async {
|
func stopCapture() async {
|
||||||
@@ -329,10 +330,19 @@ class HardwareEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Configure for streaming
|
// Configure for streaming
|
||||||
|
let targetFrameRate: Int32 = 60
|
||||||
|
let keyframeInterval = Int(targetFrameRate) * 2
|
||||||
|
let bitrate = HardwareEncoder.recommendedBitrate(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
frameRate: Int(targetFrameRate)
|
||||||
|
)
|
||||||
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_RealTime, value: kCFBooleanTrue)
|
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_RealTime, value: kCFBooleanTrue)
|
||||||
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_ProfileLevel, value: kVTProfileLevel_H264_High_AutoLevel)
|
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_ProfileLevel, value: kVTProfileLevel_H264_High_AutoLevel)
|
||||||
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_AverageBitRate, value: 4_500_000 as CFNumber) // 4.5 Mbps
|
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_ExpectedFrameRate, value: targetFrameRate as CFNumber)
|
||||||
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_MaxKeyFrameInterval, value: 60 as CFNumber) // Keyframe every 2s @ 30fps
|
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_AverageBitRate, value: bitrate as CFNumber)
|
||||||
|
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_MaxKeyFrameInterval, value: keyframeInterval as CFNumber)
|
||||||
|
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, value: 2 as CFNumber)
|
||||||
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_AllowFrameReordering, value: kCFBooleanFalse) // No B-frames for low latency
|
VTSessionSetProperty(session, key: kVTCompressionPropertyKey_AllowFrameReordering, value: kCFBooleanFalse) // No B-frames for low latency
|
||||||
|
|
||||||
VTCompressionSessionPrepareToEncodeFrames(session)
|
VTCompressionSessionPrepareToEncodeFrames(session)
|
||||||
@@ -374,6 +384,19 @@ class HardwareEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func recommendedBitrate(width: Int, height: Int, frameRate: Int) -> Int {
|
||||||
|
let baseWidth = 2560
|
||||||
|
let baseHeight = 1440
|
||||||
|
let baseFrameRate = 60
|
||||||
|
let baseBitrate = 30_000_000
|
||||||
|
|
||||||
|
let pixels = max(1, width) * max(1, height)
|
||||||
|
let basePixels = baseWidth * baseHeight
|
||||||
|
let fpsScale = Double(max(frameRate, 1)) / Double(baseFrameRate)
|
||||||
|
let raw = Double(baseBitrate) * (Double(pixels) / Double(basePixels)) * fpsScale
|
||||||
|
return min(max(Int(raw.rounded()), 12_000_000), 80_000_000)
|
||||||
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
if let session = session {
|
if let session = session {
|
||||||
VTCompressionSessionInvalidate(session)
|
VTCompressionSessionInvalidate(session)
|
||||||
|
|||||||
@@ -112,9 +112,9 @@ pub async fn start_hls(
|
|||||||
"-c:a",
|
"-c:a",
|
||||||
"aac",
|
"aac",
|
||||||
"-b:a",
|
"-b:a",
|
||||||
"128k",
|
"256k",
|
||||||
"-ar",
|
"-ar",
|
||||||
"44100",
|
"48000",
|
||||||
// HLS output settings
|
// HLS output settings
|
||||||
"-f",
|
"-f",
|
||||||
"hls",
|
"hls",
|
||||||
@@ -183,9 +183,9 @@ pub async fn start_youtube(
|
|||||||
"-c:a",
|
"-c:a",
|
||||||
"aac",
|
"aac",
|
||||||
"-b:a",
|
"-b:a",
|
||||||
"128k",
|
"256k",
|
||||||
"-ar",
|
"-ar",
|
||||||
"44100",
|
"48000",
|
||||||
// FLV container for RTMP
|
// FLV container for RTMP
|
||||||
"-f",
|
"-f",
|
||||||
"flv",
|
"flv",
|
||||||
@@ -268,9 +268,9 @@ pub async fn start_youtube_with_filter(
|
|||||||
"-c:a".to_string(),
|
"-c:a".to_string(),
|
||||||
"aac".to_string(),
|
"aac".to_string(),
|
||||||
"-b:a".to_string(),
|
"-b:a".to_string(),
|
||||||
"128k".to_string(),
|
"256k".to_string(),
|
||||||
"-ar".to_string(),
|
"-ar".to_string(),
|
||||||
"44100".to_string(),
|
"48000".to_string(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
|
|||||||
Reference in New Issue
Block a user