Resize video avec AVFoundation
Alak
Membre
Bonjour,
J'ai récemment et pour la premiere fois mis les mains dans AVFoundation pour resize une video (avec sa bande son).
Je suis parvenu a quelque chose qui fonctionne mais qui comporte quelques bug qui font crasher mon app.
Ma classe qui gère le resize est composé de 3 methods :
func resizeVideo(originUrl: NSURL, destinationUrl: NSURL, size: CGSize, completion: (success: Bool) -> ()) {
println("Write Started")
self.completion = completion
var error: NSError? = nil;
var videoWriter = AVAssetWriter(URL: destinationUrl, fileType: AVFileTypeMPEG4, error: &error)
let videoCleanApertureSettings = [AVVideoCleanApertureWidthKey: 360, AVVideoCleanApertureHeightKey: 640, AVVideoCleanApertureHorizontalOffsetKey: 10, AVVideoCleanApertureVerticalOffsetKey: 10]
let videoAspectRatioSettings = [AVVideoPixelAspectRatioHorizontalSpacingKey: 3, AVVideoPixelAspectRatioVerticalSpacingKey: 3]
let codecSettings = [AVVideoAverageBitRateKey: 2000000, AVVideoMaxKeyFrameIntervalKey: 1, AVVideoCleanApertureKey: videoCleanApertureSettings]
let audioOutputSettings = [AVFormatIDKey: kAudioFormatMPEG4AAC, AVNumberOfChannelsKey: 1, AVSampleRateKey: 44100, AVEncoderBitRateKey: 64000]
var audioWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioOutputSettings)
audioWriterInput.expectsMediaDataInRealTime = false
let videoSettings = [AVVideoCodecKey: AVVideoCodecH264, AVVideoCompressionPropertiesKey: codecSettings, AVVideoWidthKey: size.width, AVVideoHeightKey: size.height]
var videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings as [NSObject : AnyObject])
assert(videoWriter.canAddInput(videoWriterInput), "fail")
videoWriterInput.expectsMediaDataInRealTime = false
videoWriter.addInput(videoWriterInput)
videoWriter.addInput(audioWriterInput)
var avAsset: AVAsset = AVURLAsset(URL: originUrl, options: nil)
var aerror: NSError? = nil;
var reader = AVAssetReader(asset: avAsset, error: &aerror)
var videoTrack = avAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as! AVAssetTrack
let videoOptions = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]
var asset_reader_output = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoOptions)
var audioTrack = avAsset.tracksWithMediaType(AVMediaTypeAudio)[0] as! AVAssetTrack
var asset_audio_reader_output = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: [AVFormatIDKey: kAudioFormatLinearPCM])
reader.addOutput(asset_reader_output)
reader.addOutput(asset_audio_reader_output)
videoWriter.startWriting()
videoWriter.startSessionAtSourceTime(kCMTimeZero)
reader.startReading()
for (index, output) in enumerate(reader.outputs) {
self.requestMediaDataForTrack(index, writer: videoWriter, reader: reader)
}
}
func requestMediaDataForTrack(i: Int, writer: AVAssetWriter, reader: AVAssetReader) {
var output = reader.outputs[i] as! AVAssetReaderOutput
var input: AVAssetWriterInput = writer.inputs[i] as! AVAssetWriterInput
input.requestMediaDataWhenReadyOnQueue(dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0), usingBlock: { () -> Void in
println("requestMediaDataWhenReadyOnQueue")
while input.readyForMoreMediaData {
var buffer: CMSampleBufferRef?
buffer = output.copyNextSampleBuffer()
println("read")
if reader.status == .Reading && buffer != nil {
println("write")
if !input.appendSampleBuffer(buffer!) {
reader.cancelReading()
}
} else {
input.markAsFinished()
switch reader.status {
case .Reading:
break
case .Completed:
self.complete(writer)
break
case .Cancelled:
writer.cancelWriting()
break
case .Failed, .Unknown:
writer.cancelWriting()
break
}
break
}
}
})
}
func complete(writer: AVAssetWriter) {
writer.finishWritingWithCompletionHandler({ () -> Void in
switch writer.status {
case .Completed:
println("Completed")
if let completion = self.completion {
completion(success: true)
}
default:
println(writer.error)
if let completion = self.completion {
completion(success: false)
}
}
})
}
Et donc de temps en temps j'ai un des deux crash suivant :
Fatal Exception: NSInternalInconsistencyException*** -[AVAssetWriterInput appendSampleBuffer:] A sample buffer cannot be appended when readyForMoreMediaData is NO.
Fatal Exception: NSRangeExceptionCannot remove an observer <AVAssetWriterInputWritingHelper 0x17046c340> for the key path "readyForMoreMediaData" from <AVAssetWriterInputWritingHelper 0x17046c340> because it is not registered as an observer.
Vous auriez une idée, ou une piste sur ce que je fais mal?
Merci d'avance.
Mots clés:
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
J'ai eu pas mal de surprises avec AVFoundation sous OS X avec certains codec.
Notamment, le statut de l'AVItem qui passait sans arrêt de ready à not ready.
Je dirais que tu essaies de lire des données mais que le reader n'est pas prêt à t'en donner. A priori, il faut attendre que le flag repasse à YES.
Là , tu ne donnes pas le code qui fait le KVO, mais ça me semble assez explicite.
Merci de tes réponses, pour le premier j'ai pourtant bien :
Pour le 2eme je n'ai pas de KVO
T'as raison, on dirait qu'une classe interne (AVAssetWriterInputWritingHelper) fait le KVO. Ce qui est encore plus bizarre, c'est que l'instance s'observe elle-même.
As-tu essayé avec des vidéos encodées avec d'autres codecs ? Tu testes sous quel OS ?
C'est sur iOS 8, avec une video record par l'iPhone.
Bon je dois mal m'y prendre je vais tous recommencer.
J'ai une video (j'ai l'url du media qui est sur le disque). Que dois-je utiliser pour resize la video (qui a une bande audio) ?
Alak
Bon pour info j'ai modifié la class "CyanifyOperation" du sample code d'apple : AVReaderWriterOfflineAudioVideoProcessing (que j'ai du repasser en swift 1.2 car il été en 2.0)
https://github.com/Alak/ResizeOperation