HaishinKit.swift/Sources/Codec/AACEncoder.swift

303 lines
11 KiB
Swift

import AVFoundation
protocol AudioEncoderDelegate: class {
func didSetFormatDescription(audio formatDescription: CMFormatDescription?)
func sampleOutput(audio bytes: UnsafeMutablePointer<UInt8>?, count: UInt32, presentationTimeStamp: CMTime)
}
// MARK: -
/**
- seealse:
- https://developer.apple.com/library/ios/technotes/tn2236/_index.html
*/
final class AACEncoder: NSObject {
enum Error: Swift.Error {
case setPropertyError(id: AudioConverterPropertyID, status: OSStatus)
}
static let supportedSettingsKeys: [String] = [
"muted",
"bitrate",
"profile",
"sampleRate", // down,up sampleRate not supported yet #58
"actualBitrate"
]
static let packetSize: UInt32 = 1
static let framesPerPacket: UInt32 = 1024
static let minimumBitrate: UInt32 = 8 * 1024
static let defaultProfile: UInt32 = UInt32(MPEG4ObjectID.AAC_LC.rawValue)
static let defaultBitrate: UInt32 = 32 * 1024
// 0 means according to a input source
static let defaultChannels: UInt32 = 0
// 0 means according to a input source
static let defaultSampleRate: Double = 0
static let defaultMaximumBuffers: Int = 1
static let defaultBufferListSize: Int = AudioBufferList.sizeInBytes(maximumBuffers: 1)
#if os(iOS)
static let defaultInClassDescriptions: [AudioClassDescription] = [
AudioClassDescription(mType: kAudioEncoderComponentType, mSubType: kAudioFormatMPEG4AAC, mManufacturer: kAppleSoftwareAudioCodecManufacturer),
AudioClassDescription(mType: kAudioEncoderComponentType, mSubType: kAudioFormatMPEG4AAC, mManufacturer: kAppleHardwareAudioCodecManufacturer)
]
#else
static let defaultInClassDescriptions: [AudioClassDescription] = []
#endif
@objc var muted: Bool = false
@objc var bitrate: UInt32 = AACEncoder.defaultBitrate {
didSet {
guard bitrate != oldValue else {
return
}
lockQueue.async {
if let format = self._inDestinationFormat {
self.setBitrateUntilNoErr(self.bitrate * format.mChannelsPerFrame)
}
}
}
}
@objc var profile: UInt32 = AACEncoder.defaultProfile
@objc var sampleRate: Double = AACEncoder.defaultSampleRate
@objc var actualBitrate: UInt32 = AACEncoder.defaultBitrate {
didSet {
logger.info("\(actualBitrate)")
}
}
var channels: UInt32 = AACEncoder.defaultChannels
var inClassDescriptions: [AudioClassDescription] = AACEncoder.defaultInClassDescriptions
var formatDescription: CMFormatDescription? {
didSet {
if !CMFormatDescriptionEqual(formatDescription, otherFormatDescription: oldValue) {
delegate?.didSetFormatDescription(audio: formatDescription)
}
}
}
var lockQueue = DispatchQueue(label: "com.haishinkit.HaishinKit.AACEncoder.lock")
weak var delegate: AudioEncoderDelegate?
internal(set) var running: Bool = false
private var maximumBuffers: Int = AACEncoder.defaultMaximumBuffers
private var bufferListSize: Int = AACEncoder.defaultBufferListSize
private var currentBufferList: UnsafeMutableAudioBufferListPointer?
private var inSourceFormat: AudioStreamBasicDescription? {
didSet {
logger.info("\(String(describing: self.inSourceFormat))")
guard let inSourceFormat: AudioStreamBasicDescription = self.inSourceFormat else {
return
}
let nonInterleaved: Bool = inSourceFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved != 0
maximumBuffers = nonInterleaved ? Int(inSourceFormat.mChannelsPerFrame) : AACEncoder.defaultMaximumBuffers
bufferListSize = nonInterleaved ? AudioBufferList.sizeInBytes(maximumBuffers: maximumBuffers) : AACEncoder.defaultBufferListSize
}
}
private var _inDestinationFormat: AudioStreamBasicDescription?
private var inDestinationFormat: AudioStreamBasicDescription {
get {
if _inDestinationFormat == nil {
_inDestinationFormat = AudioStreamBasicDescription(
mSampleRate: sampleRate == 0 ? inSourceFormat!.mSampleRate : sampleRate,
mFormatID: kAudioFormatMPEG4AAC,
mFormatFlags: profile,
mBytesPerPacket: 0,
mFramesPerPacket: AACEncoder.framesPerPacket,
mBytesPerFrame: 0,
mChannelsPerFrame: (channels == 0) ? inSourceFormat!.mChannelsPerFrame : channels,
mBitsPerChannel: 0,
mReserved: 0
)
CMAudioFormatDescriptionCreate(
allocator: kCFAllocatorDefault, asbd: &_inDestinationFormat!, layoutSize: 0, layout: nil, magicCookieSize: 0, magicCookie: nil, extensions: nil, formatDescriptionOut: &formatDescription
)
}
return _inDestinationFormat!
}
set {
_inDestinationFormat = newValue
}
}
private var inputDataProc: AudioConverterComplexInputDataProc = {(
converter: AudioConverterRef,
ioNumberDataPackets: UnsafeMutablePointer<UInt32>,
ioData: UnsafeMutablePointer<AudioBufferList>,
outDataPacketDescription: UnsafeMutablePointer<UnsafeMutablePointer<AudioStreamPacketDescription>?>?,
inUserData: UnsafeMutableRawPointer?) in
return Unmanaged<AACEncoder>.fromOpaque(inUserData!).takeUnretainedValue().onInputDataForAudioConverter(
ioNumberDataPackets,
ioData: ioData,
outDataPacketDescription: outDataPacketDescription
)
}
private var _converter: AudioConverterRef?
private var converter: AudioConverterRef {
var status: OSStatus = noErr
if _converter == nil {
status = AudioConverterNewSpecific(
&inSourceFormat!,
&inDestinationFormat,
UInt32(inClassDescriptions.count),
&inClassDescriptions,
&_converter
)
setBitrateUntilNoErr(bitrate * inDestinationFormat.mChannelsPerFrame)
}
if status != noErr {
logger.warn("\(status)")
}
return _converter!
}
func encodeSampleBuffer(_ sampleBuffer: CMSampleBuffer) {
guard let format: CMAudioFormatDescription = sampleBuffer.formatDescription, running else {
return
}
if inSourceFormat == nil {
inSourceFormat = format.streamBasicDescription?.pointee
}
var blockBuffer: CMBlockBuffer?
currentBufferList = AudioBufferList.allocate(maximumBuffers: maximumBuffers)
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
sampleBuffer,
bufferListSizeNeededOut: nil,
bufferListOut: currentBufferList!.unsafeMutablePointer,
bufferListSize: bufferListSize,
blockBufferAllocator: kCFAllocatorDefault,
blockBufferMemoryAllocator: kCFAllocatorDefault,
flags: 0,
blockBufferOut: &blockBuffer
)
if blockBuffer == nil {
logger.warn("IllegalState for blockBuffer")
return
}
if muted {
for i in 0..<currentBufferList!.count {
memset(currentBufferList![i].mData, 0, Int(currentBufferList![i].mDataByteSize))
}
}
var finished: Bool = false
repeat {
var ioOutputDataPacketSize: UInt32 = 1
let dataLength: Int = blockBuffer!.dataLength
let outOutputData: UnsafeMutableAudioBufferListPointer = AudioBufferList.allocate(maximumBuffers: 1)
outOutputData[0].mNumberChannels = inDestinationFormat.mChannelsPerFrame
outOutputData[0].mDataByteSize = UInt32(dataLength)
outOutputData[0].mData = UnsafeMutableRawPointer.allocate(byteCount: dataLength, alignment: 0)
let status: OSStatus = AudioConverterFillComplexBuffer(
converter,
inputDataProc,
Unmanaged.passUnretained(self).toOpaque(),
&ioOutputDataPacketSize,
outOutputData.unsafeMutablePointer,
nil
)
switch status {
// kAudioConverterErr_InvalidInputSize: perhaps mistake. but can support macOS BuiltIn Mic #61
case noErr, kAudioConverterErr_InvalidInputSize:
delegate?.sampleOutput(
audio: outOutputData[0].mData?.assumingMemoryBound(to: UInt8.self),
count: outOutputData[0].mDataByteSize,
presentationTimeStamp: sampleBuffer.presentationTimeStamp
)
case -1:
finished = true
default:
finished = true
}
for i in 0..<outOutputData.count {
free(outOutputData[i].mData)
}
free(outOutputData.unsafeMutablePointer)
} while !finished
}
func invalidate() {
lockQueue.async {
self.inSourceFormat = nil
self._inDestinationFormat = nil
if let converter: AudioConverterRef = self._converter {
AudioConverterDispose(converter)
}
self._converter = nil
}
}
func onInputDataForAudioConverter(
_ ioNumberDataPackets: UnsafeMutablePointer<UInt32>,
ioData: UnsafeMutablePointer<AudioBufferList>,
outDataPacketDescription: UnsafeMutablePointer<UnsafeMutablePointer<AudioStreamPacketDescription>?>?) -> OSStatus {
guard let bufferList: UnsafeMutableAudioBufferListPointer = currentBufferList else {
ioNumberDataPackets.pointee = 0
return -1
}
memcpy(ioData, bufferList.unsafePointer, bufferListSize)
ioNumberDataPackets.pointee = 1
free(bufferList.unsafeMutablePointer)
currentBufferList = nil
return noErr
}
private func setBitrateUntilNoErr(_ bitrate: UInt32) {
do {
try setProperty(id: kAudioConverterEncodeBitRate, data: bitrate * inDestinationFormat.mChannelsPerFrame)
actualBitrate = bitrate
} catch {
if AACEncoder.minimumBitrate < bitrate {
setBitrateUntilNoErr(bitrate - AACEncoder.minimumBitrate)
} else {
actualBitrate = AACEncoder.minimumBitrate
}
}
}
private func setProperty<T>(id: AudioConverterPropertyID, data: T) throws {
guard let converter: AudioConverterRef = _converter else {
return
}
let size = UInt32(MemoryLayout<T>.size)
var buffer = data
let status = AudioConverterSetProperty(converter, id, size, &buffer)
guard status == 0 else {
throw Error.setPropertyError(id: id, status: status)
}
}
}
extension AACEncoder: Running {
// MARK: Running
func startRunning() {
lockQueue.async {
self.running = true
}
}
func stopRunning() {
lockQueue.async {
if let convert: AudioQueueRef = self._converter {
AudioConverterDispose(convert)
self._converter = nil
}
self.inSourceFormat = nil
self.formatDescription = nil
self._inDestinationFormat = nil
self.currentBufferList = nil
self.running = false
}
}
}