1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AVFoundationつかって動画のh264,aacデータ直読み込み

Last updated at Posted at 2023-08-26

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
}
1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?