AVAssetExportSession
で失敗するAVAsset
の書き出しもAVAssetReader
で読み込んで(バッファリングして)、AVAssetWriter
で書き出すと上手く保存できたので忘れないようにメモ。
///
// MARK: ------------------------------ AVAsset
///
///
///
extension AVAsset {
///
// MARK: ------------------------------ func
///
/// Readerで読み込んでWriterで書き出し
///
/// ```
/// asset.wroteTempDir { (err: NSError?, wroteUrl: URL?) in
/// if let err = err {
/// print("\(err.localizedDescription)")
/// } else {
/// /// success
/// }
/// }
/// ```
///
func wroteTempDir(_ cbWrote: ((_ err: NSError?, _ wroteUrl: URL?) -> Void)?) {
let err: NSError = NSError.init(domain: "hoge", code: 999, userInfo: [NSLocalizedDescriptionKey: "エラー説明"])
let wroteUrl: URL = NSHomeDirectory() + "/tmp/wrote.MOV"
///
/// setup reader & writer
///
guard let reader: AVAssetReader = try? AVAssetReader.init(asset: self) else {
cbWrote?(err, nil)
return
}
guard let writer: AVAssetWriter = try? AVAssetWriter(outputURL: wroteUrl, fileType: AVFileType.mov) else {
cbWrote?(err, nil)
return
}
///
/// setup finish closure
///
var audioFinished: Bool = false
var videoFinished: Bool = false
let finishClosure: (() -> Void) = {
if audioFinished == true && videoFinished == true {
writer.finishWriting {
cbWrote?(nil, wroteUrl)
}
reader.cancelReading()
}
}
///
/// prepare reader for video
///
let readerVideoOutput: AVAssetReaderTrackOutput = AVAssetReaderTrackOutput(
track: self.tracks(withMediaType: AVMediaType.video)[0],
outputSettings: [
kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange),
]
)
reader.add(readerVideoOutput)
///
/// prepare reader for audio
///
var readerAudioOutput: AVAssetReaderTrackOutput!
if self.tracks(withMediaType: AVMediaType.audio).count <= 0 {
/// 無音も有り得るので考慮
audioFinished = true
} else {
readerAudioOutput = AVAssetReaderTrackOutput.init(
track: self.tracks(withMediaType: AVMediaType.audio)[0],
outputSettings: [
AVSampleRateKey: 44100,
AVFormatIDKey: kAudioFormatLinearPCM,
]
)
if reader.canAdd(readerAudioOutput) {
reader.add(readerAudioOutput)
} else {
print("Cannot add audio output reader")
audioFinished = true
}
}
///
/// prepare writer input for video
///
let writerVideoInput: AVAssetWriterInput = AVAssetWriterInput.init(
mediaType: AVMediaType.video,
outputSettings: [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: self.tracks(withMediaType: AVMediaType.video)[0].naturalSize.width,
AVVideoHeightKey: self.tracks(withMediaType: AVMediaType.video)[0].naturalSize.height,
AVVideoCompressionPropertiesKey: [
AVVideoAverageBitRateKey: self.tracks(withMediaType: AVMediaType.video)[0].estimatedDataRate,
],
]
)
writerVideoInput.expectsMediaDataInRealTime = false
writerVideoInput.transform = self.tracks(withMediaType: AVMediaType.video)[0].preferredTransform
writer.add(writerVideoInput)
///
/// prepare writer input for audio
///
var writerAudioInput: AVAssetWriterInput! = nil
if self.tracks(withMediaType: AVMediaType.audio).count > 0 {
let formatDesc: [Any] = self.tracks(withMediaType: AVMediaType.audio)[0].formatDescriptions
var channels: UInt32 = 1
var sampleRate: Float64 = 44100.000000
for i in 0 ..< formatDesc.count {
guard let bobTheDesc: UnsafePointer<AudioStreamBasicDescription> = CMAudioFormatDescriptionGetStreamBasicDescription(formatDesc[i] as! CMAudioFormatDescription) else {
continue
}
channels = bobTheDesc.pointee.mChannelsPerFrame
sampleRate = bobTheDesc.pointee.mSampleRate
break
}
writerAudioInput = AVAssetWriterInput.init(
mediaType: AVMediaType.audio,
outputSettings: [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey: channels,
AVSampleRateKey: sampleRate,
AVEncoderBitRateKey: 128000,
]
)
writerAudioInput.expectsMediaDataInRealTime = true
writer.add(writerAudioInput)
}
///
/// write
///
let videoQueue = DispatchQueue.init(label: "videoQueue")
let audioQueue = DispatchQueue.init(label: "audioQueue")
writer.startWriting()
reader.startReading()
writer.startSession(atSourceTime: CMTime.zero)
///
/// write for video
///
writerVideoInput.requestMediaDataWhenReady(on: videoQueue) {
while writerVideoInput.isReadyForMoreMediaData {
autoreleasepool {
let buffer = readerVideoOutput.copyNextSampleBuffer()
if buffer != nil {
writerVideoInput.append(buffer!)
} else {
writerVideoInput.markAsFinished()
DispatchQueue.main.async {
videoFinished = true
finishClosure()
}
}
}
}
}
if writerAudioInput != nil {
writerAudioInput.requestMediaDataWhenReady(on: audioQueue) {
while writerAudioInput.isReadyForMoreMediaData {
autoreleasepool {
let buffer = readerAudioOutput.copyNextSampleBuffer()
if buffer != nil {
writerAudioInput.append(buffer!)
} else {
writerAudioInput.markAsFinished()
DispatchQueue.main.async {
audioFinished = true
finishClosure()
}
}
}
}
}
}
}
こちらの記事が大変参考になりました🙇♂️
https://samkirkiles.svbtle.com/swift-using-avassetwriter-to-compress-video-files-for-network-transfer
https://qiita.com/takecian/items/f66942df058695b46f5f
動画のマスク処理もできそうなので、自分のアプリ「ShapingMovie」もバージョンアップしようと思います✨