Swiftgram/TelegramUI/FFMpegAudioFrameDecoder.swift
2018-12-11 17:54:32 +04:00

66 lines
2.5 KiB
Swift

import Foundation
import CoreMedia
import FFMpeg
final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder {
private let codecContext: FFMpegAVCodecContext
private let swrContext: FFMpegSWResample
private let audioFrame: FFMpegAVFrame
private var resetDecoderOnNextFrame = true
init(codecContext: FFMpegAVCodecContext) {
self.codecContext = codecContext
self.audioFrame = FFMpegAVFrame()
self.swrContext = FFMpegSWResample(sourceChannelCount: Int(codecContext.channels()), sourceSampleRate: Int(codecContext.sampleRate()), sourceSampleFormat: codecContext.sampleFormat(), destinationChannelCount: 2, destinationSampleRate: 44100, destinationSampleFormat: FFMPEG_AV_SAMPLE_FMT_S16)
}
func decode(frame: MediaTrackDecodableFrame) -> MediaTrackFrame? {
let status = frame.packet.send(toDecoder: self.codecContext)
if status == 0 {
if self.codecContext.receive(into: self.audioFrame) {
return convertAudioFrame(self.audioFrame, pts: frame.pts, duration: frame.duration)
}
}
return nil
}
func takeRemainingFrame() -> MediaTrackFrame? {
return nil
}
private func convertAudioFrame(_ frame: FFMpegAVFrame, pts: CMTime, duration: CMTime) -> MediaTrackFrame? {
guard let data = self.swrContext.resample(frame) else {
return nil
}
var blockBuffer: CMBlockBuffer?
let bytes = malloc(data.count)!
data.copyBytes(to: bytes.assumingMemoryBound(to: UInt8.self), count: data.count)
let status = CMBlockBufferCreateWithMemoryBlock(nil, bytes, data.count, nil, nil, 0, data.count, 0, &blockBuffer)
if status != noErr {
return nil
}
var timingInfo = CMSampleTimingInfo(duration: duration, presentationTimeStamp: pts, decodeTimeStamp: pts)
var sampleBuffer: CMSampleBuffer?
var sampleSize = data.count
guard CMSampleBufferCreate(nil, blockBuffer, true, nil, nil, nil, 1, 1, &timingInfo, 1, &sampleSize, &sampleBuffer) == noErr else {
return nil
}
let resetDecoder = self.resetDecoderOnNextFrame
self.resetDecoderOnNextFrame = false
return MediaTrackFrame(type: .audio, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: true)
}
func reset() {
self.codecContext.flushBuffers()
self.resetDecoderOnNextFrame = true
}
}