Я читаю входной файл и в автономном режиме ручного рендеринга хочу выполнить амплитудную модуляцию и записать результат в выходной файл.
Ради тестирования я генерирую чистые синусоидальные волны - это хорошо работает для частот ниже 6000 Гц. Для более высоких частот (моя цель состоит в том, чтобы работать примерно с 20 000 Гц), сигнал (таким образом, прослушивание выходного файла) искажается, и спектр заканчивается на 8 000 Гц - больше нет чистого спектра с множеством пиков между 0 и 8 000 Гц.
Вот мой фрагмент кода:
let outputFile: AVAudioFile
do {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let outputURL = documentsURL.appendingPathComponent("output.caf")
outputFile = try AVAudioFile(forWriting: outputURL, settings: sourceFile.fileFormat.settings)
} catch {
fatalError("Unable to open output audio file: \(error).")
}
var sampleTime: Float32 = 0
while engine.manualRenderingSampleTime < sourceFile.length {
do {
let frameCount = sourceFile.length - engine.manualRenderingSampleTime
let framesToRender = min(AVAudioFrameCount(frameCount), buffer.frameCapacity)
let status = try engine.renderOffline(framesToRender, to: buffer)
switch status {
case .success:
// The data rendered successfully. Write it to the output file.
let sampleRate:Float = Float((mixer.outputFormat(forBus: 0).sampleRate))
let modulationFrequency: Float = 20000.0
for i in stride(from:0, to: Int(buffer.frameLength), by: 1) {
let val = sinf(2.0 * .pi * modulationFrequency * Float(sampleTime) / Float(sampleRate))
// TODO: perform modulation later
buffer.floatChannelData?.pointee[Int(i)] = val
sampleTime = sampleTime + 1.0
}
try outputFile.write(from: buffer)
case .insufficientDataFromInputNode:
// Applicable only when using the input node as one of the sources.
break
case .cannotDoInCurrentContext:
// The engine couldn't render in the current render call.
// Retry in the next iteration.
break
case .error:
// An error occurred while rendering the audio.
fatalError("The manual rendering failed.")
@unknown default:
fatalError("unknown error")
}
} catch {
fatalError("The manual rendering failed: \(error).")
}
}
Мой вопрос: есть ли s.th. не так с моим кодом? Или у кого-нибудь есть идеи, как создавать выходные файлы с синусоидальными волнами более высоких частот?
Я полагаю, что ручной режим рендеринга недостаточно быстр, чтобы иметь дело с более высокими частотами.
Обновление: тем временем я проанализировал выходной файл с помощью Audacity. Выше представлена форма волны 1.000 Гц, ниже - частота 20.000 Гц:
Когда я увеличиваю масштаб, я вижу следующее:
Сравнивая спектры двух выходных файлов, я получаю следующее:
Странно, что с более высокими частотами амплитуда стремится к нулю. Вдобавок я вижу больше частот во втором спектре.
Новый вопрос в связи с результатом - правильность следующего алгоритма:
// Process the audio in `renderBuffer` here
for i in 0..<Int(renderBuffer.frameLength) {
let val = sinf(1000.0*Float(index) *2 * .pi / Float(sampleRate))
renderBuffer.floatChannelData?.pointee[i] = val
index += 1
}
Я действительно проверил частоту дискретизации, которая составляет 48000 - я знаю, что, когда частота дискретизации более чем в два раза превышает максимальную частоту дискретизируемого сигнала, исходный сигнал может быть точно реконструирован.
Обновление 2:
Я изменил настройки следующим образом:
settings[AVFormatIDKey] = kAudioFormatAppleLossless
settings[AVAudioFileTypeKey] = kAudioFileCAFType
settings[AVSampleRateKey] = readBuffer.format.sampleRate
settings[AVNumberOfChannelsKey] = 1
settings[AVLinearPCMIsFloatKey] = (readBuffer.format.commonFormat == .pcmFormatInt32)
settings[AVSampleRateConverterAudioQualityKey] = AVAudioQuality.max
settings[AVLinearPCMBitDepthKey] = 32
settings[AVEncoderAudioQualityKey] = AVAudioQuality.max
Теперь качество выходного сигнала лучше, но не идеально. Я получаю более высокие амплитуды, но всегда более одной частоты в анализаторе спектра. Может быть, обходной путь может заключаться в применении фильтра высоких частот?
Тем временем я работал с чем-то вроде SignalGenerator, передавая управляемый буфер (с синусоидальными волнами) прямо на громкоговоритель - в этом случае выход идеален. Я думаю, что маршрутизация сигнала в файл вызывает такие проблемы.