video track missed after compression

hello everybody,

I'm writing a video compression module for iOS. Recently we found an issue that, some videos cannot be compressed correctly. Anybody can help me clarify the reason? Not all videos will trigger this (90% will not). The error message is like below:

the phenomenon is the asset.tracks(withMediaType: AVMediaType.video).first will return nil.

Debug trace: Fatal error: Unexpectedly found nil while unwrapping an Optional value: file video_compressor/SwiftVideoCompressPlugin.swift, line 194 2022-06-24 17:14:51.932848-0700 Runner[55911:7600657] Fatal error: Unexpectedly found nil while unwrapping an Optional value: file video_compressor/SwiftVideoCompressPlugin.swift, line 194

And the code spinnet is here:

var frameCount = 0     let compressionOperation = Compression()

    // Compression started //    completion(.onStart)       //    self.getStatusUpdate("Start to compress ...", result)

    let asset = AVURLAsset(url: source)     guard let videoTrack = asset.tracks(withMediaType: AVMediaType.video).first else {       _ = CompressionError(title: "Cannot find video track") //      completion(.onFailure(error))       self.getStatusUpdate("Cannot find video track", result)       return Compression()     }

//    let bitrate = videoTrack.estimatedDataRate

    // Generate a bitrate based on desired quality //    let newBitrate = getBitrate(bitrate: bitrate, quality: quality)     let newBitrate = bps

    // Handle new width and height values     // let videoSize = videoTrack.naturalSize     let newWidth = tWidth     let newHeight = tHeight

    // Total Frames     let durationInSeconds = asset.duration.seconds     let frameRate = videoTrack.nominalFrameRate     let totalFrames = ceil(durationInSeconds * Double(frameRate))

    // Progress     let totalUnits = Int64(totalFrames)     let progress = Progress(totalUnitCount: totalUnits)           // Setup video writer input     let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: getVideoWriterSettings(bitrate: newBitrate, width: newWidth, height: newHeight))     videoWriterInput.expectsMediaDataInRealTime = true     videoWriterInput.transform = videoTrack.preferredTransform

    let videoWriter = try! AVAssetWriter(outputURL: destination, fileType: AVFileType.mov)     videoWriter.add(videoWriterInput)

    // Setup video reader output     let videoReaderSettings:[String : AnyObject] = [       kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) as AnyObject     ]     let videoReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)

    var videoReader: AVAssetReader!     do{       videoReader = try AVAssetReader(asset: asset)     }     catch {       _ = CompressionError(title: error.localizedDescription) //      completion(.onFailure(compressionError))       self.getStatusUpdate("Cannot find video track", result)     }

    videoReader.add(videoReaderOutput)           let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first     //setup audio writer     let audioWriterInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: nil)     audioWriterInput.expectsMediaDataInRealTime = false     videoWriter.add(audioWriterInput)     //setup audio reader     var audioReader: AVAssetReader?     var audioReaderOutput: AVAssetReaderTrackOutput?     if(audioTrack != nil) {       audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack!, outputSettings: nil)       audioReader = try! AVAssetReader(asset: asset)       audioReader?.add(audioReaderOutput!)     }     videoWriter.startWriting()

    //start writing from video reader     videoReader.startReading()     videoWriter.startSession(atSourceTime: CMTime.zero)     let processingQueue = DispatchQueue(label: "processingQueue1")

//    var isFirstBuffer = true     videoWriterInput.requestMediaDataWhenReady(on: processingQueue, using: {() -> Void in       while videoWriterInput.isReadyForMoreMediaData {

        // Observe any cancellation         if compressionOperation.cancel {           videoReader.cancelReading()           videoWriter.cancelWriting() //          completion(.onCancelled)           self.getStatusUpdate("Cancelled", result)           return         }

        // Update progress based on number of processed frames         frameCount += 1         if let handler = progressHandler {           progress.completedUnitCount = Int64(frameCount)           progressQueue.async { handler(progress) }         }

        let sampleBuffer: CMSampleBuffer? = videoReaderOutput.copyNextSampleBuffer()

        if videoReader.status == .reading && sampleBuffer != nil {           videoWriterInput.append(sampleBuffer!)         } else {           videoWriterInput.markAsFinished()                       if videoReader.status == .completed {                           //start writing from audio reader             if(audioReader != nil){

              audioReader!.startReading()               videoWriter.startSession(atSourceTime: CMTime.zero)               let processingQueue = DispatchQueue(label: "processingQueue2")                               audioWriterInput.requestMediaDataWhenReady(on: processingQueue, using: {() -> Void in                 while audioWriterInput.isReadyForMoreMediaData {                   let sampleBuffer:CMSampleBuffer? = audioReaderOutput!.copyNextSampleBuffer()                   if audioReader!.status == .reading && sampleBuffer != nil {                     audioWriterInput.append(sampleBuffer!)                   }                   else {                     audioWriterInput.markAsFinished()                                           if audioReader?.status == .completed {                       videoWriter.finishWriting(completionHandler: {() -> Void in                         self.getMediaInfo(destination.absoluteString, result)                       })                     }                   }                 }               })             }           }         }                 }     })           return compressionOperation

Replies

I recommend using loadTracks(withMediaType:) instead of tracks(withMediaType:), since the latter is being deprecated this year. The loadTracks method will also throw an error if something goes wrong. You can use asset.status(of: .tracks) to query the status of the tracks property at any time.