Article / 2026/05/29

录制

录制 的技术笔记。


//
//  YSVideoRecoder.swift
//  wscanner
//
//  Created by yunshen on 2025/9/5.
//

import AVFoundation
import MetalKit

class YSVideoRecoder: IRecoder {
    func startRecording(outputURL: URL, playbackSpeed: Double, maxFrames: Int64, autoStopCompletion: ((Bool, URL?) -> Void)?) -> Bool {
        return true
    }
    
    
    private weak var delegate: VideoRecorderDelegate?
    private var isRecording: Bool = false
    private var videoWidth: Int = 0
    private var videoHeight: Int = 0
    private let recordingQueue = DispatchQueue(
        label: "com.videorecorder.queue",
        qos: .userInitiated
    )
    private var pixelBufferPool: CVPixelBufferPool?
    private var reusablePixelData: UnsafeMutablePointer<UInt8>?
    private var pixelDataSize: Int = 0
    private var videoSpeedMultiplier: Double = 1.0
    
    private var assetWriter: AVAssetWriter?
    private var assetWriterInput: AVAssetWriterInput?
    private var assetWriterPixelBufferAdaptor:
    AVAssetWriterInputPixelBufferAdaptor?
    
    // 时间管理
    private var recordingStartTime: CMTime?
    private var recordingStartTimeAbs: CFAbsoluteTime = 0
    private var lastCaptureTime: CFAbsoluteTime = 0
    private var lastPresentationTime: CMTime = .zero
    
    private var firstValidFrameCaptured: Bool = false
    var frameCount: Int64 = 0
    
    // 帧缓冲
    private var pendingFrames = [PendingFrame]()
    private let pendingFramesLock = NSLock()
    private var isProcessingFrames = false
    
    private var device: MTLDevice
    private var captureBuffer: MTLBuffer?
    private var captureCommandQueue: MTLCommandQueue?
    
    init(device: MTLDevice) {
        self.device = device
        self.captureCommandQueue = device.makeCommandQueue()
    }
    
    deinit {
        cleanupResources()
    }
    
    func setDelegate(delegate: any VideoRecorderDelegate) {
        self.delegate = delegate
    }
    
    func configureSize(width: Int, height: Int) {
        guard !isRecording else {
            print("无法在录制中更改尺寸")
            return
        }
        
        videoWidth = width
        videoHeight = height
        
        // 更新像素数据缓冲区
        pixelDataSize = width * height * 4
        reusablePixelData?.deallocate()
        reusablePixelData = UnsafeMutablePointer<UInt8>.allocate(
            capacity: pixelDataSize
        )
        
        self.captureBuffer = device.makeBuffer(
            length: pixelDataSize,
            options: .storageModeShared
        )
    }
    
    /// 开始录制
    func startRecording(outputURL: URL, playbackSpeed: Double = 1.0) -> Bool {
        guard !isRecording else {
            print("已经在录制中")
            return false
        }
        
        guard videoWidth > 0 && videoHeight > 0 else {
            print("请先配置视频尺寸")
            return false
        }
        
        videoSpeedMultiplier = playbackSpeed
        
        do {
            // 移除已存在的文件
            if FileManager.default.fileExists(atPath: outputURL.path) {
                try FileManager.default.removeItem(at: outputURL)
            }
            
            // 创建 AVAssetWriter
            assetWriter = try AVAssetWriter(
                outputURL: outputURL,
                fileType: .mp4
            )
            
            // 配置视频设置 - 修复1: 使用正确的视频编码设置
            let videoSettings = createRealVideoSettings()
            assetWriterInput = AVAssetWriterInput(
                mediaType: .video,
                outputSettings: videoSettings
            )
            assetWriterInput?.expectsMediaDataInRealTime = true  // 修复2: 改为 true
            
            // 创建像素缓冲适配器
            let pixelBufferAttributes = createPixelBufferAttributes()
            assetWriterPixelBufferAdaptor =
            AVAssetWriterInputPixelBufferAdaptor(
                assetWriterInput: assetWriterInput!,
                sourcePixelBufferAttributes: pixelBufferAttributes
            )
            
            // 添加输入到 writer
            if assetWriter!.canAdd(assetWriterInput!) {
                assetWriter!.add(assetWriterInput!)
            } else {
                print("无法添加视频输入到 AssetWriter")
                return false
            }
            
            // 开始写入
            if !assetWriter!.startWriting() {
                print(
                    "无法开始写入,错误: \(assetWriter!.error?.localizedDescription ?? "未知错误")"
                )
                return false
            }
            
            // 开始会话
            assetWriter!.startSession(atSourceTime: .zero)
            
            // 更新状态
            isRecording = true
            firstValidFrameCaptured = false
            frameCount = 0
            lastCaptureTime = 0
            
            // 启动帧处理
            startFrameProcessing()
            
            // 通知代理
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.delegate?.videoRecorderDidStartRecording(self)
            }
            
            print("录制开始成功")
            return true
            
        } catch {
            print("开始录制失败: \(error)")
            return false
        }
    }
    
    func stopRecording(completion: @escaping (Bool, URL?) -> Void) {
        guard isRecording, let assetWriter = self.assetWriter else {
            completion(false, nil)
            return
        }
        
        isRecording = false
        firstValidFrameCaptured = false
        isProcessingFrames = false
        
        print("正在停止录制,处理剩余帧...")
        
        // 在后台队列处理剩余帧并完成写入
        recordingQueue.async { [weak self] in
            guard let self = self else { return }
            
            // 处理剩余的帧
            self.processPendingFrames()
            
            // 等待输入准备好
            while !(self.assetWriterInput?.isReadyForMoreMediaData ?? false) {
                Thread.sleep(forTimeInterval: 0.01)
            }
            
            // 标记输入完成
            self.assetWriterInput?.markAsFinished()
            
            // 完成写入
            assetWriter.finishWriting {
                DispatchQueue.main.async {
                    let success = assetWriter.status == .completed
                    let outputURL = success ? assetWriter.outputURL : nil
                    
                    if !success {
                        print("录制失败,状态: \(assetWriter.status.rawValue)")
                        if let error = assetWriter.error {
                            print("错误信息: \(error)")
                        }
                    } else {
                        print("录制成功完成,共 \(self.frameCount) 帧")
                    }
                    
                    // 清理资源
                    self.cleanupRecordingResources()
                    
                    // 通知代理
                    self.delegate?.videoRecorderDidStopRecording(
                        self,
                        success: success,
                        outputURL: outputURL
                    )
                    
                    // 调用完成回调
                    completion(success, outputURL)
                }
            }
        }
    }
    
    func captureFrame(from texture: MTLTexture) {
        guard isRecording else { return }
        let currentTime = CFAbsoluteTimeGetCurrent()
        
        lastCaptureTime = currentTime
        
        let presentationTime: CMTime
        if !firstValidFrameCaptured {
            presentationTime = .zero
            // 初始化时间
            recordingStartTimeAbs = CFAbsoluteTimeGetCurrent()
            lastPresentationTime = .zero
            recordingStartTime = .zero
            firstValidFrameCaptured = true
        } else {
            presentationTime = calculatePresentationTimeReal()
        }
        
        guard let commandQueue = captureCommandQueue,
              let buffer = captureBuffer
        else {
            print("Metal resources not initialized")
            return
        }
        
        guard let commandBuffer = commandQueue.makeCommandBuffer(),
              let blitEncoder = commandBuffer.makeBlitCommandEncoder()
        else {
            return
        }
        
        // 异步复制纹理到缓冲区
        blitEncoder.copy(
            from: texture,
            sourceSlice: 0,
            sourceLevel: 0,
            sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
            sourceSize: MTLSize(
                width: texture.width,
                height: texture.height,
                depth: 1
            ),
            to: buffer,
            destinationOffset: 0,
            destinationBytesPerRow: texture.width * 4,
            destinationBytesPerImage: texture.width * texture.height * 4
        )
        blitEncoder.endEncoding()
        
        // 添加完成回调,在 GPU 复制完成后处理数据
        commandBuffer.addCompletedHandler { [weak self] _ in
            guard let self = self else { return }
            
            // 现在数据已经在 buffer 中,创建 Data 对象
            let bufferPointer = buffer.contents()
            let frameData = Data(bytes: bufferPointer,
                                 count: pixelDataSize)
            
            let pendingFrame = PendingFrame(
                pixelData: frameData,
                presentationTime: presentationTime,
                width: self.videoWidth,
                height: self.videoHeight
            )
            
            // 添加到待处理队列
            self.pendingFramesLock.lock()
            self.pendingFrames.append(pendingFrame)
            self.pendingFramesLock.unlock()
        }
        
        commandBuffer.commit()
    }
    
    func isDecodering() -> Bool {
        
        return isRecording
    }
    
    private func startFrameProcessing() {
        isProcessingFrames = true
        recordingQueue.async { [weak self] in
            while self?.isProcessingFrames == true {
                self?.processPendingFrames()
                Thread.sleep(forTimeInterval: 0.01)  // 更频繁地处理
            }
        }
    }
    
    private func processPendingFrames() {
        pendingFramesLock.lock()
        let framesToProcess = pendingFrames
        pendingFrames.removeAll()
        pendingFramesLock.unlock()
        
        for frame in framesToProcess {
            processFrame(frame)
        }
    }
    
    private func processFrame(_ frame: PendingFrame) {
        guard let assetWriterInput = self.assetWriterInput,
              let pixelBufferAdaptor = self.assetWriterPixelBufferAdaptor
        else {
            return
        }
        
        // 等待输入准备好
        while !assetWriterInput.isReadyForMoreMediaData && isProcessingFrames {
            Thread.sleep(forTimeInterval: 0.01)
        }
        
        guard assetWriterInput.isReadyForMoreMediaData else { return }
        
        autoreleasepool {
            // 创建像素缓冲区 - 修复4: 确保从池中获取
            var pixelBuffer: CVPixelBuffer?
            let pool = pixelBufferAdaptor.pixelBufferPool
            
            if let pool = pool {
                let status = CVPixelBufferPoolCreatePixelBuffer(
                    kCFAllocatorDefault,
                    pool,
                    &pixelBuffer
                )
                if status != kCVReturnSuccess {
                    print("从池创建像素缓冲区失败: \(status)")
                }
            }
            
            // 如果池中没有可用的,创建新的
            if pixelBuffer == nil {
                let attributes = createPixelBufferAttributes()
                let status = CVPixelBufferCreate(
                    kCFAllocatorDefault,
                    frame.width,
                    frame.height,
                    kCVPixelFormatType_32BGRA,
                    attributes as CFDictionary,
                    &pixelBuffer
                )
                
                if status != kCVReturnSuccess {
                    print("创建像素缓冲区失败: \(status)")
                    return
                }
            }
            
            guard let buffer = pixelBuffer else {
                print("无法创建像素缓冲区")
                return
            }
            
            // 转换并复制像素数据
            CVPixelBufferLockBaseAddress(
                buffer,
                CVPixelBufferLockFlags(rawValue: 0)
            )
            
            if let baseAddress = CVPixelBufferGetBaseAddress(buffer) {
                let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)
                let dstBuffer = baseAddress.assumingMemoryBound(to: UInt8.self)
                
                frame.pixelData.withUnsafeBytes { srcBytes in
                    if let srcBuffer = srcBytes.bindMemory(to: UInt8.self)
                        .baseAddress
                    {
                        copyBGRA(
                            src: srcBuffer,
                            dst: dstBuffer,
                            width: frame.width,
                            height: frame.height,
                            dstBytesPerRow: bytesPerRow
                        )
                    }
                }
            }
            
            CVPixelBufferUnlockBaseAddress(
                buffer,
                CVPixelBufferLockFlags(rawValue: 0)
            )
            
            // 添加到视频 - 修复5: 确保时间戳正确
            if pixelBufferAdaptor.append(
                buffer,
                withPresentationTime: frame.presentationTime
            ) {
                frameCount += 1
                lastPresentationTime = frame.presentationTime
                
                // 通知代理(在主线程)
                if frameCount % 30 == 0 {  // 每30帧通知一次,减少开销
                    DispatchQueue.main.async { [weak self] in
                        guard let self = self else { return }
                        self.delegate?.videoRecorderDidCaptureFrame(
                            self,
                            frameCount: self.frameCount
                        )
                    }
                }
            } else {
                print("添加帧失败,时间戳: \(frame.presentationTime.seconds)")
            }
        }
    }
    
    private func calculatePresentationTimeReal() -> CMTime {
        let currentTimeAbs = CFAbsoluteTimeGetCurrent()
        let elapsedTime = currentTimeAbs - recordingStartTimeAbs
        let adjustedTime = elapsedTime * videoSpeedMultiplier
        return CMTime(seconds: adjustedTime, preferredTimescale: 600)
    }
    
    private func cleanupRecordingResources() {
        isProcessingFrames = false
        assetWriter = nil
        assetWriterInput = nil
        assetWriterPixelBufferAdaptor = nil
        recordingStartTime = nil
        recordingStartTimeAbs = 0
        frameCount = 0
        pixelBufferPool = nil
        lastPresentationTime = .zero
        
        pendingFramesLock.lock()
        pendingFrames.removeAll()
        pendingFramesLock.unlock()
    }
    
    private func cleanupResources() {
        cleanupRecordingResources()
        reusablePixelData?.deallocate()
        reusablePixelData = nil
    }
    
    private func copyBGRA(
        src: UnsafePointer<UInt8>,
        dst: UnsafeMutablePointer<UInt8>,
        width: Int,
        height: Int,
        dstBytesPerRow: Int
    ) {
        //        memcpy(dst, src, width * height * 4)
        let srcBytesPerRow = width * 4
        //        print("dstBytesPerRow = \(dstBytesPerRow) srcBytesPerRow = \(srcBytesPerRow)")
        for y in 0..<height {
            let srcRowStart = y * srcBytesPerRow
            let dstRowStart = y * dstBytesPerRow
            memcpy(dst + dstRowStart, src + srcRowStart, srcBytesPerRow)
        }
    }
    
    private func createRealVideoSettings() -> [String: Any] {
        return [
            AVVideoCodecKey: AVVideoCodecType.h264,
            AVVideoWidthKey: videoWidth,
            AVVideoHeightKey: videoHeight,
            AVVideoCompressionPropertiesKey: [
                AVVideoAverageBitRateKey: NSNumber(
                    value: videoWidth * videoHeight * 10
                ),  // 提高比特率
                AVVideoProfileLevelKey: AVVideoProfileLevelH264High41,  // 使用更高的配置
                //                AVVideoProfileLevelKey:
                //                    AVVideoProfileLevelH264BaselineAutoLevel,
                AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC,
            ],
        ]
    }
    
    private func createPixelBufferAttributes() -> [String: Any] {
        return [
            kCVPixelBufferPixelFormatTypeKey as String:
                kCVPixelFormatType_32BGRA,
            kCVPixelBufferWidthKey as String: videoWidth,
            kCVPixelBufferHeightKey as String: videoHeight,
            kCVPixelBufferOpenGLESCompatibilityKey as String: true,
            kCVPixelBufferIOSurfacePropertiesKey as String: [:],
            kCVPixelBufferCGImageCompatibilityKey as String: true,
            kCVPixelBufferCGBitmapContextCompatibilityKey as String: true,
        ]
    }
    
}

//
//  YSVideoRecoder2.swift
//  wscanner
//
//  Created by yunshen on 2025/9/5.
//
internal import common
import AVFoundation
import MetalKit

import Foundation
import AVFoundation
import MetalKit

// MARK: - 改进的帧数据容器
class ReusableFrameData {
    var pixelData: UnsafeMutablePointer<UInt8>
    let capacity: Int
    var presentationTime: CMTime = .zero
    var width: Int = 0
    var height: Int = 0
    var isValid: Bool = false
    
    init(capacity: Int) {
        self.capacity = capacity
        self.pixelData = UnsafeMutablePointer<UInt8>.allocate(capacity: capacity)
        print("ReusableFrameData init - capacity: \(capacity)")
    }
    
    deinit {
        pixelData.deallocate()
        print("ReusableFrameData deinit - 不应该频繁出现此消息")
    }
    
    func reset() {
        isValid = false
        presentationTime = .zero
        width = 0
        height = 0
    }
    
    func copyFrom(buffer: UnsafeRawPointer, size: Int, time: CMTime, width: Int, height: Int) {
        guard size <= capacity else {
            print("Error: size \(size) > capacity \(capacity)")
            return
        }
        memcpy(pixelData, buffer, size)
        self.presentationTime = time
        self.width = width
        self.height = height
        self.isValid = true
    }
}

// MARK: - 优化后的视频录制器
class YSVideoRecoder2: IRecoder {
    
    private weak var delegate: VideoRecorderDelegate?
    private var isRecording: Bool = false
    private var videoWidth: Int = 0
    private var videoHeight: Int = 0
    private let recordingQueue = DispatchQueue(
        label: "com.videorecorder.queue",
        qos: .userInitiated
    )
    
    // 使用强引用池
    private var frameDataPool: StrongSynchronizedPool<ReusableFrameData>!
    private var metalBufferPool: StrongSynchronizedPool<MTLBuffer>!
    
    private var pixelDataSize: Int = 0
    private var videoSpeedMultiplier: Double = 1.0
    
    private var assetWriter: AVAssetWriter?
    private var assetWriterInput: AVAssetWriterInput?
    private var assetWriterPixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor?
    
    // 时间管理
    private var recordingStartTime: CMTime?
    private var recordingStartTimeAbs: CFAbsoluteTime = 0
    private var lastCaptureTime: CFAbsoluteTime = 0
    private var lastPresentationTime: CMTime = .zero
    
    private var firstValidFrameCaptured: Bool = false
    var frameCount: Int64 = 0
    
    // 直接使用 ReusableFrameData 的队列
    private var isProcessingFrames = false
    private var linkBlockingQueue = LinkedBlockingQueue<ReusableFrameData>(
        capacity: 200,
        target: "YSVideoRecoder2",
        isPrint: false
    )
    private var thread: YSThread<ReusableFrameData>? = nil
    
    private var device: MTLDevice
    private var captureCommandQueue: MTLCommandQueue?
    
    // 状态管理
    private let stateLock = NSLock()
    private var isFinishing = false
    private var pendingFrameCount = 0
    private let pendingFrameLock = NSLock()
    
    // 帧数限制功能
    private var maxFrameCount: Int64 = 0
    private var autoStopCompletion: ((Bool, URL?) -> Void)?
    
    // 池配置
    private let maxFrameDataObjects = 15  // 增加池大小
    private let maxMetalBuffers = 8
    
    // 调试统计
    private var poolHitCount = 0
    private var poolMissCount = 0
    
    init(device: MTLDevice) {
        self.device = device
        self.captureCommandQueue = device.makeCommandQueue()
        // 初始化强引用池
        frameDataPool = StrongSynchronizedPool<ReusableFrameData>(maxPoolSize: maxFrameDataObjects)
        metalBufferPool = StrongSynchronizedPool<MTLBuffer>(maxPoolSize: maxMetalBuffers)
    }
    
    deinit {
        cleanupResources()
    }
    
    func setDelegate(delegate: any VideoRecorderDelegate) {
        self.delegate = delegate
    }
    
    func configureSize(width: Int, height: Int) {
        stateLock.lock()
        defer { stateLock.unlock() }
        
        guard !isRecording else {
            print("无法在录制中更改尺寸")
            return
        }
        
        videoWidth = width
        videoHeight = height
        pixelDataSize = width * height * 4
        
        // 预创建帧数据对象
        print("预创建 \(maxFrameDataObjects/2) 个帧数据对象...")
        for i in 0..<(maxFrameDataObjects/2) {
            let frameData = ReusableFrameData(capacity: pixelDataSize)
            do {
                _ = try frameDataPool.release(frameData)
                print("预创建帧数据对象 \(i+1) 成功")
            } catch {
                print("预创建帧数据对象 \(i+1) 失败: \(error)")
            }
        }
        
        // 预创建 Metal 缓冲区
        print("预创建 \(maxMetalBuffers/2) 个 Metal 缓冲区...")
        for i in 0..<(maxMetalBuffers/2) {
            if let buffer = device.makeBuffer(
                length: pixelDataSize,
                options: .storageModeShared
            ) {
                do {
                    _ = try metalBufferPool.release(buffer)
                    print("预创建 Metal 缓冲区 \(i+1) 成功")
                } catch {
                    print("预创建 Metal 缓冲区 \(i+1) 失败: \(error)")
                }
            }
        }
    }
    
    func captureFrame(from texture: MTLTexture) {
        stateLock.lock()
        guard isRecording && !isFinishing else {
            stateLock.unlock()
            return
        }
        stateLock.unlock()
        
        let currentTime = CFAbsoluteTimeGetCurrent()
        lastCaptureTime = currentTime
        
        let presentationTime: CMTime
        if !firstValidFrameCaptured {
            presentationTime = .zero
            recordingStartTimeAbs = CFAbsoluteTimeGetCurrent()
            lastPresentationTime = .zero
            recordingStartTime = .zero
            firstValidFrameCaptured = true
        } else {
            presentationTime = calculatePresentationTimeReal()
        }
        
        guard let commandQueue = captureCommandQueue else {
            print("Metal resources not initialized")
            return
        }
        
        // 从池中获取 Metal 缓冲区
        var metalBuffer: MTLBuffer?
        if let poolBuffer = metalBufferPool.acquire() {
            metalBuffer = poolBuffer
            poolHitCount += 1
        } else {
            metalBuffer = device.makeBuffer(
                length: pixelDataSize,
                options: .storageModeShared
            )
            poolMissCount += 1
            if frameCount % 100 == 0 {
                print("Metal Buffer Pool - Hits: \(poolHitCount), Misses: \(poolMissCount)")
            }
        }
        
        guard let buffer = metalBuffer,
              let commandBuffer = commandQueue.makeCommandBuffer(),
              let blitEncoder = commandBuffer.makeBlitCommandEncoder() else {
            if let buf = metalBuffer {
                _ = try? metalBufferPool.release(buf)
            }
            return
        }
        
        pendingFrameLock.lock()
        pendingFrameCount += 1
        pendingFrameLock.unlock()
        
        // 异步复制纹理到缓冲区
        blitEncoder.copy(
            from: texture,
            sourceSlice: 0,
            sourceLevel: 0,
            sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
            sourceSize: MTLSize(
                width: texture.width,
                height: texture.height,
                depth: 1
            ),
            to: buffer,
            destinationOffset: 0,
            destinationBytesPerRow: texture.width * 4,
            destinationBytesPerImage: texture.width * texture.height * 4
        )
        blitEncoder.endEncoding()
        
        // 添加完成回调
        commandBuffer.addCompletedHandler { [weak self] _ in
            guard let self = self else {
                _ = try? self?.metalBufferPool.release(buffer)
                return
            }
            
            self.stateLock.lock()
            let shouldProcess = self.isRecording && !self.isFinishing
            self.stateLock.unlock()
            
            if shouldProcess {
                // 从池中获取帧数据对象
                var frameData: ReusableFrameData?
                if let poolData = self.frameDataPool.acquire() {
                    frameData = poolData
                    frameData?.reset()  // 重置状态
                } else {
                    frameData = ReusableFrameData(capacity: self.pixelDataSize)
                    print("创建新的 ReusableFrameData,池可能太小")
                }
                
                guard let data = frameData else { return }
                
                // 复制数据
                data.copyFrom(
                    buffer: buffer.contents(),
                    size: self.pixelDataSize,
                    time: presentationTime,
                    width: self.videoWidth,
                    height: self.videoHeight
                )
                
                // 将帧数据加入队列
                self.linkBlockingQueue.putWithReplace(data) { droppedData in
                    print("Frame dropped due to queue full")
                    droppedData.reset()
                    _ = try? self.frameDataPool.release(droppedData)
                }
            }
            
            // 回收 Metal 缓冲区
            do {
                _ = try self.metalBufferPool.release(buffer)
            } catch {
                print("Metal buffer 回收失败: \(error)")
            }
            
            self.pendingFrameLock.lock()
            self.pendingFrameCount -= 1
            self.pendingFrameLock.unlock()
        }
        
        commandBuffer.commit()
    }
    
    private func processFrame(_ frameData: ReusableFrameData) {
        defer {
            // 重要:处理完成后回收到池中
            frameData.reset()
            do {
                let released = try frameDataPool.release(frameData)
                if !released {
                    print("警告:帧数据对象未能回收到池中(池满)")
                }
            } catch {
                print("帧数据回收错误: \(error)")
            }
        }
        
        guard frameData.isValid,
              let assetWriterInput = self.assetWriterInput,
              let pixelBufferAdaptor = self.assetWriterPixelBufferAdaptor else {
            return
        }
        
        // 等待输入准备好
        while !assetWriterInput.isReadyForMoreMediaData && isProcessingFrames {
            Thread.sleep(forTimeInterval: 0.01)
        }
        
        guard assetWriterInput.isReadyForMoreMediaData else { return }
        
        autoreleasepool {
            var pixelBuffer: CVPixelBuffer?
            
            // 使用 AVAssetWriter 的像素缓冲池
            if let pool = pixelBufferAdaptor.pixelBufferPool {
                CVPixelBufferPoolCreatePixelBuffer(
                    kCFAllocatorDefault,
                    pool,
                    &pixelBuffer
                )
            }
            
            // 如果池创建失败,手动创建
            if pixelBuffer == nil {
                let attributes = createPixelBufferAttributes()
                CVPixelBufferCreate(
                    kCFAllocatorDefault,
                    frameData.width,
                    frameData.height,
                    kCVPixelFormatType_32BGRA,
                    attributes as CFDictionary,
                    &pixelBuffer
                )
            }
            
            guard let buffer = pixelBuffer else {
                print("无法创建像素缓冲区")
                return
            }
            
            // 复制像素数据
            CVPixelBufferLockBaseAddress(buffer, [])
            
            if let baseAddress = CVPixelBufferGetBaseAddress(buffer) {
                let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)
                let dstBuffer = baseAddress.assumingMemoryBound(to: UInt8.self)
                
                copyBGRA(
                    src: frameData.pixelData,
                    dst: dstBuffer,
                    width: frameData.width,
                    height: frameData.height,
                    dstBytesPerRow: bytesPerRow
                )
            }
            
            CVPixelBufferUnlockBaseAddress(buffer, [])
            
            // 写入视频
            if pixelBufferAdaptor.append(buffer, withPresentationTime: frameData.presentationTime) {
                frameCount += 1
                lastPresentationTime = frameData.presentationTime
                
                // 检查帧数限制
                if maxFrameCount > 0 && frameCount >= maxFrameCount {
                    print("达到最大帧数限制: \(maxFrameCount)")
                    
                    stateLock.lock()
                    let completion = self.autoStopCompletion
                    self.autoStopCompletion = nil
                    stateLock.unlock()
                    
                    DispatchQueue.main.async { [weak self] in
                        self?.stopRecording { success, url in
                            completion?(success, url)
                        }
                    }
                }
                
                // 定期通知进度
                if frameCount % 30 == 0 {
                    DispatchQueue.main.async { [weak self] in
                        guard let self = self else { return }
                        self.delegate?.videoRecorderDidCaptureFrame(self, frameCount: self.frameCount)
                    }
                }
            }
        }
    }
    
    private func startFrameProcessing() {
        isProcessingFrames = true
        
        thread?.cancel()
        thread = YSThread(queue: linkBlockingQueue, name: "YSVideoRecoder2", run: { [weak self] data in
            guard let self = self else {
                // 如果 self 已释放,仍需要回收数据
                data.reset()
                return
            }
            
            self.stateLock.lock()
            let shouldProcess = self.isProcessingFrames
            self.stateLock.unlock()
            
            if shouldProcess {
                self.processFrame(data)
            } else {
                // 如果不应该处理,回收数据
                data.reset()
                _ = try? self.frameDataPool.release(data)
            }
        })
        
        thread?.start()
    }
    
    func startRecording(
        outputURL: URL,
        playbackSpeed: Double = 1.0,
        maxFrames: Int64 = 0,
        autoStopCompletion: ((Bool, URL?) -> Void)? = nil
    ) -> Bool {
        stateLock.lock()
        defer { stateLock.unlock() }
        
        guard !isRecording else {
            print("已经在录制中")
            return false
        }
        
        guard videoWidth > 0 && videoHeight > 0 else {
            print("请先配置视频尺寸")
            return false
        }
        
        // 重置统计
        poolHitCount = 0
        poolMissCount = 0
        
        videoSpeedMultiplier = playbackSpeed
        maxFrameCount = maxFrames
        self.autoStopCompletion = autoStopCompletion
        
        do {
            if FileManager.default.fileExists(atPath: outputURL.path) {
                try FileManager.default.removeItem(at: outputURL)
            }
            
            assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: .mp4)
            
            let videoSettings = createRealVideoSettings()
            assetWriterInput = AVAssetWriterInput(
                mediaType: .video,
                outputSettings: videoSettings
            )
            assetWriterInput?.expectsMediaDataInRealTime = true
            
            let pixelBufferAttributes = createPixelBufferAttributes()
            assetWriterPixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(
                assetWriterInput: assetWriterInput!,
                sourcePixelBufferAttributes: pixelBufferAttributes
            )
            
            if assetWriter!.canAdd(assetWriterInput!) {
                assetWriter!.add(assetWriterInput!)
            } else {
                return false
            }
            
            if !assetWriter!.startWriting() {
                return false
            }
            
            assetWriter!.startSession(atSourceTime: .zero)
            
            isRecording = true
            isFinishing = false
            firstValidFrameCaptured = false
            frameCount = 0
            lastCaptureTime = 0
            pendingFrameCount = 0
            
            linkBlockingQueue.clear()
            linkBlockingQueue.reset()
            
            startFrameProcessing()
            
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.delegate?.videoRecorderDidStartRecording(self)
            }
            
            print("录制开始,池大小 - 帧数据: \(maxFrameDataObjects), Metal缓冲: \(maxMetalBuffers)")
            return true
            
        } catch {
            print("开始录制失败: \(error)")
            return false
        }
    }
    
    func startRecording(outputURL: URL, playbackSpeed: Double = 1.0) -> Bool {
        return startRecording(outputURL: outputURL, playbackSpeed: playbackSpeed, maxFrames: 0, autoStopCompletion: nil)
    }
    
    func stopRecording(completion: @escaping (Bool, URL?) -> Void) {
        stateLock.lock()
        
        guard isRecording, let assetWriter = self.assetWriter else {
            stateLock.unlock()
            completion(false, nil)
            return
        }
        
        isRecording = false
        isFinishing = true
        let finalFrameCount = self.frameCount
        self.autoStopCompletion = nil
        
        // 打印最终统计
        print("录制统计 - Pool Hits: \(poolHitCount), Misses: \(poolMissCount)")
        
        stateLock.unlock()
        
        print("正在停止录制,处理剩余帧...")
        
        linkBlockingQueue.interrupt()
        
        recordingQueue.async { [weak self] in
            guard let self = self else {
                completion(false, nil)
                return
            }
            
            self.waitForPendingFrames()
            self.thread?.cancel()
            
            var waitCount = 0
            while self.linkBlockingQueue.size > 0 && waitCount < 100 {
                Thread.sleep(forTimeInterval: 0.05)
                waitCount += 1
            }
            
            while !(self.assetWriterInput?.isReadyForMoreMediaData ?? false) {
                Thread.sleep(forTimeInterval: 0.01)
            }
            
            self.assetWriterInput?.markAsFinished()
            
            assetWriter.finishWriting {
                DispatchQueue.main.async {
                    let success = assetWriter.status == .completed
                    let outputURL = success ? assetWriter.outputURL : nil
                    
                    print("录制完成 - 状态: \(success ? "成功" : "失败"), 总帧数: \(finalFrameCount)")
                    
                    self.cleanupRecordingResources()
                    
                    self.delegate?.videoRecorderDidStopRecording(
                        self,
                        success: success,
                        outputURL: outputURL
                    )
                    
                    completion(success, outputURL)
                }
            }
        }
    }
    
    func isDecodering() -> Bool {
        stateLock.lock()
        defer { stateLock.unlock() }
        return isRecording
    }
    
    func getCurrentFrameCount() -> Int64 {
        stateLock.lock()
        defer { stateLock.unlock() }
        return frameCount
    }
    
    func getMaxFrameCount() -> Int64 {
        stateLock.lock()
        defer { stateLock.unlock() }
        return maxFrameCount
    }
    
    private func waitForPendingFrames() {
        var waitCount = 0
        while waitCount < 100 {
            pendingFrameLock.lock()
            let count = pendingFrameCount
            pendingFrameLock.unlock()
            
            if count == 0 {
                break
            }
            
            Thread.sleep(forTimeInterval: 0.05)
            waitCount += 1
        }
    }
    
    private func calculatePresentationTimeReal() -> CMTime {
        let currentTimeAbs = CFAbsoluteTimeGetCurrent()
        let elapsedTime = currentTimeAbs - recordingStartTimeAbs
        let adjustedTime = elapsedTime * videoSpeedMultiplier
        return CMTime(seconds: adjustedTime, preferredTimescale: 600)
    }
    
    private func cleanupRecordingResources() {
        stateLock.lock()
        defer { stateLock.unlock() }
        
        isProcessingFrames = false
        isFinishing = false
        assetWriter = nil
        assetWriterInput = nil
        assetWriterPixelBufferAdaptor = nil
        recordingStartTime = nil
        recordingStartTimeAbs = 0
        frameCount = 0
        lastPresentationTime = .zero
        pendingFrameCount = 0
        maxFrameCount = 0
        autoStopCompletion = nil
        
        linkBlockingQueue.clear()
        thread?.cancel()
        thread = nil
    }
    
    private func cleanupResources() {
        cleanupRecordingResources()
        frameDataPool = nil
        metalBufferPool = nil
    }
    
    private func copyBGRA(
        src: UnsafePointer<UInt8>,
        dst: UnsafeMutablePointer<UInt8>,
        width: Int,
        height: Int,
        dstBytesPerRow: Int
    ) {
        let srcBytesPerRow = width * 4
        for y in 0..<height {
            let srcRowStart = y * srcBytesPerRow
            let dstRowStart = y * dstBytesPerRow
            memcpy(dst + dstRowStart, src + srcRowStart, srcBytesPerRow)
        }
    }
    
    private func createRealVideoSettings() -> [String: Any] {
        return [
            AVVideoCodecKey: AVVideoCodecType.h264,
            AVVideoWidthKey: videoWidth,
            AVVideoHeightKey: videoHeight,
            AVVideoCompressionPropertiesKey: [
                AVVideoAverageBitRateKey: NSNumber(value: videoWidth * videoHeight * 10),
                AVVideoProfileLevelKey: AVVideoProfileLevelH264High41,
                AVVideoH264EntropyModeKey: AVVideoH264EntropyModeCABAC,
            ],
        ]
    }
    
    private func createPixelBufferAttributes() -> [String: Any] {
        return [
            kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
            kCVPixelBufferWidthKey as String: videoWidth,
            kCVPixelBufferHeightKey as String: videoHeight,
            kCVPixelBufferOpenGLESCompatibilityKey as String: true,
            kCVPixelBufferIOSurfacePropertiesKey as String: [:],
            kCVPixelBufferCGImageCompatibilityKey as String: true,
            kCVPixelBufferCGBitmapContextCompatibilityKey as String: true,
        ]
    }
}

//
//  Pool.swift
//  wscanner
//
//  Created by yunshen on 2025/2/17.
//

import Foundation

/// 定义池操作可能抛出的错误
public enum PoolError: Error {
    case alreadyInPool
}

/// 定义对象池协议
public protocol Pool {
    associatedtype Element: AnyObject
    /// 从池中获取一个对象,如果池中没有则返回 nil
    func acquire() -> Element?
    /// 将一个对象放回池中,如果对象已存在则抛出异常,放回成功返回 true,否则返回 false
    func release(_ instance: Element) throws -> Bool
}

/// 非线程安全的简单对象池实现
//public class SimplePool<T: AnyObject>: Pool {
//    public typealias Element = T
//    /// 池中保存的对象集合(后进先出)
//    private var pool: [T]
//    /// 池的最大容量(池中可保存的对象种类数)
//    private let maxPoolSize: Int
//
//    /// 构造方法,maxPoolSize 必须大于 0
//    public init(maxPoolSize: Int) {
//        precondition(maxPoolSize > 0, "maxPoolSize must be > 0")
//        self.maxPoolSize = maxPoolSize
//        self.pool = []
//    }
//
//    /// 获取一个对象(后进先出)
//    public func acquire() -> T? {
//        if !pool.isEmpty {
//            return pool.removeLast()
//        }
//        return nil
//    }
//
//    /// 将对象放回池中,如果该对象已存在则抛出 PoolError.alreadyInPool 异常,
//    /// 如果池中未满则返回 true,否则返回 false(不放入池中)。
//    public func release(_ instance: T) throws -> Bool {
//        if pool.contains(where: { $0 === instance }) {
//            throw PoolError.alreadyInPool
//        }
//        if pool.count < maxPoolSize {
//            pool.append(instance)
//            return true
//        }
//        return false
//    }
//}

//// 弱引用包装类
//private class WeakRef<T: AnyObject> {
//    weak var value: T?
//    init(value: T) { self.value = value }
//}
//
//public class SimplePool<T: AnyObject>: Pool {
//    public typealias Element = T
//    private var pool: [WeakRef<T>]
//    private let maxPoolSize: Int
//
//    public init(maxPoolSize: Int) {
//        precondition(maxPoolSize > 0, "maxPoolSize must be > 0")
//        self.maxPoolSize = maxPoolSize
//        self.pool = []
//    }
//
//    public func acquire() -> T? {
//        //        while !pool.isEmpty {
//        //            if let instance = pool.removeLast().value {
//        //                return instance
//        //            }
//        //        }
//        //        return nil
//        pool = pool.filter { $0.value != nil }  // 清理所有空引用
//        return pool.popLast()?.value  // 直接取最后一个有效对象
//    }
//
//    public func release(_ instance: T) throws -> Bool {
//        if pool.contains(where: { $0.value === instance }) {
//            throw PoolError.alreadyInPool
//        }
//        if pool.count < maxPoolSize {
//            pool.append(WeakRef(value: instance))
//            return true
//        }
//        return false
//    }
//}

public class SimplePool<T: AnyObject>: Pool {
    public typealias Element = T
    private var pool: Set<WeakRef<T>>
    private let maxPoolSize: Int

    
    public init(maxPoolSize: Int) {
        precondition(maxPoolSize > 0, "maxPoolSize must be > 0")
        self.maxPoolSize = maxPoolSize
        self.pool = []
    }
    
    public func acquire() -> T? {
        while let weakRef = pool.popFirst() {
            if let value = weakRef.value {
                return value
            }
            
            pool = pool.filter( { $0.value != nil } )
        }
        return nil
    }
    
    public func release(_ instance: T) throws -> Bool {
        let weakRef = WeakRef(value: instance)
        if pool.contains(where: { $0.value === instance }) {
            throw PoolError.alreadyInPool
        }
        
        if pool.count < maxPoolSize {
            pool.insert(weakRef)
            return true
        }
        return false
    }
}

private class WeakRef<T: AnyObject>: Hashable {
    weak var value: T?
    private let identifier: ObjectIdentifier
    
    init(value: T) {
        self.value = value
        self.identifier = ObjectIdentifier(value)
    }
    
    static func == (lhs: WeakRef<T>, rhs: WeakRef<T>) -> Bool {
        lhs.identifier == rhs.identifier
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
}

public class SynchronizedPool<T: AnyObject>: SimplePool<T> {
    private let lock = NSLock()

    public override func acquire() -> T? {
        lock.lock()
        defer {
            lock.unlock()
        }
        
        return super.acquire()
    }

    public override func release(_ instance: T) throws -> Bool {
        lock.lock()
        defer {
            lock.unlock()
        }
        
        return try super.release(instance)
    }
}

public class StrongSimplePool<T: AnyObject>: Pool {
    public typealias Element = T
    private var pool: [T]
    private let maxPoolSize: Int
    
    public init(maxPoolSize: Int) {
        precondition(maxPoolSize > 0, "maxPoolSize must be > 0")
        self.maxPoolSize = maxPoolSize
        self.pool = []
    }
    
    public func acquire() -> T? {
        if !pool.isEmpty {
            return pool.removeLast()
        }
        return nil
    }
    
    public func release(_ instance: T) throws -> Bool {
        if pool.contains(where: { $0 === instance }) {
            throw PoolError.alreadyInPool
        }
        if pool.count < maxPoolSize {
            pool.append(instance)
            return true
        }
        return false
    }
}

// MARK: - 线程安全的强引用池
public class StrongSynchronizedPool<T: AnyObject>: StrongSimplePool<T> {
    private let lock = NSLock()
    
    public override func acquire() -> T? {
        lock.lock()
        defer { lock.unlock() }
        return super.acquire()
    }
    
    public override func release(_ instance: T) throws -> Bool {
        lock.lock()
        defer { lock.unlock() }
        return try super.release(instance)
    }
}

//
//  YSVideoRecoder2.swift
//  wscanner
//
//  Created by yunshen on 2025/9/5.
//

import AVFoundation
import Foundation
import MetalKit
internal import common

protocol OnRecordFinishDelegate: AnyObject {
    func onRecordFinish(isSuccess: Bool, outPath: URL?)
    func onRecordStart()
    func onRecordProgress(progress: CGFloat)
    func onRecordFrameIndex(index: Int)
}

protocol IRecoder: AnyObject {
    var frameCount: Int64 { get }
    func setDelegate(delegate: OnRecordFinishDelegate)
    func configureSize(width: Int, height: Int)
    func startRecording(
        outputURL: URL,
        playbackSpeed: Double,
        maxFrames: Int64
    )
    func stopRecording()
    func captureFrame(from texture: MTLTexture)
    func isDecodering() -> Bool
}

class ReusableFrameData {
    var pixelData: UnsafeMutablePointer<UInt8>
    let capacity: Int
    var presentationTime: CMTime = .zero
    var width: Int = 0
    var height: Int = 0
    var isValid: Bool = false

    init(capacity: Int) {
        self.capacity = capacity
        self.pixelData = UnsafeMutablePointer<UInt8>.allocate(
            capacity: capacity
        )
        print("ReusableFrameData init - capacity: \(capacity)")
    }

    deinit {
        pixelData.deallocate()
        print("ReusableFrameData deinit - 不应该频繁出现此消息")
    }

    func reset() {
        isValid = false
        presentationTime = .zero
        width = 0
        height = 0
    }

    func copyFrom(
        buffer: UnsafeRawPointer,
        size: Int,
        time: CMTime,
        width: Int,
        height: Int
    ) {
        guard size <= capacity else {
            print("Error: size \(size) > capacity \(capacity)")
            return
        }
        memcpy(pixelData, buffer, size)
        self.presentationTime = time
        self.width = width
        self.height = height
        self.isValid = true
    }
}

// MARK: - 录制状态
private enum RecorderState {
    case idle
    case preparing
    case recording
    case stopping
    case stopped
}

// MARK: - 优化后的 YSVideoRecoder
class YSVideoRecoder: IRecoder {

    // MARK: - Properties

    private weak var delegate: OnRecordFinishDelegate?
    private let device: MTLDevice
    private var captureCommandQueue: MTLCommandQueue?

    // 使用并发队列替代锁,避免死锁
    private let stateQueue = DispatchQueue(
        label: "com.videorecorder.state",
        attributes: .concurrent
    )
    private let recordingQueue = DispatchQueue(
        label: "com.videorecorder.recording",
        qos: .userInitiated
    )

    // 状态管理(私有,通过 stateQueue 访问)
    private var _state: RecorderState = .idle
    private var _frameCount: Int64 = 0
    private var _videoWidth: Int = 0
    private var _videoHeight: Int = 0
    private var _maxFrameCount: Int64 = 0

    // 配置
    private var videoSpeedMultiplier: Double = 1.0
    private var pixelDataSize: Int = 0

    // AVAssetWriter 相关
    private var assetWriter: AVAssetWriter?
    private var assetWriterInput: AVAssetWriterInput?
    private var assetWriterPixelBufferAdaptor:
        AVAssetWriterInputPixelBufferAdaptor?

    // 时间管理
    private var recordingStartTimeAbs: CFAbsoluteTime = 0
    private var lastPresentationTime: CMTime = .zero
    private var firstValidFrameCaptured: Bool = false

    // 帧处理
    private var frameDataPool: StrongSynchronizedPool<ReusableFrameData>!
    private var metalBufferPool: StrongSynchronizedPool<MTLBuffer>!
    private var frameQueue = LinkedBlockingQueue<ReusableFrameData>(
        capacity: 100,  // 降低到100,避免内存占用过大
        target: "YSVideoRecoder",
        isPrint: false
    )
    private var frameProcessingThread: YSThread<ReusableFrameData>?

    // 性能监控
    private var performanceMonitor = PerformanceMonitor()

    // 池配置(优化后的值)
    private let maxFrameDataObjects = 10  // 降低池大小,够用即可
    private let maxMetalBuffers = 6

    // MARK: - Computed Properties (线程安全)

    private var state: RecorderState {
        get { stateQueue.sync { _state } }
        set { stateQueue.async(flags: .barrier) { self._state = newValue } }
    }

    var frameCount: Int64 {
        stateQueue.sync { _frameCount }
    }

    var isRecording: Bool {
        stateQueue.sync { _state == .recording }
    }

    // MARK: - Initialization

    init(device: MTLDevice) {
        self.device = device
        self.captureCommandQueue = device.makeCommandQueue()
        setupResourcePools()
    }

    deinit {
        cleanup()
    }

    // MARK: - Setup

    private func setupResourcePools() {
        frameDataPool = StrongSynchronizedPool<ReusableFrameData>(
            maxPoolSize: maxFrameDataObjects
        )
        metalBufferPool = StrongSynchronizedPool<MTLBuffer>(
            maxPoolSize: maxMetalBuffers
        )
    }

    // MARK: - Public Methods

    func setDelegate(delegate: any OnRecordFinishDelegate) {
        self.delegate = delegate
    }

    func configureSize(width: Int, height: Int) {
        stateQueue.async(flags: .barrier) { [weak self] in
            guard let self = self else { return }

            guard self._state == .idle else {
                print("⚠️ 无法在录制中更改尺寸")
                return
            }

            self._videoWidth = width
            self._videoHeight = height
            self.pixelDataSize = width * height * 4

            // 可选:预热资源池
            self.recordingQueue.async {
                self.warmupPools()
            }
        }
    }

    func startRecording(
        outputURL: URL,
        playbackSpeed: Double = 1.0,
        maxFrames: Int64 = 0
    ) {
        // 异步执行,不阻塞调用线程
        recordingQueue.async { [weak self] in
            self?.performStartRecording(
                outputURL: outputURL,
                playbackSpeed: playbackSpeed,
                maxFrames: maxFrames
            )
        }
    }

    func stopRecording() {
        // 更新状态
        let wasRecording = stateQueue.sync { () -> Bool in
            guard _state == .recording else { return false }
            return true
        }

        guard wasRecording else {
            notifyCompletion(success: false, url: nil)
            return
        }

        stateQueue.async(flags: .barrier) {
            self._state = .stopping
        }

        // 异步停止
        recordingQueue.async { [weak self] in
            self?.performStopRecording()
        }
    }

    func captureFrame(from texture: MTLTexture) {
        // 快速检查状态,不阻塞
        guard stateQueue.sync(execute: { _state == .recording }) else { return }

        // 性能监控
        performanceMonitor.frameStart()

        // 计算时间戳
        let presentationTime = calculatePresentationTime()

        // 获取或创建 Metal 缓冲区
        let metalBuffer = getOrCreateMetalBuffer()
        guard let buffer = metalBuffer,
            let commandBuffer = captureCommandQueue?.makeCommandBuffer(),
            let blitEncoder = commandBuffer.makeBlitCommandEncoder()
        else {
            recycleMetalBuffer(metalBuffer)
            return
        }

        // 配置 GPU 命令
        configureBlit(encoder: blitEncoder, from: texture, to: buffer)
        blitEncoder.endEncoding()

        // 异步处理完成的帧
        commandBuffer.addCompletedHandler { [weak self] _ in
            self?.handleCapturedFrame(
                buffer: buffer,
                presentationTime: presentationTime
            )
        }

        commandBuffer.commit()
    }

    // MARK: - Private Methods

    private func performStartRecording(
        outputURL: URL,
        playbackSpeed: Double,
        maxFrames: Int64
    ) {
        // 检查状态
        let canStart = stateQueue.sync { () -> (Bool, Int, Int) in
            guard _state == .idle else {
                print("⚠️ 当前状态不允许开始录制: \(_state)")
                return (false, 0, 0)
            }
            guard _videoWidth > 0 && _videoHeight > 0 else {
                print("⚠️ 请先配置视频尺寸")
                return (false, 0, 0)
            }
            _state = .preparing
            return (true, _videoWidth, _videoHeight)
        }

        guard canStart.0 else {
            notifyCompletion(success: false, url: nil)
            return
        }

        // 准备录制
        videoSpeedMultiplier = playbackSpeed

        stateQueue.async(flags: .barrier) {
            self._maxFrameCount = maxFrames
            self._frameCount = 0
        }

        // 设置 AVAssetWriter
        guard
            setupAssetWriter(
                outputURL: outputURL,
                width: canStart.1,
                height: canStart.2
            )
        else {
            stateQueue.async(flags: .barrier) { self._state = .idle }
            notifyCompletion(success: false, url: nil)
            return
        }

        // 重置状态
        resetRecordingState()

        // 启动帧处理线程
        startFrameProcessing()

        // 更新状态为录制中
        stateQueue.async(flags: .barrier) {
            self._state = .recording
        }

        // 通知开始
        DispatchQueue.main.async { [weak self] in
            self?.delegate?.onRecordStart()
        }

        print("✅ 录制开始")
    }

    private func performStopRecording() {
        print("⏹ 正在停止录制...")

        // 停止接收新帧
        frameQueue.interrupt()

        // 等待队列清空
        waitForQueueEmpty(timeout: 5.0)

        // 停止处理线程
        frameProcessingThread?.cancel()
        frameProcessingThread = nil

        // 完成写入
        finishAssetWriting()

        // 清理资源
        cleanup()

        // 更新状态
        stateQueue.async(flags: .barrier) {
            self._state = .idle
        }

        print("✅ 录制停止完成")
    }

    private func setupAssetWriter(outputURL: URL, width: Int, height: Int)
        -> Bool
    {
        do {
            // 删除已存在的文件
            if FileManager.default.fileExists(atPath: outputURL.path) {
                try FileManager.default.removeItem(at: outputURL)
            }

            // 创建 writer
            assetWriter = try AVAssetWriter(
                outputURL: outputURL,
                fileType: .mp4
            )

            // 视频设置
            let videoSettings: [String: Any] = [
                AVVideoCodecKey: AVVideoCodecType.h264,
                AVVideoWidthKey: width,
                AVVideoHeightKey: height,
                AVVideoCompressionPropertiesKey: [
                    AVVideoAverageBitRateKey: width * height * 10,
                    AVVideoProfileLevelKey: AVVideoProfileLevelH264High41,
                    AVVideoMaxKeyFrameIntervalKey: 30,
                    AVVideoExpectedSourceFrameRateKey: 30,
                ],
            ]

            // 创建输入
            assetWriterInput = AVAssetWriterInput(
                mediaType: .video,
                outputSettings: videoSettings
            )
            assetWriterInput?.expectsMediaDataInRealTime = true

            // 像素缓冲适配器
            let pixelBufferAttributes: [String: Any] = [
                kCVPixelBufferPixelFormatTypeKey as String:
                    kCVPixelFormatType_32BGRA,
                kCVPixelBufferWidthKey as String: width,
                kCVPixelBufferHeightKey as String: height,
                kCVPixelBufferIOSurfacePropertiesKey as String: [:],
                kCVPixelBufferMetalCompatibilityKey as String: true,
            ]

            assetWriterPixelBufferAdaptor =
                AVAssetWriterInputPixelBufferAdaptor(
                    assetWriterInput: assetWriterInput!,
                    sourcePixelBufferAttributes: pixelBufferAttributes
                )

            // 添加输入并开始
            guard let writer = assetWriter,
                let input = assetWriterInput,
                writer.canAdd(input)
            else {
                return false
            }

            writer.add(input)

            guard writer.startWriting() else {
                print("⚠️ AVAssetWriter startWriting 失败")
                return false
            }

            writer.startSession(atSourceTime: .zero)
            return true

        } catch {
            print("⚠️ 设置 AVAssetWriter 失败: \(error)")
            return false
        }
    }

    private func startFrameProcessing() {
        frameQueue.clear()
        frameQueue.reset()

        frameProcessingThread = YSThread(
            queue: frameQueue,
            name: "FrameProcessor",
            run: { [weak self] frameData in
                self?.processFrame(frameData)
            },
            timeout: 30,
            onTimeout: { [weak self] in
                print("⚠️ 帧处理超时")
                self?.stopRecording()
            }
        )

        frameProcessingThread?.start()
    }

    private func processFrame(_ frameData: ReusableFrameData) {
        defer {
            frameData.reset()
            _ = try? frameDataPool.release(frameData)
        }

        guard frameData.isValid,
            let input = assetWriterInput,
            let adaptor = assetWriterPixelBufferAdaptor
        else {
            return
        }

        // 等待输入准备好
        waitForInputReady(input: input, timeout: 0.1)

        guard input.isReadyForMoreMediaData else { return }

        // 创建像素缓冲区并写入
        autoreleasepool {
            if let pixelBuffer = createPixelBuffer(from: frameData) {
                if adaptor.append(
                    pixelBuffer,
                    withPresentationTime: frameData.presentationTime
                ) {
                    incrementFrameCount()
                    checkFrameLimit()
                }
            }
        }
    }

    private func handleCapturedFrame(
        buffer: MTLBuffer,
        presentationTime: CMTime
    ) {
        defer {
            recycleMetalBuffer(buffer)
        }

        // 检查状态
        guard stateQueue.sync(execute: { _state == .recording }) else { return }

        // 获取或创建帧数据对象
        guard let frameData = getOrCreateFrameData() else { return }

        // 复制数据
        frameData.copyFrom(
            buffer: buffer.contents(),
            size: pixelDataSize,
            time: presentationTime,
            width: stateQueue.sync { _videoWidth },
            height: stateQueue.sync { _videoHeight }
        )

        // 加入队列
        frameQueue.putWithReplace(frameData) { droppedData in
            print("丢帧")
            droppedData.reset()
            _ = try? self.frameDataPool.release(droppedData)
        }

        performanceMonitor.frameEnd()
    }

    // MARK: - Helper Methods

    private func calculatePresentationTime() -> CMTime {
        if !firstValidFrameCaptured {
            firstValidFrameCaptured = true
            recordingStartTimeAbs = CFAbsoluteTimeGetCurrent()
            return .zero
        }

        let elapsed = CFAbsoluteTimeGetCurrent() - recordingStartTimeAbs
        let adjusted = elapsed * videoSpeedMultiplier
        return CMTime(seconds: adjusted, preferredTimescale: 600)
    }

    private func incrementFrameCount() {
        stateQueue.async(flags: .barrier) {
            self._frameCount += 1

            // 定期通知进度
            if self._frameCount % 2 == 0 {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    let count = self.frameCount
                    self.delegate?.onRecordFrameIndex(index: Int(count))

                    if self._maxFrameCount > 0 {
                        let progress =
                            CGFloat(count) / CGFloat(self._maxFrameCount)
                        self.delegate?.onRecordProgress(progress: progress)
                    }
                }
            }
        }
    }

    private func checkFrameLimit() {
        let (count, max) = stateQueue.sync { (_frameCount, _maxFrameCount) }
        if max > 0 && count >= max {
            print("📊 达到最大帧数: \(max)")
            DispatchQueue.main.async { [weak self] in
                self?.stopRecording()
            }
        }
    }

    private func getOrCreateMetalBuffer() -> MTLBuffer? {
        if let buffer = metalBufferPool.acquire() {
            return buffer
        }
        return device.makeBuffer(
            length: pixelDataSize,
            options: .storageModeShared
        )
    }

    private func recycleMetalBuffer(_ buffer: MTLBuffer?) {
        guard let buffer = buffer else { return }
        _ = try? metalBufferPool.release(buffer)
    }

    private func getOrCreateFrameData() -> ReusableFrameData? {
        if let data = frameDataPool.acquire() {
            data.reset()
            return data
        }
        return ReusableFrameData(capacity: pixelDataSize)
    }

    private func createPixelBuffer(from frameData: ReusableFrameData)
        -> CVPixelBuffer?
    {
        var pixelBuffer: CVPixelBuffer?

        // 尝试从池中获取
        if let pool = assetWriterPixelBufferAdaptor?.pixelBufferPool {
            CVPixelBufferPoolCreatePixelBuffer(
                kCFAllocatorDefault,
                pool,
                &pixelBuffer
            )
        }

        // 如果失败,手动创建
        if pixelBuffer == nil {
            let attrs: [String: Any] = [
                kCVPixelBufferPixelFormatTypeKey as String:
                    kCVPixelFormatType_32BGRA,
                kCVPixelBufferWidthKey as String: frameData.width,
                kCVPixelBufferHeightKey as String: frameData.height,
            ]
            CVPixelBufferCreate(
                kCFAllocatorDefault,
                frameData.width,
                frameData.height,
                kCVPixelFormatType_32BGRA,
                attrs as CFDictionary,
                &pixelBuffer
            )
        }

        guard let buffer = pixelBuffer else { return nil }

        // 复制数据
        CVPixelBufferLockBaseAddress(buffer, [])
        defer { CVPixelBufferUnlockBaseAddress(buffer, []) }

        if let baseAddress = CVPixelBufferGetBaseAddress(buffer) {
            let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)
            let dstBuffer = baseAddress.assumingMemoryBound(to: UInt8.self)

            copyBGRA(
                src: frameData.pixelData,
                dst: dstBuffer,
                width: frameData.width,
                height: frameData.height,
                dstBytesPerRow: bytesPerRow
            )
        }

        return buffer
    }

    private func copyBGRA(
        src: UnsafePointer<UInt8>,
        dst: UnsafeMutablePointer<UInt8>,
        width: Int,
        height: Int,
        dstBytesPerRow: Int
    ) {
        let srcBytesPerRow = width * 4
        for y in 0..<height {
            let srcRowStart = y * srcBytesPerRow
            let dstRowStart = y * dstBytesPerRow
            memcpy(dst + dstRowStart, src + srcRowStart, srcBytesPerRow)
        }
    }

    private func configureBlit(
        encoder: MTLBlitCommandEncoder,
        from texture: MTLTexture,
        to buffer: MTLBuffer
    ) {
        encoder.copy(
            from: texture,
            sourceSlice: 0,
            sourceLevel: 0,
            sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
            sourceSize: MTLSize(
                width: texture.width,
                height: texture.height,
                depth: 1
            ),
            to: buffer,
            destinationOffset: 0,
            destinationBytesPerRow: texture.width * 4,
            destinationBytesPerImage: texture.width * texture.height * 4
        )
    }

    private func waitForInputReady(
        input: AVAssetWriterInput,
        timeout: TimeInterval
    ) {
        let deadline = Date().addingTimeInterval(timeout)
        while !input.isReadyForMoreMediaData && Date() < deadline {
            Thread.sleep(forTimeInterval: 0.001)
        }
    }

    private func waitForQueueEmpty(timeout: TimeInterval) {
        let deadline = Date().addingTimeInterval(timeout)
        while frameQueue.size > 0 && Date() < deadline {
            Thread.sleep(forTimeInterval: 0.01)
        }
    }

    private func finishAssetWriting() {
        assetWriterInput?.markAsFinished()

        let semaphore = DispatchSemaphore(value: 0)
        var success = false
        var outputURL: URL?

        assetWriter?.finishWriting { [weak self] in
            success = (self?.assetWriter?.status == .completed)
            outputURL = success ? self?.assetWriter?.outputURL : nil
            semaphore.signal()
        }

        _ = semaphore.wait(timeout: .now() + 5.0)

        notifyCompletion(success: success, url: outputURL)
    }

    private func warmupPools() {
        // 预创建少量资源
        for _ in 0..<2 {
            if let data = ReusableFrameData(capacity: pixelDataSize)
                as ReusableFrameData?
            {
                _ = try? frameDataPool.release(data)
            }
            if let buffer = device.makeBuffer(
                length: pixelDataSize,
                options: .storageModeShared
            ) {
                _ = try? metalBufferPool.release(buffer)
            }
        }
    }

    private func resetRecordingState() {
        firstValidFrameCaptured = false
        recordingStartTimeAbs = 0
        lastPresentationTime = .zero
        performanceMonitor.reset()
    }

    private func cleanup() {
        frameQueue.clear()
        frameProcessingThread?.cancel()
        frameProcessingThread = nil
        assetWriter = nil
        assetWriterInput = nil
        assetWriterPixelBufferAdaptor = nil
        resetRecordingState()
    }

    private func notifyCompletion(success: Bool, url: URL?) {
        DispatchQueue.main.async { [weak self] in
            self?.delegate?.onRecordFinish(isSuccess: success, outPath: url)
        }
    }

    // MARK: - IRecoder Protocol

    func isDecodering() -> Bool {
        return isRecording
    }

    func getCurrentFrameCount() -> Int64 {
        return frameCount
    }

    func getMaxFrameCount() -> Int64 {
        return stateQueue.sync { _maxFrameCount }
    }
}

// MARK: - 性能监控器
private class PerformanceMonitor {
    private var frameStartTime: CFAbsoluteTime = 0
    private var frameTimes: [TimeInterval] = []
    private let maxSamples = 50

    func frameStart() {
        frameStartTime = CFAbsoluteTimeGetCurrent()
    }

    func frameEnd() {
        let duration = CFAbsoluteTimeGetCurrent() - frameStartTime
        frameTimes.append(duration)

        if frameTimes.count > maxSamples {
            frameTimes.removeFirst()
        }

        // 定期输出性能统计
        if frameTimes.count == maxSamples {
            let avg = frameTimes.reduce(0, +) / Double(frameTimes.count) * 1000
            //            let fps = 1.0 / avg
            print("平均 毫秒: \(avg.description)")
            frameTimes.removeAll()
        }
    }

    func reset() {
        frameTimes.removeAll()
        frameStartTime = 0
    }
}

Giscus 未启用:请在 src/site.config.ts 中配置 repoId 与 categoryId。