swift - 为什么我的 Core Audio 程序会出现爆裂声?

标签 swift core-audio

我想弄清楚如何使用 Apple 的 Core Audio 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 捕获了音频,并注意到跳转确实与缓冲区的大小相关:
captured audio output using Audio Hijack and Sound Studio
为什么会发生这种情况,我可以做些什么来解决它?

最佳答案

我相信您已经开始归零,或者至少怀疑您听到的爆音的原因:它是由波形的不连续性引起的。
我最初的预感是您是独立生成缓冲区(即假设每个缓冲区从 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修改为使用此功能,您将获得可爱的稳定音调。我会尽快将更改添加到播放器。我不能凭良心向公众展示它现在 swift 而肮脏的困惑。

根据您在下面的评论,我重新关注了您的播放器。问题是音频缓冲区需要字节计数,但切片计数和其他一些计算基于 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代码,但您可能想检查是否存在相同类型的错误。

关于swift - 为什么我的 Core Audio 程序会出现爆裂声?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64865774/

相关文章:

ios - COCOS2D 聚光灯图标在 iPhone 6 上不显示

objective-c - 制定振荡器波形代码并创建新的波形

ios - Callkit 扬声器错误/WhatsApp 如何修复它?

swift - 如何将函数的结果分配给成员变量并在 Swift 中的 if 语句中进行验证

iOS:同时使用 Facebook 和 Google,Google Plus 登录

ios - 如何避免使用子类化和/或委托(delegate)模式(Swift)为两个 View Controller 编写相同的函数

osx-snow-leopard - 在 Snow Leopard 中控制 OS X 音量

ios - SpriteKit 和 SceneKit 是否允许在游戏过程中访问 iOS 麦克风输入?

ios - 将 float 组转换为Swift文件Wav

ios - iOS 上的低延迟音频输出问题(又名如何击败 AUAudioUnit SampleRate、MaximumFramesToRender 和 ioBufferDuration 提交)