camera: emit previewSize from the first sample buffer's real dims
`device.activeFormat.formatDescription` reports the device's
*selected* format, but on macOS the session-preset remap means the
data output sometimes delivers a different resolution than what
the active format claims. The mismatch surfaced as a stretched
preview on macOS: camera_thumb's SizedBox was sized for a 4:3
buffer, the FittedBox(cover) was given a 16:9 texture, so the
texture stretched to fill the wrongly-shaped box.
PreviewSink now snapshots `CVPixelBufferGet{Width,Height}` from
the first sample buffer that arrives and forwards it via a
callback. CameraInstance hooks the callback to emit the existing
`previewSizeChanged` event (same shape the Android backend uses).
The Dart controller writes it into `value.previewSize`,
camera_thumb's SizedBox snaps to the real buffer aspect, and
FittedBox(cover) crops cleanly without stretching.
Identical wire shape and event name as the Android equivalent —
no Dart changes needed.
This commit is contained in:
@@ -132,6 +132,23 @@ final class CameraInstance {
|
||||
|
||||
try configureSession(forDeviceUniqueID: cameraId, replacing: false)
|
||||
|
||||
// The active-format dims from `device.activeFormat` don't
|
||||
// always match the buffer the data output actually delivers
|
||||
// (notably on macOS, where the session preset gets remapped
|
||||
// mid-pipeline). Forward the real first-frame size to Dart
|
||||
// so camera_thumb's SizedBox aspect matches the texture's
|
||||
// actual aspect — without this the FittedBox(cover) stretches
|
||||
// a 16:9 buffer that's been sized as 4:3.
|
||||
sink.onFirstFrameSize = { [weak self] size in
|
||||
self?.emit([
|
||||
"event": "previewSizeChanged",
|
||||
"previewSize": [
|
||||
"width": size.width,
|
||||
"height": size.height,
|
||||
],
|
||||
])
|
||||
}
|
||||
|
||||
orientation.start { [weak self] next in
|
||||
guard let self = self else { return }
|
||||
self.sessionQueue.async { self.applyOrientationFollowDevice(next) }
|
||||
|
||||
@@ -31,6 +31,16 @@ final class PreviewSink: NSObject, FlutterTexture {
|
||||
)
|
||||
private var latestPixelBuffer: CVPixelBuffer?
|
||||
|
||||
/// Fires once after the first sample buffer arrives, carrying the
|
||||
/// buffer's actual `CVPixelBuffer` dimensions. The active-format
|
||||
/// dims reported by `device.activeFormat` aren't always what the
|
||||
/// data output actually delivers (notably on macOS where the
|
||||
/// session preset gets remapped). [CameraInstance] forwards this
|
||||
/// to Dart as a `previewSizeChanged` event so the camera_thumb's
|
||||
/// SizedBox aspect matches the texture's real aspect.
|
||||
var onFirstFrameSize: ((CGSize) -> Void)?
|
||||
private var emittedFirstSize = false
|
||||
|
||||
func register(with registry: FlutterTextureRegistry) -> Int64 {
|
||||
self.registry = registry
|
||||
textureId = registry.register(self)
|
||||
@@ -63,6 +73,14 @@ final class PreviewSink: NSObject, FlutterTexture {
|
||||
return
|
||||
}
|
||||
bufferQueue.sync { latestPixelBuffer = pb }
|
||||
if !emittedFirstSize {
|
||||
emittedFirstSize = true
|
||||
let size = CGSize(
|
||||
width: CGFloat(CVPixelBufferGetWidth(pb)),
|
||||
height: CGFloat(CVPixelBufferGetHeight(pb))
|
||||
)
|
||||
onFirstFrameSize?(size)
|
||||
}
|
||||
if let registry = registry {
|
||||
registry.textureFrameAvailable(textureId)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user