iOS上的低延迟音频输出问题(也就是如何击败AUAudioUnit sampleRate,maximumFramesToRender和ioBufferDuration提交)

问题描述 投票:0回答:1

好吧,我显然在这里缺少了一些重要的内容。我正在尝试通过网络进行低延迟音频,而我的基本帧为10ms。我希望这没问题。我的目标手机是iPhone X扬声器,因此我的硬件采样率应锁定为48000Hz。我要求10毫秒,这是一个很好的偶数除数,应为480、960、1920或3840,具体取决于您要对帧/样本/字节进行切片的方式。

然而,对于我的一生,我绝对无法让iOS做我认为理智的事情。我得到10.667ms的缓冲区持续时间,这很荒谬-iOS淘汰了这种方法,使我的缓冲区大小不是sampleRate的整数倍。更糟糕的是,该帧明显是LONG,这意味着我必须吸收一个而不是two个延迟数据包,才能填充该缓冲区。我完全无法更改maximumFrameToRender,并且系统返回的是[[0作为我的采样率,即使它很明显是在48000Hz处呈现。

我显然缺少重要的东西-这是什么?我是否忘了断开/连接某些东西以获得直接的硬件映射? (我的格式是1,即pcmFormatFloat32 -我希望pcmFormatInt16或pcmFormatInt32直接映射到硬件,所以OS中的某些内容可能会妨碍您使用)指针很受赞赏,我很乐意阅读更多内容。还是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"); } }

ios swift core-audio audiounit audiotoolbox
1个回答
0
投票
假设API可以帮您完成,您就无法击败iOS芯片硬件。如果要抽象硬件,则必须自己做缓冲。

为了获得最佳(最低)延迟,您的软件将(可能会动态地)适应实际的硬件功能,实际的硬件功能可能因设备而异,并且因模式而异。

硬件采样率似乎是44.1ksps(较旧的iOS设备),48ksps(较新的arm64 iOS设备)或其整数倍(以及插入非AirPod蓝牙耳机时的其他采样率)。实际的硬件DMA(或等效的)缓冲区似乎总是2的幂,在最新的设备上可能减少到64个样本。但是,各种iOS省电模式都会将缓冲区大小(以2的幂为单位)增加到4k个样本,尤其是在较旧的iOS设备上。如果您请求的采样率不是硬件速率,则操作系统可能会将缓冲区重新采样为与2的幂不同的大小,并且如果重新采样率不是精确的整数,则此大小可以从Audio Unit回调更改为后续回调。

音频单元是可通过iOS设备上的公共API访问的最低级别。其他所有内容都建立在顶部,因此可能会变慢。例如,如果您将Audio Queue API与非硬件缓冲区大小配合使用,则操作系统将使用2次幂的音频缓冲区,并将其切碎或连接起来以返回或获取请求的Audio Queue缓冲区数据量。较慢和抖动。

© www.soinside.com 2019 - 2024. All rights reserved.