How to use the data of a PCM buffer obtained by using the AVAudioEngine outside of the audio thread?
I'm using the AVAudioEngine in iOS to obtain audio from the microphone and writing it to buffer using an Input Node and its function installTap.
Inside the tapBlock of the installTap function, where it is supposed to be the place to read and/or manipulate the PCM buffer, I need to call a C library function, this function processes the PCM buffer data, it computes an audio fingerprint, this function also needs to read a file that is the database of the pre computed audio fingerprints in order to look for a possible match.
Проблема в том, что, по-видимому (поправьте меня, если я ошибаюсь), вы не можете выполнять какие-либо вызовы файлового ввода-вывода внутри этого блока, потому что этот код выполняется в другом потоке, а указатель файла, который я передал стороне C вещей всегда равен нулю или мусору, этого не происходит вне этой функции (в основном потоке вещей) указатель работает, и C может читать файл базы данных.
Как я могу управлять буфером PCM в основном потоке, чтобы я мог выполнять вызовы файлового ввода-вывода и иметь возможность вычислить соответствие, которое мне нужно, на стороне 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")
}
1 ответ
Вызовы ввода-вывода разрешены в любом потоке. Проблема в вашем преобразовании C-строки вUnsafeMutablePointer<Int8>
(небезопасно это называется небезопасным). Вы делаете это в стеке let "переменная", которая исчезнет после завершения вашего неосновного потока PCM Audio. Следовательно, вы получаете висящий указатель, указывающий на некоторую случайную память. Я подозреваю, что вы, похоже, не сталкиваетесь с той же проблемой в основном потоке, потому что она всегда присутствует на протяжении всего времени существования приложения, а попадание висящего указателя в его стек менее вероятно (но все же определенно возможно). Решение - получитьUnsafeMutablePointer<Int8>
(любезно предоставлено Ёсаном) вот так:
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-строки, которая безопасно распределяется между всеми потоками (пока это память только для чтения).