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