Преобразование входных данных AVAudioEngine в Opus и обратно приводит к повреждению данных - PullRequest
0 голосов
/ 26 мая 2020

Я использую 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.

...