mp4ファイルから動画、音声データ編集なしでh264,aacデータのまま取ってサーバに送信したいですが、実装してみたら結構ハマったところがあったので、メモ用として整理しました。
ポイント
- AVAssetReaderTrackOutputのoutputSettings nil設定することで処理していないmp4生動画と音声データが取れる
- samplebuffer一つでh264 frame一つになるが、aacは複数の可能性がある!!!
コード説明
assetからvideo track、audio track取得
let asset = AVURLAsset(url: url)
guard let assetReader = try? AVAssetReader(asset: asset) else {
print("Unable to create AVAssetReader")
return
}
// Get video track
let videoTrack = asset.tracks(withMediaType: .video).first
// outputSettingsがnil設定することで動画のもとエンコードされたh264データがとれます
let videoReaderOutput = AVAssetReaderTrackOutput(track: videoTrack!, outputSettings: nil)
videoReaderOutput.alwaysCopiesSampleData = false
assetReader.add(videoReaderOutput)
let audioTrack = asset.tracks(withMediaType: .audio).first
// outputSettingsがnil設定することでaudioのもとエンコードされたaacデータがとれます
let audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack!, outputSettings: nil)
audioReaderOutput.alwaysCopiesSampleData = false
assetReader.add(audioReaderOutput)
読み込み
// 読み込み開始
assetReader?.startReading()
// loopしてvideo,audio buffer続ける
let videoSampleBuffer = self.videoReaderOutput?.copyNextSampleBuffer()
let audioSampleBuffer = self.audioReaderOutput?.copyNextSampleBuffer()
SampleBuffer処理
動画buffer処理
注: 1sampleBufferが1frameになります
samplebufferからdata取得
extension CMBlockBuffer {
var data: Data? {
var length: Int = 0
var pointer: UnsafeMutablePointer<Int8>?
guard CMBlockBufferGetDataPointer(self, atOffset: 0, lengthAtOffsetOut: nil, totalLengthOut: &length, dataPointerOut: &pointer) == noErr,
let p = pointer else {
return nil
}
return Data(bytes: p, count: length)
}
var length: Int {
return CMBlockBufferGetDataLength(self)
}
}
h264関連データ取得
// h264 data
guard let bufferData = CMSampleBufferGetDataBuffer(buffer)?.data else {
return nil
}
guard let attachments = CMSampleBufferGetSampleAttachmentsArray(buffer, createIfNecessary: true) as? NSArray else { return nil }
guard let attachment = attachments[0] as? NSDictionary else {
return nil
}
// key frameかどうか
let isKeyframe = !(attachment[kCMSampleAttachmentKey_DependsOnOthers] as? Bool ?? true)
// presentationTimeStamp
let pts = buffer.presentationTimeStamp.seconds.isFinite ? UInt64(buffer.presentationTimeStamp.seconds * 1000) : 0
// decodeTimeStamp
let dts = buffer.decodeTimeStamp.seconds.isFinite ? UInt64(buffer.decodeTimeStamp.seconds * 1000) : pts
let frame = VideoFrame(data: bufferData, isKeyframe: isKeyframe, pts: pts, dts: dts)
return frame
音声buffer処理
注: samplebuffer一つでaac frame複数の可能性があります
samplebuffer内のすべてのaacデータ取得
func getAACData(from sampleBuffer: CMSampleBuffer) -> Data? {
guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer) else {
print("Could not get block buffer from sample buffer")
return nil
}
let length = CMBlockBufferGetDataLength(blockBuffer)
var dataPointer: UnsafeMutablePointer<Int8>? = nil
CMBlockBufferGetDataPointer(blockBuffer, atOffset: 0, lengthAtOffsetOut: nil, totalLengthOut: nil, dataPointerOut: &dataPointer)
if let dataPointer = dataPointer {
return Data(bytes: dataPointer, count: length)
}
return nil
}
samplebuffer内のaacデータ処理
// samplebuffer内で複数のaac sampleデータがあるので、その数を取得
let numSamplesInBuffer = CMSampleBufferGetNumSamples(buffer);
// samplebufferから入ってるすべてのaac data取り出す
guard let aacData = getAACData(from: buffer) else { return nil}
var audioFrames: [AudioFrame] = []
var offset = 0
for i in 0..<numSamplesInBuffer {
// sample indexからこのsampleのdata size取得
let size = CMSampleBufferGetSampleSize(buffer, at: i)
// sample indexからこのsampleのdata取得
let data = aacData.subdata(in: offset..<offset + size)
// presentationTimeStamp
var timingInfo: CMSampleTimingInfo = CMSampleTimingInfo()
CMSampleBufferGetSampleTimingInfo(buffer, at: i, timingInfoOut: &timingInfo)
let pts = timingInfo.presentationTimeStamp.seconds.isFinite ? UInt64(timingInfo.presentationTimeStamp.seconds * 1000) : 0
let audioFrame = AudioFrame(data: data, pts: pts)
audioFrames.append(audioFrame)
offset += size
}