Skip to content

Commit dc68e89

Browse files
1313Copilot
andcommitted
fix(stream-config): pin BGRA as default pixel format (#145)
Apple's stock SCStreamConfiguration() no longer guarantees a BGRA default — on macOS 26 / Apple Silicon the runtime silently switched to kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ('420v') unless the caller explicitly overrides pixelFormat. That broke consumers that assumed packed BGRA samples (the long-standing documented default for this crate) and produced unrecognisable images when client code read the Y luma plane as if it were BGRA. Pin pixelFormat = kCVPixelFormatType_32BGRA inside the Swift bridge sc_stream_configuration_create so SCStreamConfiguration::new() and ::default() deliver a stable BGRA wire format across macOS versions. The preset path (sc_stream_configuration_create_with_preset) is intentionally untouched — presets pick their own pixel format (e.g. HDR 10-bit formats). Add test_default_pixel_format_is_bgra to lock the guarantee in, plus docstrings on PixelFormat::BGRA and set_pixel_format describing the new contract. Fixes #145 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6b34309 commit dc68e89

4 files changed

Lines changed: 45 additions & 1 deletion

File tree

src/stream/configuration/colors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ use super::{internal::SCStreamConfiguration, pixel_format::PixelFormat};
2424
impl SCStreamConfiguration {
2525
/// Set the pixel format for captured frames
2626
///
27+
/// Streams created via [`Self::new`] / [`Self::default`] are pinned to
28+
/// [`PixelFormat::BGRA`] at construction time, so calling this method is
29+
/// only required when you want a non-BGRA format (e.g. YUV `420v` for
30+
/// video encoding, or `l10r` for HDR). Apple's runtime default for
31+
/// `SCStreamConfiguration()` varies by macOS release — see
32+
/// [`PixelFormat::BGRA`] for context.
33+
///
2734
/// # Examples
2835
///
2936
/// ```

src/stream/configuration/pixel_format.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ use crate::utils::four_char_code::FourCharCode;
4747
#[derive(Debug, Clone, Copy, Default)]
4848
#[non_exhaustive]
4949
pub enum PixelFormat {
50-
/// Packed little endian ARGB8888 (most common)
50+
/// Packed little-endian 32-bit BGRA — the default pixel format for
51+
/// streams created via [`crate::stream::configuration::SCStreamConfiguration::new`].
52+
///
53+
/// The crate pins this format at construction time so that
54+
/// `SCStreamConfiguration::new()` delivers a stable BGRA wire format
55+
/// across macOS releases. Without the explicit pin Apple's runtime
56+
/// chooses its own default, which on macOS 26 / Apple Silicon is
57+
/// `420v` (bi-planar YCbCr) — silently breaking consumers that assume
58+
/// packed BGRA samples. See issue #145.
5159
#[default]
5260
BGRA,
5361
/// Packed little endian ARGB2101010 (10-bit color)

swift-bridge/Sources/ScreenCaptureKitBridge/StreamConfiguration.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import CoreGraphics
44
import CoreMedia
5+
import CoreVideo
56
import Foundation
67
import ScreenCaptureKit
78

@@ -69,6 +70,16 @@ private func streamConfigurationState(for config: SCStreamConfiguration) -> Stre
6970
@_cdecl("sc_stream_configuration_create")
7071
public func createStreamConfiguration() -> OpaquePointer {
7172
let config = SCStreamConfiguration()
73+
// Apple's stock `SCStreamConfiguration()` no longer guarantees a BGRA
74+
// default — on macOS 26 / Apple Silicon the runtime delivers
75+
// `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange` ('420v') unless the
76+
// caller explicitly overrides `pixelFormat`. That silently breaks
77+
// consumers that assume 32-bit BGRA samples (the long-standing
78+
// documented default for this crate). Pin BGRA at construction so
79+
// `SCStreamConfiguration::new()` produces the same wire-level format
80+
// across macOS versions. Callers who want YUV / HDR formats override
81+
// this via `set_pixel_format` as before. See issue #145.
82+
config.pixelFormat = kCVPixelFormatType_32BGRA
7283
retainStreamConfigurationState(for: config)
7384
return retain(config)
7485
}

tests/configuration_tests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ fn test_default_configuration() {
1010
// Just verify it doesn't crash
1111
}
1212

13+
/// Regression test for issue #145.
14+
///
15+
/// Apple's stock `SCStreamConfiguration()` no longer defaults to BGRA on
16+
/// macOS 26 / Apple Silicon — the runtime delivers `420v` unless the caller
17+
/// overrides `pixelFormat`. The Swift bridge pins BGRA at construction time
18+
/// to restore the long-standing crate default; this test locks in that
19+
/// guarantee so a future regression in the bridge surfaces immediately.
20+
#[test]
21+
fn test_default_pixel_format_is_bgra() {
22+
let config = SCStreamConfiguration::new();
23+
assert_eq!(
24+
config.pixel_format(),
25+
PixelFormat::BGRA,
26+
"SCStreamConfiguration::new() must default to BGRA across macOS \
27+
versions (see issue #145)",
28+
);
29+
}
30+
1331
#[test]
1432
fn test_set_dimensions() {
1533
let mut config = SCStreamConfiguration::default();

0 commit comments

Comments
 (0)