Путаница с форматами аудиопотока и типами данных с Core Audio - PullRequest
1 голос
/ 21 апреля 2019

Я использую Core Audio (с быстрыми обертками) для воспроизведения некоторых аудиосэмплов (короткий стимул для записи импульса). Я придерживаюсь основного аудио, а не более нового AVFoundation, так как мне требуются некоторые строгие временные рамки, а также ввод с нескольких устройств, который пока не распространяется на новом фреймворке (я прошел запрос кода Apple, чтобы они сказали, что мне пришлось использовать основной звук).

Я сейчас создал очень простую синусоидальную волну, используя:

func createSine()->[Float]{
    var timeArray = makeArray(from: ((1.0/Float(sampleRate))*((-0.5)*Float(kernelLength))), to: ((1.0/Float(sampleRate))*((0.5)*Float(kernelLength))), increment: 1/sampleRate)
    var sineArray = Array(repeating:0, count: timeArray.count)

    for i in 0..<timeArray.count {
            let x = 2 * Float.pi * 1000 * testTimeArray[i]
            sineArray[i] = cos(x)
    }
}

Это создает массив Float (который я считаю 32-битным) для синусоидальной волны на частоте 1000 Гц при воспроизведении с частотой дискретизации (в моем случае 44,100 Гц)

Если я запишу это в файл wav и воспроизведу, тональный сигнал будет создан так, как ожидается.

Однако я действительно хочу включить этот звук в приложении. Я настроил свой AUGraph и заполнил его аудиоустройствами. Я создал AURenderCallback, который вызывается при входе в микшер. Каждый раз, когда этот вход нуждается в сигналах, он вызывает эту функцию обратного вызова.

let genCallback: AURenderCallback = { (
    inRefCon,
    ioActionFlags,
    inTimeStamp,
    inBusNumber,
    frameCount,
    ioData) -> OSStatus in

        let audioObject = unsafeBitCast(inRefCon, to: AudioEngine.self)

        for buffer in UnsafeMutableAudioBufferListPointer(ioData!) {
            var frames = buffer.mData!.assumingMemoryBound(to: Float.self)

            var j = 0

             for i in stride(from: 0, to: Int(frameCount), by: 2) {

                frames[i] = Float((audioObject.Stimulus[j + audioObject.stimulusReadIndex]))

                j += 1

            }

            audioObject.stimulusReadIndex += Int(frameCount/2)
        }
    }

   return noErr;
}

где audioObject.Stimulus - мой SineArray, а audioObject.stimulusReadIndex - просто счетчик для запоминания того, что было прочитано в массиве.

Теперь я столкнулся с проблемой. Если я запускаю AUGraph, я слышу свою синусоидальную волну, но я также слышу много гармоник (шума). Похоже, что это не правильный формат.

Если я скопирую каждый набор кадров в другой массив, чтобы проверить правильность написанного, выходные данные соответствуют входному стимулу, поэтому пропущенные сэмплы отсутствуют.

Если я пойду и посмотрю свое AudioStreamBasicDescription для модуля микшера (так как это вызывает обратный вызов рендеринга, у меня будет следующее:

var audioFormat = AudioStreamBasicDescription()
    audioFormat.mSampleRate            = 44100.00;
    audioFormat.mFormatID            = kAudioFormatLinearPCM;
    audioFormat.mFormatFlags        = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
    audioFormat.mFramesPerPacket    = 1;
    audioFormat.mChannelsPerFrame    = 2;
    audioFormat.mBitsPerChannel        = 16;
    audioFormat.mBytesPerPacket        = 4;
    audioFormat.mBytesPerFrame        = 4;
    audioFormat.mReserved             = 0;

  status = AudioUnitSetProperty(mixerUnit!,
                                  kAudioUnitProperty_StreamFormat,
                                  kAudioUnitScope_Input,
                                  1,
                                  &stimFormat,
                                  UInt32(MemoryLayout<AudioStreamBasicDescription>.size));
    checkStatus(status: status!);

Так что это говорит мне о нескольких вещах. То, что он ожидает два канала, и чередуется (так как флаг без чередования отсутствует). В моей функции обратного вызова я делаю кадры на 2, чтобы заполнить только первый канал семплами. Если вместо этого я начну с 1 и воспроизведу, звук записывается и воспроизводится с правой стороны.

Частота дискретизации верна, однако битрейт равен 16 (что не является Float), и я вижу, что есть флаг для isSignedInteger, так что ожидается другой формат.

Итак, я попытался преобразовать массив Float Array в Int16, используя:

for i in 0..<sineArray.count{
       sineArray[i] =  Int16.init((32767 * sweepSamples[i]))
    }

Однако это все равно приводит к другому шуму, хотя и к другому. Если я проверю массив, то смогу подтвердить, что результаты подписаны int16 и попадают в границы данных.

Я не вижу, как представить эти данные в формате, который ожидается в основном звуке. Я попытался изменить флаг формата на kAudioFormatFlagIsFloat, но все еще не повезло.

1 Ответ

0 голосов
/ 21 апреля 2019

Учитывая ваши данные [Float], вместо kAudioFormatFlagIsSignedInteger и 16 бит на канал, вы, вероятно, захотите использовать kAudioFormatFlagIsFloat и 32 бита (8 байтов на пакет и кадр).

Обратите внимание, что для всех последних устройств iOS собственный аудиоформат в 32-битном формате с плавающей запятой, а не в 16-битном int, использует собственную (аппаратную?) Частоту дискретизации 48000, а не 44100.

Также обратите внимание, что Apple рекомендует не использовать Swift внутри контекста обратного вызова аудио (см. Сеансы WWDC 2017 или 2018 по аудио), поэтому обратный вызов рендеринга аудиоустройства должен, вероятно, вызывать функцию C для выполнения всей работы (все, что касается ioData или inRefCon).

Возможно, вы также захотите проверить, чтобы индекс вашего массива не превышал границ вашего массива.

...