Извлечение уровня звукового давления из AVAudioPCMBuffer
Я почти ничего не знаю об обработке сигналов, и в настоящее время я пытаюсь реализовать функцию в Swift, которая запускает событие, когда происходит повышение уровня звукового давления (например, когда человек кричит).
Я подключаюсь к входному узлу AVAudioEngine с помощью функции обратного вызова:
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat){
(buffer : AVAudioPCMBuffer?, when : AVAudioTime) in
let arraySize = Int(buffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: buffer.floatChannelData![0], count:arraySize))
//do something with samples
let volume = 20 * log10(floatArray.reduce(0){ $0 + $1} / Float(arraySize))
if(!volume.isNaN){
print("this is the current volume: \(volume)")
}
}
Превратив его в массив с плавающей точкой, я попытался просто получить приблизительную оценку уровня звукового давления путем вычисления среднего значения.
Но это дает мне значения, которые сильно колеблются, даже когда iPad просто сидел в тихой комнате:
this is the current volume: -123.971
this is the current volume: -119.698
this is the current volume: -147.053
this is the current volume: -119.749
this is the current volume: -118.815
this is the current volume: -123.26
this is the current volume: -118.953
this is the current volume: -117.273
this is the current volume: -116.869
this is the current volume: -110.633
this is the current volume: -130.988
this is the current volume: -119.475
this is the current volume: -116.422
this is the current volume: -158.268
this is the current volume: -118.933
Это действительно значительное увеличение этого значения, если я хлопаю рядом с микрофоном.
Таким образом, я могу сделать что-то вроде первого вычисления среднего из этих томов на этапе подготовки и сравнения, если есть существенное увеличение разницы на этапе запуска события:
if(!volume.isNaN){
if(isInThePreparingPhase){
print("this is the current volume: \(volume)")
volumeSum += volume
volumeCount += 1
}else if(isInTheEventTriggeringPhase){
if(volume > meanVolume){
//triggers an event
}
}
}
где AverageVolume вычисляется во время перехода от фазы подготовки к фазе запускающего события: meanVolume = volumeSum / Float(volumeCount)
....
Тем не менее, я не вижу значительного увеличения, если я играю громкую музыку, кроме микрофона. И в редких случаях, volume
больше, чем meanVolume
даже когда в окружающей среде нет значительного увеличения громкости (слышно для человеческого слуха).
Итак, как правильно извлечь уровень звукового давления из AVAudioPCMBuffer?
Википедия дает такую формулу
где p - среднеквадратичное звуковое давление, а p0 - эталонное звуковое давление.
Но я понятия не имею, что значения float в AVAudioPCMBuffer.floatChannelData
представлять. Страница яблока только говорит
Аудио сэмплы буфера как значения с плавающей точкой.
Как мне с ними работать?
2 ответа
Я думаю, что первый шаг должен получить конверт звука. Вы можете использовать простое усреднение для расчета огибающей, но вам нужно добавить шаг выпрямления (обычно это означает использование abs() или square(), чтобы сделать все выборки положительными)
Чаще всего вместо усреднения используется простой iir-фильтр с различными константами для атаки и затухания, вот лаборатория. Обратите внимание, что эти константы зависят от частоты дискретизации, вы можете использовать эту формулу для вычисления констант:
1 - exp(-timePerSample*2/smoothingTime)
Шаг 2
Если у вас есть конверт, вы можете сгладить его дополнительным фильтром, а затем сравнить два конверта, чтобы найти звук, который громче базового уровня, вот вам более полная лаборатория.
Обратите внимание, что обнаружение аудио "событий" может быть довольно сложным и трудно предсказать, убедитесь, что у вас есть много помощи отладки!
Благодаря ответу @teadrinker я наконец-то нашел решение этой проблемы. Я разделяю мой код Swift, который выводит объем AVAudioPCMBuffer
вход:
private func getVolume(from buffer: AVAudioPCMBuffer, bufferSize: Int) -> Float {
guard let channelData = buffer.floatChannelData?[0] else {
return 0
}
let channelDataArray = Array(UnsafeBufferPointer(start:channelData, count: bufferSize))
var outEnvelope = [Float]()
var envelopeState:Float = 0
let envConstantAtk:Float = 0.16
let envConstantDec:Float = 0.003
for sample in channelDataArray {
let rectified = abs(sample)
if envelopeState < rectified {
envelopeState += envConstantAtk * (rectified - envelopeState)
} else {
envelopeState += envConstantDec * (rectified - envelopeState)
}
outEnvelope.append(envelopeState)
}
// 0.007 is the low pass filter to prevent
// getting the noise entering from the microphone
if let maxVolume = outEnvelope.max(),
maxVolume > Float(0.015) {
return maxVolume
} else {
return 0.0
}
}