【问题标题】:What is the best practice for updating UI from Core Audio Callback?从 Core Audio 回调更新 UI 的最佳实践是什么?
【发布时间】:2016-03-17 06:37:31
【问题描述】:

我目前正在研究 Core Audio,但遇到了如何更新 GUI 和 AudioQueueInputCallback 的问题。首先,我想使用从麦克风读取的电平表来更新标签。

在我的代码中,我将当前电平表值存储在每个回调的结构中。

func MyAudioQueueInputCallback(inUserData: UnsafeMutablePointer<Void>, inAQ: AudioQueueRef, inBuffer: AudioQueueBufferRef, inStartTime: UnsafePointer<AudioTimeStamp>, var inNumberPacketDesc: UInt32, inPacketDesc: UnsafePointer<AudioStreamPacketDescription>){

var error: OSStatus

if (inNumberPacketDesc > 0){

    error = AudioFileWritePackets(MyRecorder.recordFile, false, inBuffer.memory.mAudioDataByteSize, inPacketDesc, MyRecorder.recordPacket, &inNumberPacketDesc, inBuffer.memory.mAudioData)
    checkError(error, operation: "AudioFileWritePackets Failed ")

    // Increment the packet index
    MyRecorder.recordPacket += Int64(inNumberPacketDesc)

    if (MyRecorder.running){
        error = AudioQueueEnqueueBuffer(inAQ, inBuffer, inNumberPacketDesc, inPacketDesc)
        checkError(error, operation: "AudioQueueEnqueueBuffer Failed BAHHHH")

        var level: Float32 = 0
        var levelSize = UInt32(sizeof(level.dynamicType))
        error = AudioQueueGetProperty(inAQ, kAudioQueueProperty_CurrentLevelMeter, &level, &levelSize)
        checkError(error, operation: "AudioQueueGetProperty Failed...  Get help!")

        MyRecorder.meterLevel = level // meter level stored in public struct
    } 
}    

}

//MARK:  User Data Struct / Class
struct MyRecorder {
    static var recordFile: AudioFileID = nil
    static var recordPacket: Int64 = 0
    static var running: Bool = false
    static var queue: AudioQueueRef = nil
    static var meterLevel: Float32 = 0.00
}

从这里使用主线程上的 NSTimer 轮询meterLevel 变量。

func setMeterLabel() -> Void{

    meter.text = String(MyRecorder.meterLevel)

}

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    main()

    var timer = NSTimer.scheduledTimerWithTimeInterval(0.05, target: self, selector: "setMeterLabel", userInfo: nil, repeats: true)

}

这段代码工作得很好,但我觉得使用 NSTimer 可能不是最好的方法。有没有办法让我的回调直接更新标签?

【问题讨论】:

    标签: ios c swift callback core-audio


    【解决方案1】:

    使用重复的 NSTimer 或 CADisplayLink 是一种很好的标准方法。任何其他方法都可能导致您的音频回调被阻塞太久。

    您知道音频采样率和音频缓冲区的大小。因此,您大约知道新数据缓冲区出现的频率。因此,在 2 个速率中最慢的一个(新的音频缓冲速率或您想要的帧动画速率,通常为 60 或 30 Hz 或更慢),您可以设置重复计时器回调(或 CADisplayLink 计时器回调)来轮询是否有新音频数据已被记录,并做一些事情,例如在需要更新的某个视图上调用 setNeedsDisplay。 (UIView drawRect 更新将始终在主 UI 线程上调用)。

    将状态或数据从音频回调传递到计时器回调的一种好方法是使用无锁循环 FIFO/缓冲区。然后保证你永远不会阻塞你的音频回调线程。

    如果您在 UI 线程上运行计时器回调,则无需对同一线程执行异步调度。

    【讨论】:

      【解决方案2】:

      看起来一切都在同一个控制器中发生,因此您应该能够从回调函数中访问所需的一切。

      您可能不想在每次回调时都更新 GUI,但您可以设置一个每次递增的计数器,并定期更新 GUI。

      您唯一需要更改的是确保在主队列中调用setMeterLabel - 您不能从后台队列进行 GUI 更改。

      dispatch_async(dispatch_get_main_queue(),
      {
          self.setMeterLabel()
      })
      

      【讨论】:

        【解决方案3】:

        回调速率可能太快,因此请考虑创建一个平均过滤器(或箱平均)。 让音频回调更新控制器中的简单原语(int、float 等)。如果你愿意,你可以委托。将它们存储在队列中,只保留最后 20 个项目。去掉最旧的,保留最新的。

        让你的 UI 计算你最近选择的 n 个项目的平均值,并定期显示在 UI 上。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-07
          相关资源
          最近更新 更多