Я использую 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, но все еще не повезло.