我正在开发一个使用 Kotlin Multiplatform 和 Google MLKit 进行条形码扫描的应用程序。我无法在 AVCaptureVideoDataOutputSampleBufferDelegate 的 captureOutput 方法中创建 VisionImage。尝试时,我遇到每 15 帧周期性冻结 10 秒的情况。
在我的 CameraViewController (iosMain) 中
class CameraViewController(
private val onQRCodeDetected: (String) -> Unit
) : UIViewController(null, null), AVCaptureVideoDataOutputSampleBufferDelegateProtocol {
我覆盖 captureOutput
override fun captureOutput(
output: AVCaptureOutput,
didOutputSampleBuffer: CMSampleBufferRef?,
fromConnection: AVCaptureConnection
) {
frameCounter+=1
logging("CViewC").i { "FRAME #$frameCounter" }
到目前为止一切都很完美。 captureOutput 会及时调用每一帧并记录帧计数。当我添加以下行时,事情就会中断。
val visionImage = MLKVisionImage(didOutputSampleBuffer)
现在 captureOutput 的前 15 次调用都可以了。在此期间,手机上的预览工作流畅。然后预览冻结并且不会调用 captureOutput。这持续 10 秒。 10 秒后,我再次正常收到 15 帧。这个循环持续下去,应用程序永远不会崩溃。
当我现在添加该行时
CFRelease(didOutputSampleBuffer)
我获得了 288 帧的连续流,直到崩溃。崩溃报告表明我尝试释放已释放的缓冲区(检测到 CFTypeRef %p (%lu / %s) 的过度释放)。
我尝试复制缓冲区:
val copiedBufferVar = nativeHeap.alloc<CMSampleBufferRefVar>()
CMSampleBufferCreateCopy(
allocator = kCFAllocatorDefault,
sbuf = didOutputSampleBuffer,
sampleBufferOut = copiedBufferVar.ptr
)
val visionImage = MLKVisionImage(didOutputSampleBuffer.value)
CFRelease(copiedBufferVar.value)
因为我假设由于 VisionImage 对缓冲区的强引用,缓冲区没有从内存中释放。 (我不知道这是否有道理 - 我拼命地试图理解某些事情)。这导致了同样的事情。 15 帧 OK 和 10 秒冻结的循环。
我尝试通过将 VisionImage 设为可为空并在使用后将其设置为 null 来从内存中释放它。这也没有改变什么。 LLM 建议使用自动释放池,但这也没有帮助。
所以我猜这是一个记忆问题。我以为我的 AVCaptureSession 会自动处理这些事情。我是这样配置的。我删除了一些空检查以使其更短。
@OptIn(ExperimentalForeignApi::class)
private fun setupCaptureSession() {
captureSession = AVCaptureSession().apply {
beginConfiguration()
sessionPreset = AVCaptureSessionPreset1280x720
}
val device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
val input = AVCaptureDeviceInput.deviceInputWithDevice(device, null)
if (captureSession?.canAddInput(input) == true) {
captureSession?.addInput(input)
}
videoOutput = AVCaptureVideoDataOutput().apply {
setSampleBufferDelegate(this@CameraViewController, processingQueue)
videoSettings = mapOf(
kCVPixelBufferPixelFormatTypeKey to NSNumber(kCVPixelFormatType_32BGRA)
)
alwaysDiscardsLateVideoFrames = true
}
if (captureSession?.canAddOutput(videoOutput!!) == true) {
captureSession?.addOutput(videoOutput!!)
}
captureSession?.commitConfiguration()
previewLayer = AVCaptureVideoPreviewLayer(session = captureSession!!).apply {
videoGravity = AVLayerVideoGravityResizeAspectFill
}
}
我也有同样的问题。对我有用的是,一旦不再需要 MLKVisionImage,就致电
kotlin.native.runtime.GC.collect()
。
var visionImage: MLKVisionImage? = MLKVisionImage(didOutputSampleBuffer)
// ...
recognizer.processImage(visionImage as MLKCompatibleImageProtocol) { mlkText, nsError ->
visionImage = null
kotlin.native.runtime.GC.collect()
// ...
}