File size: 2,946 Bytes
64f7370
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import AVFoundation
import Foundation

/// Converts arbitrary PCM input (any sample rate / channel count) to the
/// 16 kHz mono Float32 the models expect. Wraps AVAudioConverter with a
/// simple incremental API safe to call from one audio-callback thread.
public final class AudioResampler {
    public static let targetRate = 16_000.0

    private var converter: AVAudioConverter?
    private var sourceFormat: AVAudioFormat?
    private let targetFormat = AVAudioFormat(
        commonFormat: .pcmFormatFloat32, sampleRate: targetRate,
        channels: 1, interleaved: false)!

    public init() {}

    /// Converts one buffer. Reconfigures automatically if the input format
    /// changes (e.g. route change to a Bluetooth mic).
    public func convert(_ buffer: AVAudioPCMBuffer) throws -> [Float] {
        let format = buffer.format
        if format.sampleRate == Self.targetRate, format.channelCount == 1,
           format.commonFormat == .pcmFormatFloat32, let ch = buffer.floatChannelData {
            return Array(UnsafeBufferPointer(start: ch[0], count: Int(buffer.frameLength)))
        }
        if converter == nil || sourceFormat != format {
            guard let c = AVAudioConverter(from: format, to: targetFormat) else {
                throw SpeechError.audioFormatUnsupported(format.description)
            }
            converter = c
            sourceFormat = format
        }
        let ratio = Self.targetRate / format.sampleRate
        let capacity = AVAudioFrameCount(Double(buffer.frameLength) * ratio) + 16
        guard let out = AVAudioPCMBuffer(pcmFormat: targetFormat, frameCapacity: capacity) else {
            throw SpeechError.resamplingFailed("buffer allocation")
        }
        var err: NSError?
        var served = false
        converter!.convert(to: out, error: &err) { _, status in
            if served { status.pointee = .noDataNow; return nil }
            served = true
            status.pointee = .haveData
            return buffer
        }
        if let err { throw SpeechError.resamplingFailed(err.localizedDescription) }
        guard let ch = out.floatChannelData else {
            throw SpeechError.resamplingFailed("no channel data")
        }
        return Array(UnsafeBufferPointer(start: ch[0], count: Int(out.frameLength)))
    }

    /// Loads an audio file and returns 16 kHz mono samples.
    public static func loadFile(_ url: URL) throws -> [Float] {
        guard let file = try? AVAudioFile(forReading: url) else {
            throw SpeechError.audioFormatUnsupported("cannot open \(url.lastPathComponent)")
        }
        guard let buf = AVAudioPCMBuffer(pcmFormat: file.processingFormat,
                                         frameCapacity: AVAudioFrameCount(file.length)) else {
            throw SpeechError.resamplingFailed("file buffer allocation")
        }
        try file.read(into: buf)
        return try AudioResampler().convert(buf)
    }
}