【问题标题】:Why do I get popping noises from my Core Audio program?为什么我的 Core Audio 程序会发出爆裂声?
【发布时间】:2020-11-16 21:10:17
【问题描述】:

我正在尝试弄清楚如何使用 Apple 的核心音频 API 来录制和播放线性 PCM 音频没有任何文件 I/O。 (录音方面似乎工作得很好。)

我拥有的代码很短,而且有点效果。但是,我无法识别输出中点击和弹出的来源。我已经为此苦苦挣扎了很多天,但没有成功。

我在这里发布了一个 git repo,带有一个显示我所在位置的命令行程序程序:https://github.com/maxharris9/AudioRecorderPlayerSwift/tree/main/AudioRecorderPlayerSwift

我添加了几个函数来预填充录音。音调发生器 (makeWave) 和噪声发生器 (makeNoise) 只是在这里作为调试辅助工具。当您在audioData 中播放录音时,我最终会尝试找出混乱输出的来源:

// makeWave(duration: 30.0, frequency: 441.0) // appends to `audioData`
// makeNoise(frameCount: Int(44100.0 * 30)) // appends to `audioData`
_ = Recorder() // appends to `audioData`
_ = Player() // reads from `audioData`

这是播放器代码:

var lastIndexRead: Int = 0

func outputCallback(inUserData: UnsafeMutableRawPointer?, inAQ: AudioQueueRef, inBuffer: AudioQueueBufferRef) {
    guard let player = inUserData?.assumingMemoryBound(to: Player.PlayingState.self) else {
        print("missing user data in output callback")
        return
    }

    let sliceStart = lastIndexRead
    let sliceEnd = min(audioData.count, lastIndexRead + bufferByteSize - 1)
    print("slice start:", sliceStart, "slice end:", sliceEnd, "audioData.count", audioData.count)

    if sliceEnd >= audioData.count {
        player.pointee.running = false
        print("found end of audio data")
        return
    }

    let slice = Array(audioData[sliceStart ..< sliceEnd])
    let sliceCount = slice.count

    // doesn't fix it
    // audioData[sliceStart ..< sliceEnd].withUnsafeBytes {
    //     inBuffer.pointee.mAudioData.copyMemory(from: $0.baseAddress!, byteCount: Int(sliceCount))
    // }

    memcpy(inBuffer.pointee.mAudioData, slice, sliceCount)
    inBuffer.pointee.mAudioDataByteSize = UInt32(sliceCount)
    lastIndexRead += sliceCount + 1

    // enqueue the buffer, or re-enqueue it if it's a used one
    check(AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nil))
}

struct Player {
    struct PlayingState {
        var packetPosition: UInt32 = 0
        var running: Bool = false
        var start: Int = 0
        var end: Int = Int(bufferByteSize)
    }

    init() {
        var playingState: PlayingState = PlayingState()
        var queue: AudioQueueRef?

        // this doesn't help
        // check(AudioQueueNewOutput(&audioFormat, outputCallback, &playingState, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue, 0, &queue))

        check(AudioQueueNewOutput(&audioFormat, outputCallback, &playingState, nil, nil, 0, &queue))

        var buffers: [AudioQueueBufferRef?] = Array<AudioQueueBufferRef?>.init(repeating: nil, count: BUFFER_COUNT)

        print("Playing\n")
        playingState.running = true

        for i in 0 ..< BUFFER_COUNT {
            check(AudioQueueAllocateBuffer(queue!, UInt32(bufferByteSize), &buffers[i]))
            outputCallback(inUserData: &playingState, inAQ: queue!, inBuffer: buffers[i]!)

            if !playingState.running {
                break
            }
        }

        check(AudioQueueStart(queue!, nil))

        repeat {
            CFRunLoopRunInMode(CFRunLoopMode.defaultMode, BUFFER_DURATION, false)
        } while playingState.running

        // delay to ensure queue emits all buffered audio
        CFRunLoopRunInMode(CFRunLoopMode.defaultMode, BUFFER_DURATION * Double(BUFFER_COUNT + 1), false)

        check(AudioQueueStop(queue!, true))
        check(AudioQueueDispose(queue!, true))
    }
}

我使用 Audio Hijack 捕获了音频,并注意到跳跃确实与缓冲区的大小相关:

为什么会发生这种情况,我可以做些什么来解决它?

【问题讨论】:

  • 看来我的问题与四年前的这个问题相似,从未成功回答:stackoverflow.com/questions/38509920/…
  • 我怀疑我知道答案。只是想看看你的回购。很快就会回复您。

标签: swift core-audio


【解决方案1】:

我相信你已经开始归零,或者至少怀疑你听到的爆裂声的原因:它是由你的波形中的不连续性引起的。

我最初的预感是您正在独立生成缓冲区(即假设每个缓冲区从 time=0 开始),但我检查了您的代码,但事实并非如此。我怀疑makeWave 中的一些计算有问题。为了验证这个理论,我将您的 makeWave 替换为以下内容:

func makeWave(offset: Double, numSamples: Int, sampleRate: Float64, frequency: Float64, numChannels: Int) -> [Int16] {
    var data = [Int16]()
    for sample in 0..<numSamples / numChannels {
        // time in s
        let t = offset + Double(sample) / sampleRate
        let value = Double(Int16.max) * sin(2 * Double.pi * frequency * t)
        for _ in 0..<numChannels {
            data.append(Int16(value))
        }
    }
    return data
}

这个函数去除了原来的双循环,接受一个偏移量,这样它就知道正在生成波的哪一部分,并对正弦波的采样进行一些更改。

Player 被修改为使用此功能时,您会得到一个可爱的稳定音调。我会尽快将更改添加到播放器。我不能凭良心向公众展示现在的快速和肮脏的混乱。


根据您下面的 cmets,我重新关注您的播放器。问题是音频缓冲区需要字节计数,但切片计数和其他一些计算基于 Int16 计数。以下版本的outputCallback 将修复它。集中精力使用新变量bytesPerChannel

func outputCallback(inUserData: UnsafeMutableRawPointer?, inAQ: AudioQueueRef, inBuffer: AudioQueueBufferRef) {
    guard let player = inUserData?.assumingMemoryBound(to: Player.PlayingState.self) else {
        print("missing user data in output callback")
        return
    }

    let bytesPerChannel = MemoryLayout<Int16>.size
    let sliceStart = lastIndexRead
    let sliceEnd = min(audioData.count, lastIndexRead + bufferByteSize/bytesPerChannel)

    if sliceEnd >= audioData.count {
        player.pointee.running = false
        print("found end of audio data")
        return
    }

    let slice = Array(audioData[sliceStart ..< sliceEnd])
    let sliceCount = slice.count
    
        print("slice start:", sliceStart, "slice end:", sliceEnd, "audioData.count", audioData.count, "slice count:", sliceCount)

    // need to be careful to convert from counts of Ints to bytes
    memcpy(inBuffer.pointee.mAudioData, slice, sliceCount*bytesPerChannel)
    inBuffer.pointee.mAudioDataByteSize = UInt32(sliceCount*bytesPerChannel)
    lastIndexRead += sliceCount

    // enqueue the buffer, or re-enqueue it if it's a used one
    check(AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nil))
}

我没有查看Recorder 代码,但您可能想检查那里是否出现了相同类型的错误。

【讨论】:

  • 哇,谢谢!那个音调发生器真的只是为了帮助调试录制的音频的另一个播放问题。如果您串联调用录音机和播放器,您可以听到我试图解决的原始播放问题,并使用它来录制和播放其他内容,例如通过系统扬声器播放的音乐:``` _ = Recorder() ; _ = 播放器(); ```
  • 嗯!这可能是一个单独的问题。以后尝试在问题中提及此类内容。
  • 稍后在这里。明天可能会看看其他问题。
  • 别担心,晚安!
  • 不客气,老实说,当我第一次查看您的代码时,我完全错过了这个问题。祝你的项目好运!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-02-17
  • 2016-11-16
  • 1970-01-01
  • 1970-01-01
  • 2018-10-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多