Проблемы с аудиовыходом с низкой задержкой на iOS (также как превратить AUAudioUnit sampleRate, MaximumFramesToRender и ioBufferDuration в отправку) - PullRequest
0 голосов
/ 08 ноября 2019

Хорошо, я явно упускаю какую-то важную часть здесь. Я пытаюсь сделать звук с низкой задержкой через сеть, и мои основные кадры 10 мс. Я ожидал, что это не будет проблемой. Мой целевой телефон - колонки iPhone X - так что моя аппаратная частота дискретизации должна быть установлена ​​на 48000 Гц. Я запрашиваю 10 мс, что является хорошим четным делителем и должно быть 480, 960, 1920 или 3840 в зависимости от того, как вы хотите нарезать кадры / сэмплы / байты.

Тем не менее, для моей жизни я абсолютноне могу заставить iOS делать то, что я считаю нормальным. Я получаю 10.667мс длительности буфера, что нелепо - iOS старается изо всех сил предоставлять мне размеры буфера, которые не являются целыми числами, кратными sampleRate. Хуже того, фрейм выглядит LONG , что означает, что я должен поглощать не один, а два пакета задержки, чтобы иметь возможность заполнить этот буфер. Я не могу заставить MaximumFrameToRender вообще измениться, и система возвращает 0 в качестве частоты дискретизации, даже если она довольно явно рендерится на частоте 48000 Гц.

Я явно упускаю что-то важное--что это такое? Я забыл отключить / подключить что-то, чтобы получить прямое аппаратное сопоставление? (Мой формат - 1, который pcmFormatFloat32 - я бы ожидал, что pcmFormatInt16 или pcmFormatInt32 для отображения непосредственно на аппаратное обеспечение, поэтому что-то в ОС, вероятно, мешает). Или AUAudioUnit просто недоделан, и мне нужно вернуться к более старым, более полезным API? Или я полностью пропустил сюжет, и аудиолюбители с низкой задержкой используют совершенно другой набор функций управления звуком?

Спасибо за помощь - это очень ценится.

Вывод из кода:

2019-11-07 23:28:29.782786-0800 latencytest[3770:50382] Ready to receive user events
2019-11-07 23:28:34.727478-0800 latencytest[3770:50382] Start button pressed
2019-11-07 23:28:34.727745-0800 latencytest[3770:50382] Launching auxiliary thread
2019-11-07 23:28:34.729278-0800 latencytest[3770:50445] Thread main started
2019-11-07 23:28:35.006005-0800 latencytest[3770:50445] Sample rate: 0
2019-11-07 23:28:35.016935-0800 latencytest[3770:50445] Buffer duration: 0.010667
2019-11-07 23:28:35.016970-0800 latencytest[3770:50445] Number of output busses: 2
2019-11-07 23:28:35.016989-0800 latencytest[3770:50445] Max frames: 4096
2019-11-07 23:28:35.017010-0800 latencytest[3770:50445] Can perform output: 1
2019-11-07 23:28:35.017023-0800 latencytest[3770:50445] Output Enabled: 1
2019-11-07 23:28:35.017743-0800 latencytest[3770:50445] Bus channels: 2
2019-11-07 23:28:35.017864-0800 latencytest[3770:50445] Bus format: 1
2019-11-07 23:28:35.017962-0800 latencytest[3770:50445] Bus rate: 0
2019-11-07 23:28:35.018039-0800 latencytest[3770:50445] Sleeping 0
2019-11-07 23:28:35.018056-0800 latencytest[3770:50445] Buffer count: 2 4096
2019-11-07 23:28:36.023220-0800 latencytest[3770:50445] Sleeping 1
2019-11-07 23:28:36.023400-0800 latencytest[3770:50445] Buffer count: 190 389120
2019-11-07 23:28:37.028610-0800 latencytest[3770:50445] Sleeping 2
2019-11-07 23:28:37.028790-0800 latencytest[3770:50445] Buffer count: 378 774144
2019-11-07 23:28:38.033983-0800 latencytest[3770:50445] Sleeping 3
2019-11-07 23:28:38.034142-0800 latencytest[3770:50445] Buffer count: 566 1159168
2019-11-07 23:28:39.039333-0800 latencytest[3770:50445] Sleeping 4
2019-11-07 23:28:39.039534-0800 latencytest[3770:50445] Buffer count: 756 1548288
2019-11-07 23:28:40.041787-0800 latencytest[3770:50445] Sleeping 5
2019-11-07 23:28:40.041943-0800 latencytest[3770:50445] Buffer count: 944 1933312
2019-11-07 23:28:41.042878-0800 latencytest[3770:50445] Sleeping 6
2019-11-07 23:28:41.043037-0800 latencytest[3770:50445] Buffer count: 1132 2318336
2019-11-07 23:28:42.048219-0800 latencytest[3770:50445] Sleeping 7
2019-11-07 23:28:42.048375-0800 latencytest[3770:50445] Buffer count: 1320 2703360
2019-11-07 23:28:43.053613-0800 latencytest[3770:50445] Sleeping 8
2019-11-07 23:28:43.053771-0800 latencytest[3770:50445] Buffer count: 1508 3088384
2019-11-07 23:28:44.058961-0800 latencytest[3770:50445] Sleeping 9
2019-11-07 23:28:44.059119-0800 latencytest[3770:50445] Buffer count: 1696 3473408

Фактический код:

import UIKit

import os.log

import Foundation
import AudioToolbox
import AVFoundation

class AuxiliaryWork: Thread {
    let II_SAMPLE_RATE = 48000

    var iiStopRequested: Int32 = 0;  // Int32 is normally guaranteed to be atomic on most architectures

    var iiBufferFillCount: Int32 = 0;
    var iiBufferByteCount: Int32 = 0;

    func requestStop() {
        iiStopRequested = 1;
    }

    func myAVAudioSessionInterruptionNotificationHandler(notification: Notification ) -> Void {
        os_log(OSLogType.info, "AVAudioSession Interrupted: %s", notification.debugDescription)
    }

    func myAudioUnitProvider(actionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, timestamp: UnsafePointer<AudioTimeStamp>,
                             frameCount: AUAudioFrameCount, inputBusNumber: Int, inputData: UnsafeMutablePointer<AudioBufferList>) -> AUAudioUnitStatus {
        let ppInputData = UnsafeMutableAudioBufferListPointer(inputData)
        let iiNumBuffers = ppInputData.count

        if (iiNumBuffers > 0) {
            assert(iiNumBuffers == 2)

            for bbBuffer in ppInputData {
                assert(Int(bbBuffer.mDataByteSize) == 2048)  // FIXME: This should be 960 or 1920 ...

                iiBufferFillCount += 1
                iiBufferByteCount += Int32(bbBuffer.mDataByteSize)

                memset(bbBuffer.mData, 0, Int(bbBuffer.mDataByteSize))  // Just send silence

            }
        } else {
            os_log(OSLogType.error, "Zero buffers from system")
            assert(iiNumBuffers != 0)  // Force crash since os_log would cause an audio hiccup due to locks anyway
        }

        return noErr
    }

    override func main() {
        os_log(OSLogType.info, "Thread main started")

#if os(iOS)
        let kOutputUnitSubType = kAudioUnitSubType_RemoteIO
#else
        let kOutputUnitSubType = kAudioUnitSubtype_HALOutput
#endif

        let audioSession = AVAudioSession.sharedInstance()  // FIXME: Causes the following message No Factory registered for id
        try! audioSession.setCategory(AVAudioSession.Category.playback, options: [])
        try! audioSession.setMode(AVAudioSession.Mode.measurement)

        try! audioSession.setPreferredSampleRate(48000.0)
        try! audioSession.setPreferredIOBufferDuration(0.010)

        NotificationCenter.default.addObserver(
            forName: AVAudioSession.interruptionNotification,
            object: nil,
            queue: nil,
            using: myAVAudioSessionInterruptionNotificationHandler
        )

        let ioUnitDesc = AudioComponentDescription(
            componentType: kAudioUnitType_Output,
            componentSubType: kOutputUnitSubType,
            componentManufacturer: kAudioUnitManufacturer_Apple,
            componentFlags: 0,
            componentFlagsMask: 0)

        let auUnit = try! AUAudioUnit(componentDescription: ioUnitDesc,
                                      options: AudioComponentInstantiationOptions())

        auUnit.outputProvider = myAudioUnitProvider;
        auUnit.maximumFramesToRender = 256


        try! audioSession.setActive(true)

        try! auUnit.allocateRenderResources()  // Make sure audio unit has hardware resources--we could provide the buffers from the circular buffer if we want
        try! auUnit.startHardware()


        os_log(OSLogType.info, "Sample rate: %d", audioSession.sampleRate);
        os_log(OSLogType.info, "Buffer duration: %f", audioSession.ioBufferDuration);

        os_log(OSLogType.info, "Number of output busses: %d", auUnit.outputBusses.count);
        os_log(OSLogType.info, "Max frames: %d", auUnit.maximumFramesToRender);


        os_log(OSLogType.info, "Can perform output: %d", auUnit.canPerformOutput)
        os_log(OSLogType.info, "Output Enabled: %d", auUnit.isOutputEnabled)
        //os_log(OSLogType.info, "Audio Format: %p", audioFormat)

        var bus0 = auUnit.outputBusses[0]
        os_log(OSLogType.info, "Bus channels: %d", bus0.format.channelCount)
        os_log(OSLogType.info, "Bus format: %d", bus0.format.commonFormat.rawValue)
        os_log(OSLogType.info, "Bus rate: %d", bus0.format.sampleRate)

        for ii in 0..<10 {
            if (iiStopRequested != 0) {
                os_log(OSLogType.info, "Manual stop requested");
                break;
            }

            os_log(OSLogType.info, "Sleeping %d", ii);
            os_log(OSLogType.info, "Buffer count: %d %d", iiBufferFillCount, iiBufferByteCount)
            Thread.sleep(forTimeInterval: 1.0);
        }

        auUnit.stopHardware()
    }
}

class FirstViewController: UIViewController {
    var thrAuxiliaryWork: AuxiliaryWork? = nil;

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func startButtonPressed(_ sender: Any) {
        os_log(OSLogType.error, "Start button pressed");
        os_log(OSLogType.error, "Launching auxiliary thread");

        thrAuxiliaryWork = AuxiliaryWork();
        thrAuxiliaryWork?.start();
    }

    @IBAction func stopButtonPressed(_ sender: Any) {
        os_log(OSLogType.error, "Stop button pressed");
        os_log(OSLogType.error, "Manually stopping auxiliary thread");
        thrAuxiliaryWork?.requestStop();
    }

    @IBAction func muteButtonPressed(_ sender: Any) {
        os_log(OSLogType.error, "Mute button pressed");
    }

    @IBAction func unmuteButtonPressed(_ sender: Any) {
        os_log(OSLogType.error, "Unmute button pressed");
    }
}

1 Ответ

0 голосов
/ 12 ноября 2019

Вы не можете превратить кремниевое оборудование iOS в представление, если API сделает это за вас. Вы должны сделать свою собственную буферизацию, если хотите абстрагировать оборудование.

Для самых лучших (самых низких) задержек ваше программное обеспечение должно (потенциально динамически) адаптироваться к фактическим аппаратным возможностям, которые могут варьироваться от устройства к устройству и от режима к режиму.

Частота дискретизации аппаратного обеспечения составляет 44,1 кбит / с (старые устройства iOS), 48 кбит / с (более новые устройства iOS с arm64) или их кратное целое число (и, возможно, другие скорости при подключении Bluetooth-гарнитур, отличных от AirPod, или внешних АЦП). Реальные аппаратные буферы прямого доступа к памяти (или эквивалентные), кажется, всегда имеют степень 2, потенциально до 64 выборок на новейших устройствах. Однако различные режимы энергосбережения для iOS увеличат размер буфера (в степени 2) до 4 тыс. Сэмплов, особенно на старых устройствах iOS. Если вы запрашиваете частоту дискретизации, отличную от аппаратной, ОС может повторно сэмплировать буферы до размера, отличного от степени 2, и этот размер может измениться с обратного вызова аудиоустройства на последующий обратный вызов, если коэффициент повторной выборки не является точным целым числом.

Аудиоустройства - это самый низкий уровень, доступный через публичный API на устройствах iOS. Все остальное построено на вершине и, следовательно, может привести к большим задержкам. Например, если вы используете API аудио-очереди с не аппаратными размерами буфера, ОС будет внутренне использовать аудио-буферы степени 2 для доступа к аппаратному обеспечению и разделять их или частично объединять их для возврата или выборки буферов аудио-очередине аппаратные размеры. Медленный и нервный.

Долгое время API iOS был единственным API, используемым на мобильных телефонах и планшетах для живого исполнения музыки с малой задержкой. Но при разработке программного обеспечения, соответствующего аппаратному обеспечению.

...