AURenderCallback в Swift

Я создаю приложение, которое использует аудиоустройства, и хотя в Objective-C есть много примеров кода (в том числе собственный aurioTouch от Apple и другие), я пытаюсь все это кодировать в Swift.

Мне удалось настроить мой AUGraph и запустить через него звук, но я не могу понять синтаксис обратных вызовов рендеринга. Я попробовал несколько методов:

Способ 1. Создайте AURenderCallback напрямую

let render : AURenderCallback = { (
    inRefCon: UnsafeMutablePointer<Void>,
    ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
    inTimeStamp: UnsafePointer<AudioTimeStamp>,
    inBufNumber: UInt32,
    inNumberFrames: UInt32,
    ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus in

    return noErr
}

Я ничего не делаю в этом обратном вызове, кроме возврата noErr на этом этапе, так как я просто пытаюсь заставить его работать. Однако компилятор возвращает следующую ошибку:

(UnsafeMutablePointer, UnsafeMutablePointer, UnsafePointer, UInt32, UInt32, UnsafeMutablePointer) -> OSStatus 'не может быть преобразовано в'AURenderCallback

Определение AURenderCallback в документации таково:

typealias AURenderCallback = (UnsafeMutablePointer<Void>,UnsafeMutablePointer<AudioUnitRenderActionFlags>,UnsafePointer<AudioTimeStamp>, UInt32, UInt32,UnsafeMutablePointer<AudioBufferList>) -> OSStatus

Это похоже на то, что я ввел, хотя, возможно, я не понимаю, о чем просит документация.

Способ 2. Создайте функцию, представляющую AURenderCallback

func render(
    inRefCon: UnsafeMutablePointer<Void>,
    ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
    inTimeStamp: UnsafePointer<AudioTimeStamp>,
    inBufNumber: UInt32,
    inNumberFrames: UInt32,
    ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus {

    return noErr
}

Это не дает никаких ошибок как функция, но когда я помещаю это в AURenderCallbackStruct в параметре inputProc, я получаю ошибку:

Не удается найти инициализатор для типа 'AURenderCallbackStruct', который принимает список аргументов типа '(inputProc: (UnsafeMutablePointer, UnsafeMutablePointer, UnsafePointer, UInt32, UInt32, UnsafeMutablePointer) -> OSStatus, inputProcRefCon: n>

Я не нашел много примеров создания AURenderCallbacks в Swift, и, кажется, существует большая разница в синтаксисе по сравнению с Objective-C. Любая помощь будет оценена.

2 ответа

Решение

Я только что нашел ваш пост, пытаясь выяснить то же самое (нелегко найти примеры кода и примеры, объединяющие CoreAudio/Audio Unit и Swift).

Посмотрев на этот репозиторий и прочитав (несколько раз:-)) документацию Apple об использовании Swift с Какао и Objective-C, мне удалось собрать что-то вместе. Как говорится в разделе о Function Pointers

При вызове функции, которая принимает аргумент указателя на функцию, вы можете передать функцию Swift верхнего уровня, литерал замыкания или nil.

Так. За пределами моего класса у меня есть метод, который выглядит следующим образом:

func renderCallback(inRefCon:UnsafeMutablePointer<Void>,
ioActionFlags:UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp:UnsafePointer<AudioTimeStamp>,
inBusNumber:UInt32,
inNumberFrames:UInt32,
ioData:UnsafeMutablePointer<AudioBufferList>) -> OSStatus {
    let delegate = unsafeBitCast(inRefCon, AURenderCallbackDelegate.self)
    let result = delegate.performRender(ioActionFlags,
        inTimeStamp: inTimeStamp,
        inBusNumber: inBusNumber,
        inNumberFrames: inNumberFrames,
        ioData: ioData)
    return result
}

Как видите, я просто вызываю делегата здесь. Этот делегат объявлен как так (также вне класса, но вы уже знали, что:-))

@objc protocol AURenderCallbackDelegate {
func performRender(ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
    inTimeStamp: UnsafePointer<AudioTimeStamp>,
    inBusNumber: UInt32,
    inNumberFrames: UInt32,
    ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus
}

Это позволяет мне "вернуться в свой класс" в соответствии с AURenderCallbackDelegate вот так:

class AudioUnitGraphManager: NSObject, AURenderCallbackDelegate

И затем реализация renderCallback метод в моем AudioUnitGraphManager учебный класс

func performRender(ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, inTimeStamp: UnsafePointer<AudioTimeStamp>, inBusNumber: UInt32, inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus {
    print("Hello there!")
    return noErr
}

Последний кусочек головоломки состоит в том, чтобы фактически включить обратный вызов рендеринга уведомлений, который я делаю так:

AudioUnitAddRenderNotify(mixerUnit, renderCallback, UnsafeMutablePointer(unsafeAddressOf(self)))

Надеюсь, это даст вам возможность продолжить борьбу.

Изменения в Swift 3

В Swift 3 декларация для AURenderCallback изменился на это:

typealias AURenderCallback = (UnsafeMutableRawPointer, UnsafeMutablePointer<AudioUnitRenderActionFlags>, UnsafePointer<AudioTimeStamp>, UInt32, UInt32, UnsafeMutablePointer<AudioBufferList>?) -> OSStatus

Обратите внимание, что последний параметр сейчас UnsafeMutablePointer<AudioBufferList>? по сравнению с UnsafeMutablePointer<AudioBufferList> до (это необязательно сейчас).

Это означает, что код теперь выглядит следующим образом.

renderCallback функция

func renderCallback(inRefCon:UnsafeMutablePointer<Void>,
ioActionFlags:UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp:UnsafePointer<AudioTimeStamp>,
inBusNumber:UInt32,
inNumberFrames:UInt32,
ioData:UnsafeMutablePointer<AudioBufferList>?) -> OSStatus {
    let delegate = unsafeBitCast(inRefCon, AURenderCallbackDelegate.self)
    let result = delegate.performRender(ioActionFlags,
        inTimeStamp: inTimeStamp,
        inBusNumber: inBusNumber,
        inNumberFrames: inNumberFrames,
        ioData: ioData)
    return result
}

AURenderCallbackDelegate протокол

@objc protocol AURenderCallbackDelegate {
func performRender(ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
    inTimeStamp: UnsafePointer<AudioTimeStamp>,
    inBusNumber: UInt32,
    inNumberFrames: UInt32,
    ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus
}

Фактическая реализация performRender

    func performRender(ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, inTimeStamp: UnsafePointer<AudioTimeStamp>, inBusNumber: UInt32, inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus {
    print("Hello there!")
    return noErr
}

Включение обратного вызова уведомлений рендера

AudioUnitAddRenderNotify(mixerUnit!, renderCallback, Unmanaged.passUnretained(self).toOpaque())

Я немного опоздал на вечеринку, но я нашел способ, который может быть более простым, чем приведенный выше ответ. Мы можем использовать указатель, который вы можете установить в callbackStruct. передается вашей функции обратного вызова, поэтому, если вы установите к вашей ссылке на класс, вы можете напрямую «найти свой путь обратно в класс» без необходимости идти в цикле с делегатами. Вот как я это сделал:

      /* below code is inside some function in MyClass */ 

var callbackStruct = AURenderCallbackStruct()

// set inRefCon to reference to self by casting to pointer
callbackStruct.inputProcRefCon = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())

// create our C closure
callbackStruct.inputProc = {
             (inRefCon : UnsafeMutableRawPointer,
              ioActionFlags : UnsafeMutablePointer<AudioUnitRenderActionFlags>,
              inTimeStamp : UnsafePointer<AudioTimeStamp>,
              inBusNumber : UInt32,
              inNumberFrames : UInt32,
              ioData : UnsafeMutablePointer<AudioBufferList>?) -> OSStatus in
            
            // get reference to my class by de-referencing inRefCon
            let _self = Unmanaged<MyClass>.fromOpaque(inRefCon).takeUnretainedValue()
            // time to profit with reference to our class _self
            // ...
            return 0
}

// set callback
AudioUnitSetProperty(audioUnit!,
                     kAudioUnitProperty_SetRenderCallback,
                     kAudioUnitScope_Global,
                     0,
                     &callbackStruct,
                     UInt32(MemoryLayout.size(ofValue: callbackStruct)))

Другие вопросы по тегам