我想要完成的是通过Core Audio效果单元处理音频数据阵列并获取被操纵的数据(不播放它 - 即离线)。我碰壁了,这可能是我不理解的非常基本的东西。
理想情况下我想拥有的是单个音频单元(如延迟效果)通过渲染回调引入原始数据然后我反复调用该单元上的AudioUnitRender()
,保存生成的缓冲区以备日后使用。所以:{RENDER CALLBACK}->[EFFECT UNIT]->{Render Loop}->{Data}
。但是当我这样做时,无论我在循环中对AudioUnit调用AudioUnitRender()
多少次,渲染回调都只是第一次调用。
我尝试过的事情:
工作:在kAudioUnitSubType_DefaultOutput
上设置渲染回调并调用AudioOutputUnitStart()
。这很好用,并且从扬声器中播放了我的音频数据。
工作:在kAudioUnitSubType_GenericOutput
上设置渲染回调,并在循环中调用AudioUnitRender()
。这似乎有效,并且原始数据的未经修改的副本也很好。
工作:在kAudioUnitSubType_Delay
单元上设置渲染回调,并将其输出连接到kAudioUnitSubType_DefaultOutput
。呼叫AudioOutputUnitStart()
按照预期延迟播放扬声器中的音频数据。
失败最后,我在kAudioUnitSubType_Delay
单元上设置渲染回调,并将其输出连接到kAudioUnitSubType_GenericOutput
。在循环中调用AudioUnitRender()
仅在第一次调用AudioUnitRender()
时调用渲染回调,就像我尝试直接渲染效果时所发生的那样。
我没有从可能指向问题的任何函数调用中获得任何OSStatus错误。有人可以帮助我理解为什么效果不会多次调用渲染回调函数,除非它连接到默认输出?
谢谢!
以下是我上述测试中相关代码的示例。如有必要,我可以提供更多详细信息,但是连接单元的设置代码就在那里。
// Test Functions
// [EFFECT ONLY] - FAILS! - ONLY CALLS RENDER CALLBACK ON FIRST CALL TO RENDER
func TestRenderingEffectOnly() {
var testUnit = CreateUnit(type: .TestEffect)
AddRenderCallbackToUnit(&testUnit, callback: RenderCallback)
RenderUnit(testUnit)
}
// [DEFAULT OUTPUT ONLY] - WORKS!
func TestDefaultOutputPassthrough() {
var testUnit = CreateUnit(type: .DefaultOutput)
AddRenderCallbackToUnit(&testUnit, callback: RenderCallback)
AudioOutputUnitStart(testUnit)
}
// [GENERIC OUTPUT ONLY] - SEEMS TO WORK!
func TestRenderingToGenericOutputOnly() {
var testUnit = CreateUnit(type: .GenericOutput)
AddRenderCallbackToUnit(&testUnit, callback: RenderCallback)
RenderUnit(testUnit)
}
// [EFFECT]->[DEFAULT OUTPUT] - WORKS!
func TestEffectToDefaultOutput() {
var effectUnit = CreateUnit(type: .TestEffect)
var outputUnit = CreateUnit(type: .DefaultOutput)
AddRenderCallbackToUnit(&effectUnit, callback: RenderCallback)
var connection = AudioUnitConnection()
connection.sourceAudioUnit = effectUnit
connection.sourceOutputNumber = 0
connection.destInputNumber = 0
let result = AudioUnitSetProperty(outputUnit, kAudioUnitProperty_MakeConnection, kAudioUnitScope_Input, 0, &connection, UInt32(MemoryLayout<AudioUnitConnection>.stride))
NSLog("connection result = \(result)")
AudioOutputUnitStart(outputUnit)
}
// [EFFECT]->[GENERIC OUTPUT] - FAILS! - ONLY CALLS RENDER CALLBACK ON FIRST CALL TO RENDER
func TestRenderingEffectToGenericOutput() {
var effectUnit = CreateUnit(type: .TestEffect)
var outputUnit = CreateUnit(type: .GenericOutput)
AddRenderCallbackToUnit(&effectUnit, callback: RenderCallback)
var connection = AudioUnitConnection()
connection.sourceAudioUnit = effectUnit
connection.sourceOutputNumber = 0
connection.destInputNumber = 0
let result = AudioUnitSetProperty(outputUnit, kAudioUnitProperty_MakeConnection, kAudioUnitScope_Input, 0, &connection, UInt32(MemoryLayout<AudioUnitConnection>.stride))
NSLog("connection result = \(result)")
// Manually render audio
RenderUnit(outputUnit)
}
// SETUP FUNCTIONS
// AudioUnitRender callback. Read in float data from left and right channel into buffer as necessary
let RenderCallback: AURenderCallback = {(inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData) -> OSStatus in
NSLog("render \(inNumberFrames) frames")
// Load audio data into ioData here… my data is floating point and plays back ok
return noErr
}
// Configure new audio unit
func CreateUnit(type: UnitType) -> AudioUnit {
var unit: AudioUnit? = nil
var outputcd = AudioComponentDescription()
switch type {
case .DefaultOutput:
outputcd.componentType = kAudioUnitType_Output
outputcd.componentSubType = kAudioUnitSubType_DefaultOutput
case .GenericOutput:
outputcd.componentType = kAudioUnitType_Output
outputcd.componentSubType = kAudioUnitSubType_GenericOutput
case .TestEffect:
outputcd.componentType = kAudioUnitType_Effect
outputcd.componentSubType = kAudioUnitSubType_Delay
}
outputcd.componentManufacturer = kAudioUnitManufacturer_Apple
outputcd.componentFlags = 0
outputcd.componentFlagsMask = 0
let comp = AudioComponentFindNext(nil, &outputcd)
if comp == nil {
print("can't get output unit")
exit(-1)
}
let status = AudioComponentInstanceNew(comp!, &unit)
NSLog("new unit status = \(status)")
// Initialize the unit -- not actually sure *when* is best to do this
AudioUnitInitialize(unit!)
return unit!
}
// Attach a callback to an audio unit
func AddRenderCallbackToUnit(_ unit: inout AudioUnit, callback: @escaping AURenderCallback) {
var input = AURenderCallbackStruct(inputProc: callback, inputProcRefCon: &unit)
AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, UInt32(MemoryLayout<AURenderCallbackStruct>.size))
}
// Render up to 'numberOfFramesToRender' frames for testing
func RenderUnit(_ unitToRender: AudioUnit) {
let numberOfFramesToRender = UInt32(20_000) // Incoming data length: 14,463,360
let inUnit = unitToRender
var ioActionFlags = AudioUnitRenderActionFlags()
var inTimeStamp = AudioTimeStamp()
let inOutputBusNumber: UInt32 = 0
let inNumberFrames: UInt32 = 512
var ioData = AudioBufferList.allocate(maximumBuffers: 2)
var currentFrame: UInt32 = 0
while currentFrame < numberOfFramesToRender {
currentFrame += inNumberFrames
NSLog("call render…")
let status = AudioUnitRender(inUnit, &ioActionFlags, &inTimeStamp, inOutputBusNumber, inNumberFrames, ioData.unsafeMutablePointer)
if (status != noErr) {
NSLog("render status = \(status)")
break
}
// Read new buffer data here and save it for later…
}
}
答案 0 :(得分:1)
您可能需要在每次渲染调用之间将代码退出到运行循环。这允许操作系统安排一些时间让音频线程在每个连续的渲染调用之间运行OS音频单元。
答案 1 :(得分:1)
当手动调用AudioUnitRender()
时,我没有增加每个循环的时间戳。播放默认输出节点会自动执行此操作。将inTimeStamp.mSampleTime += Float64(inNumberFrames)
添加到循环中可行!现在,渲染循环可以通过单个AudioUnit处理数据并返回已处理的数据。
我的代码需要更多的工作 - 错误检查,缓冲区索引检查等。但核心功能就在那里。 (CoreAudio在单元级别确实需要更好的文档。)