Camera and Reducer #1928
-
I've been wondering what would be the best practice of using camera with reducer? Let's say we need to start the camera session and get a buffer from video output delegate method PassthroughMetalView(
device: device,
commandQueue: queue,
buffer: viewStore.binding(
get: \.buffer,
set: Action.receiveBuffer
)
) I know this example is not enough but what I'm trying to achieve is to have a specific metal view with its internal renderer and inject only the buffer. I don't want to inject renderer in This is what I quickly tried and of course doesn't work. It will receive the first buffer but even though final class Camera: NSObject, ObservableObject {
// Some properties ..
// Buffer publisher
var buffer: PassthroughSubject<CVPixelBuffer?, Never> = .init()
// Async buffer
var pixelBuffer: CVPixelBuffer? {
get async {
var iterator = buffer.values.makeAsyncIterator()
let buffer = await iterator.next()
return buffer ?? nil
}
}
// Some setup code here including setting self as AVCaptureVideoDataOutputSampleBufferDelegate ...
func start() {
session.startRunning()
}
}
extension CameraModel: AVCaptureVideoDataOutputSampleBufferDelegate {
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
buffer.send(imageBuffer)
}
struct CameraReducer: CameraReducing {
@Dependency(\.camera) var camera
struct State: CameraViewState {
var buffer: CVPixelBuffer?
}
enum Action: CameraAction {
case startSession
case sessionStarted
case receiveBuffer(CVPixelBuffer?)
}
var body: some ReducerProtocol<State, Action> {
Reduce { state, action in
switch action {
case .startSession:
return .task {
camera.start()
return .sessionStarted
}
case .sessionStarted:
return .task {
let newBuffer = await camera.pixelBuffer
return .receiveBuffer(newBuffer)
}
case let .receiveBuffer(buffer):
state.buffer = buffer
return .none
default:
return .none
}
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
I tried this now and it works but I don't think it's that performant as in theory some buffers might be missed struct CameraReducer: CameraReducing {
@Dependency(\.camera) var camera
struct State: CameraViewState {
var buffer: CVPixelBuffer?
}
enum Action: CameraAction {
case startSession
case receiveBuffer(CVPixelBuffer?)
case waitForTheNextBuffer
case startCapturing
case endCapturing
}
var body: some ReducerProtocol<State, Action> {
Reduce { state, action in
switch action {
case .startSession:
return .task {
camera.start()
return .waitForTheNextBuffer
}
case let .receiveBuffer(buffer):
state.buffer = buffer
return .task {
return .waitForTheNextBuffer
}
case .waitForTheNextBuffer:
return .task {
let newBuffer = await camera.pixelBuffer
return .receiveBuffer(newBuffer)
}
default:
return .none
}
}
}
} |
Beta Was this translation helpful? Give feedback.
-
Hey @kakhaberikiknadze! TCA reducers are not particularly well suited for this kind of high-frequency and CPU intensive task. You can check the "Performance" section of the documentation for more informations, but the idea would be to model your high frequency domain as a dependency. You should ask yourself what kind of data is really important for the reducer. For example, are you performing some business logic on each frame using TCA? If this is not the case, you can maybe find a way to make this information travel up to the In other words, the reducer should handle the start and stop, but it has very little to do with the This kind of performance-heavy and high-frequency domains requires some finesse in their implementation, and you can't unfortunately throw everything into TCA. |
Beta Was this translation helpful? Give feedback.
Hey @kakhaberikiknadze! TCA reducers are not particularly well suited for this kind of high-frequency and CPU intensive task.
ViewStore
aren't adapted either to diff very large data types efficiently. Both factors are maybe explaining the performance bottleneck you're observing.You can check the "Performance" section of the documentation for more informations, but the idea would be to model your high frequency domain as a dependency. You should ask yourself what kind of data is really important for the reducer. For example, are you performing some business logic on each frame using TCA? If this is not the case, you can maybe find a way to make this information travel up to the
View
using…