如何在音频线程外使用AVAudioEngine获取的PCM buffer的数据?
How to use the data of a PCM buffer obtained by using the AVAudioEngine outside of the audio thread?
我在 iOS 中使用 AVAudioEngine 从麦克风获取音频并使用输入节点及其函数 installTap 将其写入缓冲区。
在installTap函数的tapBlock里面,应该是读取and/or操作PCM缓冲区的地方,我需要调用一个C库函数,这个函数处理PCM缓冲区数据,计算音频指纹, 此函数还需要读取一个文件,该文件是预先计算的音频指纹的数据库,以查找可能的匹配项。
问题是显然(如果我错了请纠正我),你不能在这个块内调用任何文件 I/O 因为这段代码正在 运行 在另一个线程中,并且我传递给事物 C 端的文件指针始终为 null 或垃圾,这不会在此函数之外发生,(在事物的主线程端)指针有效,C 可以读取数据库文件。
我如何在主线程中操作 PCM 缓冲区,以便我可以进行文件 I/O 调用并能够计算我在 C 端所需的匹配?
我做错了什么?
还有其他选择吗?谢谢
import Foundation
import AVFoundation
let audioEngine = AVAudioEngine()
class AudioEngineTest: NSObject {
func setupAudioEngine() {
let input = audioEngine.inputNode
let inputFormat = input.outputFormat(forBus: 0)
let inputNode = audioEngine.inputNode;
//Convert received buffer to required format
let recordingFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: Double(44100), channels: 2, interleaved: false)
let formatConverter = AVAudioConverter(from:inputFormat, to: recordingFormat!)
let pcmBuffer = AVAudioPCMBuffer(pcmFormat: recordingFormat!, frameCapacity: AVAudioFrameCount(recordingFormat!.sampleRate * 4.0))
var error: NSError? = nil
inputNode.installTap(onBus: 0, bufferSize: AVAudioFrameCount(2048), format: inputFormat)
{
(buffer, time) in
let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer
}
formatConverter?.convert(to: pcmBuffer!, error: &error, withInputFrom: inputBlock)
if error != nil {
print(error!.localizedDescription)
}
//Calling the function from the C library, passing it the buffer and the pointer to the db file: dbFilePathForC an UnsafeMutablePointer<Int8>
creatingFingerprintAndLookingForMatch(pcmbuffer, dbFilePathForC)
//In this scope, the pointer dbFilePathFoC is either null or garbage, so the C side of things cannot read the database file, outside of this scope, the same pointer works and C can read the file, but I cannot read the PCM buffer because it only exists inside this scope of this closure of installTap, called the tapBlock
}
try! audioEngine.start()
}
}
获取数据库文件指针的代码块
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let dbPath = documentsPath+"/mydb.db"
do {
let text = try String(contentsOfFile: dbPath)
//converting dbPath to a pointer to be use in C
let cstringForDB = (dbPath as NSString).utf8String
let dbFilePathForC = UnsafeMutablePointer<Int8>(mutating: cstringForDB!);
} catch {
print("error cannot read the db file")
}
I/O 允许在任何线程上调用。问题在于您将 C-string 转换为 UnsafeMutablePointer<Int8>
(它被称为不安全是有充分理由的 )。您正在堆栈上执行此操作,让“变量”在您的 PCM 音频非主线程完成后消失。因此,您最终会得到一个指向某个随机内存的悬空指针。我怀疑您在主线程上似乎没有遇到同样的问题,因为它在整个应用程序生命周期中始终存在,并且在其堆栈上击中悬挂指针的可能性较小(但仍然绝对有可能)。
解决方案是让你的 UnsafeMutablePointer<Int8>
(礼貌 of yossan)像这样:
func makeCString(from str: String) -> UnsafeMutablePointer<Int8> {
let count = str.utf8CString.count
let result: UnsafeMutableBufferPointer<Int8> = UnsafeMutableBufferPointer<Int8>.allocate(capacity: count)
_ = result.initialize(from: str.utf8CString)
return result.baseAddress!
}
通过这样做,您可以在堆上为 C-string 分配 space,它以安全的方式在所有线程之间共享(只要它是只读内存)。
我在 iOS 中使用 AVAudioEngine 从麦克风获取音频并使用输入节点及其函数 installTap 将其写入缓冲区。
在installTap函数的tapBlock里面,应该是读取and/or操作PCM缓冲区的地方,我需要调用一个C库函数,这个函数处理PCM缓冲区数据,计算音频指纹, 此函数还需要读取一个文件,该文件是预先计算的音频指纹的数据库,以查找可能的匹配项。
问题是显然(如果我错了请纠正我),你不能在这个块内调用任何文件 I/O 因为这段代码正在 运行 在另一个线程中,并且我传递给事物 C 端的文件指针始终为 null 或垃圾,这不会在此函数之外发生,(在事物的主线程端)指针有效,C 可以读取数据库文件。
我如何在主线程中操作 PCM 缓冲区,以便我可以进行文件 I/O 调用并能够计算我在 C 端所需的匹配?
我做错了什么?
还有其他选择吗?谢谢
import Foundation
import AVFoundation
let audioEngine = AVAudioEngine()
class AudioEngineTest: NSObject {
func setupAudioEngine() {
let input = audioEngine.inputNode
let inputFormat = input.outputFormat(forBus: 0)
let inputNode = audioEngine.inputNode;
//Convert received buffer to required format
let recordingFormat = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: Double(44100), channels: 2, interleaved: false)
let formatConverter = AVAudioConverter(from:inputFormat, to: recordingFormat!)
let pcmBuffer = AVAudioPCMBuffer(pcmFormat: recordingFormat!, frameCapacity: AVAudioFrameCount(recordingFormat!.sampleRate * 4.0))
var error: NSError? = nil
inputNode.installTap(onBus: 0, bufferSize: AVAudioFrameCount(2048), format: inputFormat)
{
(buffer, time) in
let inputBlock: AVAudioConverterInputBlock = { inNumPackets, outStatus in
outStatus.pointee = AVAudioConverterInputStatus.haveData
return buffer
}
formatConverter?.convert(to: pcmBuffer!, error: &error, withInputFrom: inputBlock)
if error != nil {
print(error!.localizedDescription)
}
//Calling the function from the C library, passing it the buffer and the pointer to the db file: dbFilePathForC an UnsafeMutablePointer<Int8>
creatingFingerprintAndLookingForMatch(pcmbuffer, dbFilePathForC)
//In this scope, the pointer dbFilePathFoC is either null or garbage, so the C side of things cannot read the database file, outside of this scope, the same pointer works and C can read the file, but I cannot read the PCM buffer because it only exists inside this scope of this closure of installTap, called the tapBlock
}
try! audioEngine.start()
}
}
获取数据库文件指针的代码块
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let dbPath = documentsPath+"/mydb.db"
do {
let text = try String(contentsOfFile: dbPath)
//converting dbPath to a pointer to be use in C
let cstringForDB = (dbPath as NSString).utf8String
let dbFilePathForC = UnsafeMutablePointer<Int8>(mutating: cstringForDB!);
} catch {
print("error cannot read the db file")
}
I/O 允许在任何线程上调用。问题在于您将 C-string 转换为 UnsafeMutablePointer<Int8>
(它被称为不安全是有充分理由的 )。您正在堆栈上执行此操作,让“变量”在您的 PCM 音频非主线程完成后消失。因此,您最终会得到一个指向某个随机内存的悬空指针。我怀疑您在主线程上似乎没有遇到同样的问题,因为它在整个应用程序生命周期中始终存在,并且在其堆栈上击中悬挂指针的可能性较小(但仍然绝对有可能)。
解决方案是让你的 UnsafeMutablePointer<Int8>
(礼貌 of yossan)像这样:
func makeCString(from str: String) -> UnsafeMutablePointer<Int8> {
let count = str.utf8CString.count
let result: UnsafeMutableBufferPointer<Int8> = UnsafeMutableBufferPointer<Int8>.allocate(capacity: count)
_ = result.initialize(from: str.utf8CString)
return result.baseAddress!
}
通过这样做,您可以在堆上为 C-string 分配 space,它以安全的方式在所有线程之间共享(只要它是只读内存)。