Konversi CMSampleBuffer ke .mov saat menyiarkan dengan ReplayKit

Aug 19 2020

Tolong beritahu saya apa yang salah dengan kode saya. Saya mencoba menyiarkan layar saya ke file (newFile.mov) dan kemudian mengirimkannya dengan beberapa data ke server, tetapi tidak menyimpan dan setelah berhenti menangkap, file baja kosong.

Ini adalah kode saya dari BExtensionUpload yang menangkap siaran dan menyimpannya ke file dalam grup tempat saya mendapatkannya dan mengirim ke server

import ReplayKit
class SampleHandler: RPBroadcastSampleHandler {
    let appIdentifier =         "com.group.CY"
    var videoWriterInput:       AVAssetWriterInput!
    var audioWriterInput:       AVAssetWriterInput!
    var videoWriter:            AVAssetWriter!
    var sessionAtSourceTime:    CMTime!
    var outputFileLocation:     URL?
    
    override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
        do {
            outputFileLocation = videoFileLocation()
            videoWriter = try AVAssetWriter(outputURL: outputFileLocation!, fileType: AVFileType.mov)
            
            videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: [
                AVVideoCodecKey : AVVideoCodecType.h264,
                AVVideoWidthKey : 720,
                AVVideoHeightKey : 1280,
                AVVideoCompressionPropertiesKey : [
                    AVVideoAverageBitRateKey : 2300000,
                ],
            ])
            
            videoWriterInput.expectsMediaDataInRealTime = true
            
            if videoWriter.canAdd(videoWriterInput) {
                videoWriter.add(videoWriterInput)
            } else {
                print("no input added")
            }
            
            audioWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: nil)
            audioWriterInput.expectsMediaDataInRealTime = true
            
            if videoWriter.canAdd(audioWriterInput!) {
                videoWriter.add(audioWriterInput!)
            }
            
            videoWriter.startWriting()
        } catch let error {
            debugPrint(error.localizedDescription)
        }
    }
    
    override func broadcastFinished() {
        super.broadcastFinished()
        
        videoWriterInput.markAsFinished()
        videoWriter.finishWriting {
            ()
        }
    }
    
    override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
        super.processSampleBuffer(sampleBuffer, with: sampleBufferType)            
        if writable,
            sessionAtSourceTime == nil {
            sessionAtSourceTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
            videoWriter.startSession(atSourceTime: sessionAtSourceTime!)
        }
        
        switch sampleBufferType {
        case .video:
            if videoWriterInput.isReadyForMoreMediaData {
                videoWriterInput.append(sampleBuffer)
            }
        case .audioApp:
            if audioWriterInput.isReadyForMoreMediaData {
                audioWriterInput?.append(sampleBuffer)
            }
        case .audioMic:
            print("mic")
        @unknown default:
            print("unknown")
        }
    }
    
    func videoFileLocation() -> URL? {
        let fileManager = FileManager.default
        
        do {
            if let container = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appIdentifier) {
                let videoContainer = container.appendingPathComponent("Video")
                try? fileManager.createDirectory(at: videoContainer, withIntermediateDirectories: false, attributes: nil)
                
                let videoOutputUrl = videoContainer.appendingPathComponent("newFile").appendingPathExtension("mov")
                
                if fileManager.fileExists(atPath: videoOutputUrl.path) {
                    try fileManager.removeItem(at: videoOutputUrl)
                }
                
                fileManager.createFile(atPath: videoOutputUrl.path, contents: nil, attributes: nil)
                
                return videoOutputUrl
            }
            
        } catch {
            print(error)
        }
        
        return nil
    }
}

Jawaban

1 AntonDanilov Aug 27 2020 at 07:00

Kode ini berfungsi untuk saya, tetapi selain direktori saya dalam grup aplikasi, saya harus menggunakan PHPhotoLibrary. Direktori grup aplikasi bukan satu-satunya cara untuk membuat file, Anda dapat menggunakan direktori apa pun yang Anda inginkan. Saya berharap ini dapat membantu seseorang.

import ReplayKit
import Photos

class SampleHandler: RPBroadcastSampleHandler {
    
    let appIdentifier = "group.CY"
    let fileManager = FileManager.default
    
    var videoWriterInput: AVAssetWriterInput!
    var microphoneWriterInput: AVAssetWriterInput!
    
    var videoWriter: AVAssetWriter!
    
    var sessionBeginAtSourceTime: CMTime!
    
    var isRecording = false
    
    var outputFileLocation: URL!
    
    override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
        guard !isRecording else { return }
        isRecording = true
        sessionBeginAtSourceTime = nil
        setUpWriter()
    }
    
    func setUpWriter() {
        let width = UIScreen.main.bounds.width * 2
        let height = UIScreen.main.bounds.height * 2
        
        self.outputFileLocation = videoFileLocation()
        
        // Add the video input
        videoWriter = try? AVAssetWriter.init(outputURL: self.outputFileLocation, fileType: AVFileType.mp4)
        let videoCompressionPropertys = [
            AVVideoAverageBitRateKey: width * height * 10.1
        ]
        
        let videoSettings: [String: Any] = [
            AVVideoCodecKey: AVVideoCodecType.h264,
            AVVideoWidthKey: width,
            AVVideoHeightKey: height,
            AVVideoCompressionPropertiesKey: videoCompressionPropertys
        ]
        
        videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
        videoWriterInput.expectsMediaDataInRealTime = true
        
        // Add the microphone input
        var acl = AudioChannelLayout()
        memset(&acl, 0, MemoryLayout<AudioChannelLayout>.size)
        acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
        let audioOutputSettings: [String: Any] =
            [ AVFormatIDKey: kAudioFormatMPEG4AAC,
              AVSampleRateKey : 44100,
              AVNumberOfChannelsKey : 1,
              AVEncoderBitRateKey : 64000,
              AVChannelLayoutKey : Data(bytes: &acl, count: MemoryLayout<AudioChannelLayout>.size)]
        
        microphoneWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: audioOutputSettings)
        microphoneWriterInput.expectsMediaDataInRealTime = true
        
        if videoWriter.canAdd(videoWriterInput) {
            videoWriter.add(videoWriterInput)
        }
        
        if videoWriter.canAdd(microphoneWriterInput) {
            videoWriter.add(microphoneWriterInput)
        }
        
        videoWriter.startWriting()
    }
    
    override func broadcastFinished() {
        guard isRecording else { return }
        isRecording = false
        sessionBeginAtSourceTime = nil
        
        let dispatchGroup = DispatchGroup()
        dispatchGroup.enter()
        
        videoWriterInput.markAsFinished()
        microphoneWriterInput.markAsFinished()
        
        var finishedWriting = false
        videoWriter.finishWriting {
            PHPhotoLibrary.shared().performChanges({
                PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "xxx")
                
                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: self.outputFileLocation)
            }) { completed, error in
                if completed {
                    NSLog("Video \(self.outputFileLocation.path ?? "") has been moved to camera roll")
                }
                
                if error != nil {
                    NSLog("ERROR:::Cannot move the video \(self.outputFileLocation.path ?? "") to camera roll, error: \(error!.localizedDescription)")
                }
                
                finishedWriting = true
            }
            
            while finishedWriting == false {
                //          NSLog("DEBUG:::Waiting to finish writing...")
            }
            
            dispatchGroup.leave()
        }
        
        dispatchGroup.wait() // <= blocks the thread here
    }
    
    override func finishBroadcastWithError(_ error: Error) {
        let e = error
        print(e)
    }
    
    override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
        let writable = canWrite()
        
        if writable, sessionBeginAtSourceTime == nil {
            sessionBeginAtSourceTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
            videoWriter.startSession(atSourceTime: sessionBeginAtSourceTime!)
        }
        
        if writable {
            switch sampleBufferType {
            case .video:
                if videoWriterInput.isReadyForMoreMediaData {
                    videoWriterInput.append(sampleBuffer)
                }
            case .audioApp:
                print("audio")
            case .audioMic:
                if microphoneWriterInput.isReadyForMoreMediaData {
                    microphoneWriterInput.append(sampleBuffer)
                }
            @unknown default:
                print("unknown")
            }
        }
    }
    
    func videoFileLocation() -> URL {
        let documentsPath = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appIdentifier)!
        let videoOutputUrl = documentsPath
            .appendingPathComponent("Library")
            .appendingPathComponent("Caches")
            .appendingPathComponent("mobile")
            .appendingPathExtension("mp4")
        do {
            if fileManager.fileExists(atPath: videoOutputUrl.path) {
                try fileManager.removeItem(at: videoOutputUrl)
            }
        } catch { print(error) }
        
        return videoOutputUrl
    }
    
    func canWrite() -> Bool {
        return videoWriter != nil && isRecording && videoWriter?.status == .writing
    }
}