如何为音频处理设置高优先级线程

Posted

技术标签:

【中文标题】如何为音频处理设置高优先级线程【英文标题】:How to have hight priority thread for audio processing 【发布时间】:2019-07-18 15:03:33 【问题描述】:

我有使用 AVAudioEngine 播放音频的 ios 应用程序。我将 AudioFile 中的帧读入 AVAudioPCMBuffer,然后将缓冲区发布到 AVAudioPlayerNode.scheduleBuffer。我在 DispatchQueue 上阅读了帧。我遇到的问题是,当应用程序有很多其他工作要做 - 与服务器同步和下载文件时,DispatchQueue 上的读取帧会延迟几秒钟执行,这会导致声音中断。 我已将 DispatchQueueQos 设置为尽可能高的 .userInteractive,并且我在应用程序中没有其他具有相同 qos 的队列,但有时仍需要几秒钟才能在该队列上执行代码。

有解决办法吗?有什么方法可以告诉操作系统这个队列是用于音频或类似的吗?

注意:如果应用程序运行一些繁重的后台操作,我不会遇到 AVPlayer 的这个问题。

编辑:一些代码以便更好地理解。问题是有时在队列中发布任务需要几秒钟(从评论 1 到评论 2)。

var audioProcessingQueue = DispatchQueue(label: "audioProcessing", qos: .userInteractive)
var player = AVAudioPlayerNode()
//comment 1
self.audioProcessingQueue.async(flags: .barrier) 
    //comment 2
    //some buffer processing here...
    player.scheduleBuffer(buffer, at: nil)

谢谢

【问题讨论】:

与服务器同步和下载文件应该通过异步调用来完成。网络不是你想在主线程上做的事情。主线程基本上是用于 UI 的东西。确保调度队列 main 中没有包含任何网络调用。 你能分享一些代码sn-ps吗? 它没有做任何文件下载或类似的事情。我添加了代码以便更好地理解 我不知道这是否有帮助,但这里是:forums.developer.apple.com/thread/14138 和 git.kabellmunk.dk/talks/into-the-deep/blob/master/Carthage/… 在 Instruments 下运行它,看看是什么阻碍了您的队列(请参阅系统跟踪模板作为起点)。我不为此推荐.userInteractive;它应该是.userInitiated。 QoS 不仅仅意味着优先级;它还有其他不明显的影响。使用符合您意图的优先级。为什么要在这里添加障碍?这个队列上还有什么(应该是串行队列吗?)如果您删除处理并按原样安排缓冲区会发生什么? 【参考方案1】:

两件事:

1) 您提供的代码安排在哪个队列中? 因为它听起来像那个队列可能会与你的下载任务共享它的 qos。 调度到另一个队列确实需要多个步骤。因此,仅仅因为传递了“comment1”,其他队列之一可能会在您调度时获得线程时间来做一些工作。这会导致一些延迟,是的,而且似乎是最可能的原因。

2) WWDC 2016 视频Concurrent Programming with GCD in Swift 3,可能会有所帮助。 如果 cpu 的所有核心都已忙于其他任务,则将新内容分派到更高优先级的队列不会立即自动启动任务。 GCD 会尽力而为,但也许你在那些其他队列上的任务非常紧张/做一些中断会导致问题的事情。

因此,如果您有必须立即启动的内容,请确保一个 CPU 内核/线程处于空闲状态。我会尝试使用OperationQueue 并设置它的maxConcurrentOperationCount。然后将所有下载操作放到该操作队列中,让内核/线程准备好启动您的音频。

但是我认为选项 2 在这里无效,因为您说它有时需要几秒钟。

更新

正如您所说,您提供的代码在一个队列上运行,该队列也用于您的下载任务,这主要是一个错误的架构设计,很抱歉,没有任何人都可以想出解决方案查看您的整个项目以帮助更好地构建它。 您必须弄清楚何时以及将哪些任务分配到什么优先级。

以下是我对您提供的代码的想法:

您为什么使用.barrier 标志?此标志确保队列不会执行其他并发任务,如果您的数据被多个任务操作并希望避免“竞争条件”,那就太好了。然而,这也意味着在你调度了这个块之后,它会等待在它之前调度的其他任务,然后阻塞队列来执行你的任务。

正如其他人所评论的,使用仪器分析您的代码可能会显示这种行为。

以下是我将如何构建您的音频请求:

//A serial (not concurrent) audio queue. Will download one data at a time and play audio
let audioQueue = DispatchQueue(label: "Audio Queue My App", qos: .userInitiated)

//I'm on the main queue here!

audioQueue.async 
    //I'm on the audio queue here, thus not blocking the main thread
    let result = downloadSomeData()
    //Download is done here, and I'm not being blocked by other dispatch queues if their priority is lower.
    switch result 
    case .success(let data):
        //Do something with the data and play audio
    case .failure(let error):
        //Handle Error
    

///Downloads some audio data
func downloadSomeData() -> Swift.Result<Data, Error> 
    //Create dispatch group
    let dg = DispatchGroup()
    dg.enter()
    ///my audio data
    var data: Data?
    //Dispatch onto a background queue OR place a block operation onto your OperationQueue
    DispatchQueue.global(qos: .background).async 
        //Download audio data... Alamofire/ URLSession
        data = Data()
        dg.leave()
    
    dg.wait()

    //Data was downloaded! We're back on the queue that called us.
    if let nonOptionalData = data 
        return .success(nonOptionalData)
     else 
        return .failure(NSError(domain: "MyDomain", code: 007, userInfo: [ NSLocalizedDescriptionKey: "No data downloaded, failed?"]))
    

关于选项#2:

可以使用ProcessInfo().activeProcessorCount 检索活动内核的数量。请注意,理论上这个值可能会在运行您的应用程序时由于热节流或其他原因而改变。 通过使用此信息并将OperationQueuemaxConcurrentOperationCount 设置为等于活动处理器的数量,您可以确保操作/任务不必共享cpu 时间。 (只要没有其他 DispatchQueue 正在运行。) 然而,这种方法并不安全。只需要一名团队成员在未来将任务分派到其他地方即可。

但是,此信息的有用之处在于,您可以限制下载任务使用的资源量,方法是拥有一个专用的 OperationQueue,例如最多 2 个并发操作,并将所有后台工作放在此 DispatchQueue 上。您的 CPU 很可能不会充分发挥其潜力并留下一些空间。

【讨论】:

它与应用中的其他任务共享队列。我虽然更高的优先级(。userInteractive)会解决它,但这还不够。我想完全按照您的建议 - 为音频保留 1 个专用线程/核心。有可能吗? @vandzi 稍微更新了我的答案。我怀疑这不会解决您遇到的问题,但值得一读,希望对您有所帮助。 其实我说错了。我的队列 var audioProcessingQueue = DispatchQueue(label: "audioProcessing", qos: .userInteractive) 仅用于音频处理。我在其中运行多个不同的队列(所有队列的优先级都较低),例如db 查询、处理一些数据、下载(使用 URLSessionDownloadTask)等...主要问题是如果所有这些都启动,音频队列很慢。在 android 中,可以选择将线程优先级设置为音频,所以我想做类似的事情。那里保证立即执行没有匹配器还有什么正在运行。 这就像你要创建音频播放器库一样。无论图书馆用户在他们的应用程序中做什么,您都希望保证它能够正常工作。我使用 AVPlayer 时没有这个问题,所以它必须做一些不同的事情。

以上是关于如何为音频处理设置高优先级线程的主要内容,如果未能解决你的问题,请参考以下文章

如何为 AVAudioEngine 设置完成处理程序

如何为高质量的 Opus 音频设置 SDP

任务24处理音频问题

教你如何用ffmpeg处理音频格式转换(标贝科技)

Android中的音频处理------SoundPool,MediaRecorder,MediaPlayer以及RingStone总结

如何为 iPhone 播放 HTML 格式的音频?不起作用的例子:(