Получить все звуковые частоты WAV-файла, используя Swift и AVFoundation - PullRequest
1 голос
/ 14 октября 2019

Я хотел бы захватить все частоты между заданными временными интервалами в Wav-файле. Намерение состоит в том, чтобы сделать некоторый аудиоанализ на более позднем этапе. Для тестирования я использовал приложение «Sox» для генерации Wav-файла длиной 1 секунда, который включает в себя только один тон на частоте 13000 Гц. Я хочу прочитать файл и найти эту частоту.

Я использую AVFoundation (что важно), чтобы прочитать файл. Поскольку входные данные находятся в формате PCM, мне нужно использовать БПФ, чтобы получить фактические частоты, которые я использую с помощью инфраструктуры ускорения. Тем не менее, я не получаю ожидаемый результат (13000 Гц), а довольно много значений, которые я не понимаю. Я новичок в разработке аудио, поэтому любая подсказка о том, где мой код не работает, приветствуется. Код включает в себя несколько комментариев, где возникает проблема.

Заранее спасибо!

Код:

import AVFoundation
import Accelerate



class Analyzer {

    // This function is implemented using the code from the following tutorial:
    // https://developer.apple.com/documentation/accelerate/vdsp/fast_fourier_transforms/finding_the_component_frequencies_in_a_composite_sine_wave
    func fftTransform(signal: [Float], n: vDSP_Length) -> [Int] {

        let observed: [DSPComplex] = stride(from: 0, to: Int(n), by: 2).map {
            return DSPComplex(real: signal[$0],
                              imag: signal[$0.advanced(by: 1)])
        }

        let halfN = Int(n / 2)

        var forwardInputReal = [Float](repeating: 0, count: halfN)
        var forwardInputImag = [Float](repeating: 0, count: halfN)

        var forwardInput = DSPSplitComplex(realp: &forwardInputReal,
                                           imagp: &forwardInputImag)

        vDSP_ctoz(observed, 2,
                  &forwardInput, 1,
                  vDSP_Length(halfN))

        let log2n = vDSP_Length(log2(Float(n)))

        guard let fftSetUp = vDSP_create_fftsetup(
            log2n,
            FFTRadix(kFFTRadix2)) else {
                fatalError("Can't create FFT setup.")
        }

        defer {
            vDSP_destroy_fftsetup(fftSetUp)
        }

        var forwardOutputReal = [Float](repeating: 0, count: halfN)
        var forwardOutputImag = [Float](repeating: 0, count: halfN)
        var forwardOutput = DSPSplitComplex(realp: &forwardOutputReal,
                                            imagp: &forwardOutputImag)

        vDSP_fft_zrop(fftSetUp,
                      &forwardInput, 1,
                      &forwardOutput, 1,
                      log2n,
                      FFTDirection(kFFTDirection_Forward))

        let componentFrequencies = forwardOutputImag.enumerated().filter {
            $0.element < -1
        }.map {
            return $0.offset
        }

        return componentFrequencies
    }

    func run() {


        // The frequencies array is a array of frequencies which is then converted to points on sinus curves (signal)
        let n = vDSP_Length(4*4096)
        let frequencies: [Float] = [1, 5, 25, 30, 75, 100, 300, 500, 512, 1023]
        let tau: Float = .pi * 2
        let signal: [Float] = (0 ... n).map { index in
            frequencies.reduce(0) { accumulator, frequency in
                let normalizedIndex = Float(index) / Float(n)
                return accumulator + sin(normalizedIndex * frequency * tau)
            }
        }

        // These signals are then restored using the fftTransform function above, giving the exact same values as in the "frequencies" variable
        let frequenciesRestored = fftTransform(signal: signal, n: n).map({Float($0)})
        assert(frequenciesRestored == frequencies)

        // Now I want to do the same thing, but reading the frequencies from a file (which includes a constant tone at 13000 Hz)
        let file = { PATH TO A WAV-FILE WITH A SINGLE TONE AT 13000Hz RUNNING FOR 1 SECOND }
        let asset = AVURLAsset(url: URL(fileURLWithPath: file))
        let track = asset.tracks[0]

        do {
            let reader = try AVAssetReader(asset: asset)

            let sampleRate = 48000.0

            let outputSettingsDict: [String: Any] = [
                AVFormatIDKey: kAudioFormatLinearPCM,
                AVSampleRateKey: Int(sampleRate),
                AVLinearPCMIsNonInterleaved: false,
                AVLinearPCMBitDepthKey: 16,
                AVLinearPCMIsFloatKey: false,
                AVLinearPCMIsBigEndianKey: false,
            ]

            let output = AVAssetReaderTrackOutput(track: track, outputSettings: outputSettingsDict)
            output.alwaysCopiesSampleData = false
            reader.add(output)
            reader.startReading()

            typealias audioBuffertType = Int16

            autoreleasepool {
                while (reader.status == .reading) {
                    if let sampleBuffer = output.copyNextSampleBuffer() {

                        var audioBufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: AudioBuffer(mNumberChannels: 0, mDataByteSize: 0, mData: nil))
                        var blockBuffer: CMBlockBuffer?

                        CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
                            sampleBuffer,
                            bufferListSizeNeededOut: nil,
                            bufferListOut: &audioBufferList,
                            bufferListSize: MemoryLayout<AudioBufferList>.size,
                            blockBufferAllocator: nil,
                            blockBufferMemoryAllocator: nil,
                            flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
                            blockBufferOut: &blockBuffer
                        );

                        let buffers = UnsafeBufferPointer<AudioBuffer>(start: &audioBufferList.mBuffers, count: Int(audioBufferList.mNumberBuffers))

                        for buffer in buffers {

                            let samplesCount = Int(buffer.mDataByteSize) / MemoryLayout<audioBuffertType>.size
                            let samplesPointer = audioBufferList.mBuffers.mData!.bindMemory(to: audioBuffertType.self, capacity: samplesCount)
                            let samples = UnsafeMutableBufferPointer<audioBuffertType>(start: samplesPointer, count: samplesCount)

                            let myValues: [Float] = samples.map {
                                let value = Float($0)
                                return value
                            }

                            // Here I would expect my array to include multiple "13000" which is the frequency of the tone in my file
                            // I'm not sure what the variable 'n' does in this case, but changing it seems to change the result.
                            // The value should be twice as high as the highest measurable frequency (Nyquist frequency) (13000),
                            // but this crashes the application:
                            let mySignals = fftTransform(signal: myValues, n: vDSP_Length(2 * 13000))
                            assert(mySignals[0] == 13000)
                        }
                    }
                }
            }
        }
        catch {
            print("error!")
        }
    }
}

Тестовый клип можно создать с помощью:

sox -G -n -r 48000 ~/outputfile.wav synth 1.0 sine 13000
...