Я использую AVAudioEngine для касания входа микрофона и пытаюсь закодировать необработанные данные PCM в opus. Я кодирую кадры в opus и отправляю закодированные данные через веб-сокет на приемник, приемник затем декодирует данные и воспроизводит их пользователю.
Мне удалось получить кодировщик и декодер работает с использованием libopus и может конвертировать данные с прослушиваемого микрофона в opus и обратно без каких-либо ошибок библиотеки. Теперь к моей проблеме .
Сохранение необработанных данных PCM (в виде файла wav) дает файл намного большей продолжительности и с большим шумом. Я использую эти настройки для кодирования / декодирования opus:
private let pcmRate: Int32 = 48000 // pcm sample rate
private let pcmChannels: Int32 = 1 // pcm channels
private let pcmBytesPerFrame: Int32 = 2 // bytes per frame in the pcm audio
private let frameSize: Int32 = 960 // frame size
private let maxFrameSize: Int32 = 3840 // maximum size of an opus frame
private let opusRate: Int32 = 48000 // desired sample rate of the opus audio
Настройка кодировщика и декодера:
// status to catch errors when creating encoder
var status = Int32(0)
encoder = opus_encoder_create(opusRate, pcmChannels, OPUS_APPLICATION_VOIP, &status)
guard let error = OpusError(rawValue: status) else {
os_log("Failed to create encoder, error: %@", log: .opus, type: .error, String(cString: opus_strerror(status)))
throw OpusError.internalError
}
guard error == .okay else { throw error }
// status to catch errors when creating decoder
status = Int32(0)
decoder = opus_decoder_create(opusRate, pcmChannels, &status)
guard let error2 = OpusError(rawValue: status) else {
os_log("Failed to create decoder, error: %@", log: .opus, type: .error, String(cString: opus_strerror(status)))
throw OpusError.internalError
}
guard error2 == .okay else { throw error }
Наконец, вот как я кодирую (и декодирую) data:
func encode(pcm: Data) throws -> Data {
try pcm.withUnsafeBytes { bytes in
let buffer: UnsafePointer<Float> = bytes.baseAddress!.assumingMemoryBound(to: Float.self)
return try encode(pcm: buffer, count: pcm.count)
}
}
private func encode(pcm: UnsafePointer<Float>, count: Int) throws -> Data {
// construct audio buffers
var pcm = UnsafeMutablePointer<Float>(mutating: pcm)
var opus = [UInt8](repeating: 0, count: Int(maxFrameSize))
var out = [Float](repeating: 0, count: Int(maxFrameSize))
var count = count
// number of total encoded bytes
var totalBytesEncoded = 0
var opusData = Data()
var pcmData = Data()
// encode complete frames
while count >= Int(frameSize) * Int(pcmBytesPerFrame) {
// encode an opus frame
let encodedBytes = opus_encode_float(encoder, pcm, frameSize, &opus, maxFrameSize)
if encodedBytes < 0 {
os_log("Encoding error: %@", log: .opus, type: .error, String(cString: opus_strerror(encodedBytes)))
throw OpusError.internalError
} else {
os_log("%@ bytes encoded", log: .opus, type: .error, String(describing: encodedBytes))
}
opusData.append(contentsOf: opus)
// decode opus frame
let decodedFrames = opus_decode_float(decoder, opus, encodedBytes, &out, maxFrameSize, 0)
if decodedFrames < 0 {
os_log("Decoding error: %@", log: .opus, type: .error, String(cString: opus_strerror(decodedFrames)))
throw OpusError.internalError
} else {
os_log("%@ frames decoded", log: .opus, type: .error, String(describing: decodedFrames))
}
let decodedOpusData = Data(buffer: UnsafeBufferPointer(start: out, count: out.count))
pcmData.append(decodedOpusData)
// advance pcm buffer
let bytesEncoded = Int(frameSize) * Int(pcmBytesPerFrame)
pcm = pcm.advanced(by: bytesEncoded / MemoryLayout<Float32>.stride)
count -= bytesEncoded
totalBytesEncoded += bytesEncoded
}
// return opusData
return pcmData
}
Функция encode (pcm: count :) кодирует данные pcm, разделяя их на определенный размер кадра. Чтобы проверить кодировку, закодированные данные напрямую декодируются и добавляются к pcmData. Я намеренно возвращаю декодированные pcmData вместо opusData и записываю их с помощью API AVAudioFile.
let format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: Double(48000), channels: UInt32(1), interleaved: false)!
let outputFile = try! AVAudioFile(forWriting: tempUrl!, settings: format.settings, commonFormat: .pcmFormatFloat32, interleaved: false)
let fromDataBuffer = decodedOpus.createPCMBuffer(format: format)!
try outputFile.write(from: fromDataBuffer)
Расширение данных, используемое для создания AVAudioPCMBuffer из данных
extension Data {
func createPCMBuffer(format: AVAudioFormat) -> AVAudioPCMBuffer? {
let streamDesc = format.streamDescription.pointee
let frameCapacity = UInt32(count) / streamDesc.mBytesPerFrame
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCapacity) else { return nil }
buffer.frameLength = buffer.frameCapacity
let audioBuffer = buffer.audioBufferList.pointee.mBuffers
withUnsafeBytes { (bufferPointer) in
guard let addr = bufferPointer.baseAddress else { return }
audioBuffer.mData?.copyMemory(from: addr, byteCount: Int(audioBuffer.mDataByteSize))
}
return buffer
}
}
Я просмотрел код и попытался убедиться, что все параметры в порядке (частота дискретизации и т. Д.), Но не смог точно определить какую-либо проблему. Поскольку существует большая разница в продолжительности между входными и декодированными данными, я предполагаю, что это как-то связано с частотой дискретизации.
Был бы очень признателен за любую помощь и понимание, которые могут помочь мне решить проблему, спасибо!
PS. Я включил более полный пример в gist DS.