Ios 从麦克风获取音频,向下采样,编码(使用AAC)并通过插座传输

Ios 从麦克风获取音频,向下采样,编码(使用AAC)并通过插座传输,ios,swift,avfoundation,core-audio,avaudioengine,Ios,Swift,Avfoundation,Core Audio,Avaudioengine,在过去的几周里,我一直在尝试从许多不同的例子中拼凑出一个解决方案,这将使我能够: 从麦克风捕获音频 将其采样至8khz 使用AAC对每个缓冲区进行编码 通过套接字发送结果 我看到的几乎所有的例子都是将音频编码到一个文件中,我是这样做的,用于测试,但这不是我需要的。我需要取一个数据包,编码并传输它 话筒服务 这基本上建立了一个AVAudioEngine,并在其上附加了一个AVAudioMixerNode,从而对音频进行下采样 然后将结果放入阻塞队列(AudioEncoderQueue)中,以便编码

在过去的几周里,我一直在尝试从许多不同的例子中拼凑出一个解决方案,这将使我能够:

  • 从麦克风捕获音频
  • 将其采样至8khz
  • 使用AAC对每个缓冲区进行编码
  • 通过套接字发送结果
  • 我看到的几乎所有的例子都是将音频编码到一个文件中,我是这样做的,用于测试,但这不是我需要的。我需要取一个数据包,编码并传输它

    话筒服务
    这基本上建立了一个
    AVAudioEngine
    ,并在其上附加了一个
    AVAudioMixerNode
    ,从而对音频进行下采样

    然后将结果放入阻塞队列(
    AudioEncoderQueue
    )中,以便编码服务可以对缓冲区进行编码

    import Foundation
    import AVFoundation
    
    // Base on https://stackoverflow.com/questions/39595444/avaudioengine-downsample-issue
    // https://github.com/onmyway133/notes/issues/367
    
    class MicrophoneService {
    
        let audioEngine = AVAudioEngine()
    
        init() {
            do {
                try AVAudioSession.sharedInstance().setPreferredSampleRate(16000)
            } catch let error {
                print(error)
            }
    
            let engineInputNode = audioEngine.inputNode
            let bus = 0
            let engineInputNodeFormat = engineInputNode.outputFormat(forBus: bus)
    
            //engineInputNode.installTap(onBus: bus, bufferSize: 1024, format: engineInputNodeFormat) { (buffer, time) in
            //  AudioEncoderQueue.shared.put(buffer)
            //}
    
            let mixer = AVAudioMixerNode()
            audioEngine.attach(mixer)
    
            let mixerOutputFormat = AVAudioFormat(standardFormatWithSampleRate: 8000, channels: 1)
    
            audioEngine.connect(engineInputNode, to: mixer, format: engineInputNodeFormat)
            audioEngine.connect(mixer, to: audioEngine.outputNode, format: mixerOutputFormat)
    
            mixer.installTap(onBus: bus, bufferSize: 1024 * 4, format: mixerOutputFormat) { (buffer: AVAudioPCMBuffer, time: AVAudioTime) in
                AudioEncoderQueue.shared.put(buffer)
            }
        }
    
        func start() throws {
            stop()
            audioEngine.prepare()
            try audioEngine.start()
        }
    
        func stop() {
            audioEngine.stop()
        }
    
    }
    
    AudioEncoderService
    这基本上是从编码队列中弹出下一个音频数据包,对其进行编码并将其放入传输队列(
    TransportQueue

    AudioUtilities
    这是音频缓冲区使用AAC编码并转换为
    数据的地方。这是基于一些SO帖子和一些调整

    import Foundation
    import AVFoundation
    
    // https://stackoverflow.com/questions/51360127/decode-aac-to-pcm-format-using-avaudioconverter-swift
    // https://stackoverflow.com/questions/51360127/decode-aac-to-pcm-format-using-avaudioconverter-swift
    
    class AudioUtilities {
    
        static func AACFormat() -> AVAudioFormat? {
    
            var outDesc = AudioStreamBasicDescription(
                mSampleRate: 8000,
                mFormatID: kAudioFormatMPEG4AAC,
                mFormatFlags: 0,
                mBytesPerPacket: 0,
                mFramesPerPacket: 0,
                mBytesPerFrame: 0,
                mChannelsPerFrame: 1,
                mBitsPerChannel: 0,
                mReserved: 0)
    
            let outFormat = AVAudioFormat(streamDescription: &outDesc)
            return outFormat
        }
    
        static var lpcmToAACConverter: AVAudioConverter! = nil
    
        static func convertToAAC(from buffer: AVAudioBuffer) throws -> AVAudioCompressedBuffer? {
    
            let outputFormat = AACFormat()
            //init converter once
            if lpcmToAACConverter == nil {
                let inputFormat = buffer.format
    
                lpcmToAACConverter = AVAudioConverter(from: inputFormat, to: outputFormat!)
                lpcmToAACConverter.bitRate = 8000
            }
            let outBuffer = AVAudioCompressedBuffer(format: outputFormat!,
                                                    packetCapacity: 8,
                                                    maximumPacketSize: lpcmToAACConverter.maximumOutputPacketSize)
                                                    //maximumPacketSize: 768)
    
            try self.convert(withConverter: lpcmToAACConverter,
                                             from: buffer,
                                             to: outBuffer)
    
            return outBuffer
        }
    
        private static func convert(withConverter: AVAudioConverter, from sourceBuffer: AVAudioBuffer, to destinationBuffer: AVAudioBuffer) throws {
            // input each buffer only once
            var newBufferAvailable = true
    
            let inputBlock : AVAudioConverterInputBlock = {
                inNumPackets, outStatus in
                if newBufferAvailable {
                    outStatus.pointee = .haveData
                    newBufferAvailable = false
                    return sourceBuffer
                } else {
                    outStatus.pointee = .noDataNow
                    return nil
                }
            }
    
            var outError: NSError? = nil
            let status = withConverter.convert(to: destinationBuffer, error: &outError, withInputFrom: inputBlock)
    
            switch status {
            case .haveData: break
            case .inputRanDry: print("Input run dry")
            case .endOfStream: print("End of stream")
            case .error: print("!! Error")
            @unknown default: break
            }
    
            guard let error = outError else {
                return
            }
            throw error
        }
    }
    
    TransportService
    这基本上是从传输队列中获取数据并通过套接字发送出去。每个数据包都由一个自定义报头进行处理,由于它是无关的,所以不包括该报头

    import Foundation
    import SwiftSocket
    
    class TransportService {
    
        fileprivate var stopped: Bool = false
    
        var socket: TCPClient?
        let config: DeviceConfiguration
    
        init(config: DeviceConfiguration) {
            self.config = config
        }
    
        func start() throws {
            stop()
            stopped = false
            socket = TCPClient(address: config.ipAddress, port: Int32(config.port))
            print("Connect to \(config.ipAddress) : \(config.port)")
            switch socket!.connect(timeout: 30) {
            case .success:
                DispatchQueue.global(qos: .userInitiated).async {
                    self.transportData()
                }
            case .failure(let error):
                socket = nil
                throw error
            }
        }
    
        func stop() {
            stopped = true
            socket?.close()
            socket = nil
        }
    
        func transportData() {
            guard let socket = socket else { return }
    
            let headerLength = MemoryLayout<SPECIAL_HEADER>.size
            var header = SPECIAL_HEADER()
            // Populate the header properties
            // ...
            header.header_length = Int32(headerLength)
            header.sample_rate = 8000
    
            var count = 0
    
            repeat {
                guard let data = TransportQueue.shared.take() else { return }
                guard data.count > 0 else { return }
    
                header.packet_sn = Int32(count)
                header.data_length = Int32(data.count)
    
                var headerData = Data(bytes: &header, count: headerLength)
                headerData.append(data)
                print("\(count); \(header.data_length); \(headerData.count)")
                let result = socket.send(data: headerData)
                switch result {
                case .success: break
                case .failure(let error):
                    log(error: "\(error)")
                    return
                }
                count += 1
    
    
                Thread.sleep(forTimeInterval: 0.1)
            } while !stopped
            print("!! Stopped")
        }
    
    }
    
    <代码>导入基础 进口雨燕 等级运输服务{ fileprivate变量已停止:Bool=false 变量套接字:TCPClient? let config:DeviceConfiguration 初始化(配置:设备配置){ self.config=config } func start()抛出{ 停止() 停止=错误 socket=TCPClient(地址:config.ipAddress,端口:Int32(config.port)) 打印(“连接到\(config.ipAddress):\(config.port)”) 开关插座!.connect(超时:30){ 成功案例: DispatchQueue.global(qos:.userInitiated).async{ self.transportData() } 案例。失败(let错误): 插座=零 抛出错误 } } 函数停止(){ 停止=真 插座?.close() 插座=零 } func transportData(){ guard let socket=socket else{return} 让headerLength=MemoryLayout.size var header=特殊_header() //填充标题属性 // ... header.header_length=Int32(headerLength) header.sample_比率=8000 变量计数=0 重复{ guard let data=TransportQueue.shared.take()else{return} guard data.count>0 else{return} header.packet\u sn=Int32(计数) header.data_length=Int32(data.count) var headerData=数据(字节:&header,计数:headerLength) headerData.append(数据) 打印(“\(计数);\(header.data\u长度);\(headerData.count)”) let result=socket.send(数据:headerData) 切换结果{ 成功案例:突破 案例。失败(let错误): 日志(错误:“\(错误)”) 返回 } 计数+=1 线程睡眠(forTimeInterval:0.1) }一会儿!停下来 打印(!!已停止) } }
    注意:我已经使用一个测试文件独立测试了传输服务,并从中提取了每个文件,所以我知道这部分工作正常。这只是为了完成基本的整体情况

    问题是。。。
  • 无论我在
    麦克风服务中的缓冲区有多大,我都会不断从编码器中获取
    输入干运行
  • 在硬件端没有音频监听(同样,我已经使用静态文件测试了传输工作流程,它确实可以工作)(不,我不控制硬件,但我有测试源代码,可以使用
    TransportService
    将AAC文件逐帧发送到工作正常的it)
  • 要求。。。 音频必须为8khz,单声道,并使用AAC编码

    问题。。。 这是正确的方法还是我应该采取另一种方法来捕获音频,对其进行下采样和编码


    我能做些什么来限制
    Input run dry
    的出现次数吗?或者我不在乎吗?

    你能达到预期的结果吗?@bobski没有使用这种方法-我们最终将音频数据包从麦克风写入音频文件(用AAC编码)并使用“文件检查器”要读取单个AAC数据包,请将其变为可用数据包
    import Foundation
    import SwiftSocket
    
    class TransportService {
    
        fileprivate var stopped: Bool = false
    
        var socket: TCPClient?
        let config: DeviceConfiguration
    
        init(config: DeviceConfiguration) {
            self.config = config
        }
    
        func start() throws {
            stop()
            stopped = false
            socket = TCPClient(address: config.ipAddress, port: Int32(config.port))
            print("Connect to \(config.ipAddress) : \(config.port)")
            switch socket!.connect(timeout: 30) {
            case .success:
                DispatchQueue.global(qos: .userInitiated).async {
                    self.transportData()
                }
            case .failure(let error):
                socket = nil
                throw error
            }
        }
    
        func stop() {
            stopped = true
            socket?.close()
            socket = nil
        }
    
        func transportData() {
            guard let socket = socket else { return }
    
            let headerLength = MemoryLayout<SPECIAL_HEADER>.size
            var header = SPECIAL_HEADER()
            // Populate the header properties
            // ...
            header.header_length = Int32(headerLength)
            header.sample_rate = 8000
    
            var count = 0
    
            repeat {
                guard let data = TransportQueue.shared.take() else { return }
                guard data.count > 0 else { return }
    
                header.packet_sn = Int32(count)
                header.data_length = Int32(data.count)
    
                var headerData = Data(bytes: &header, count: headerLength)
                headerData.append(data)
                print("\(count); \(header.data_length); \(headerData.count)")
                let result = socket.send(data: headerData)
                switch result {
                case .success: break
                case .failure(let error):
                    log(error: "\(error)")
                    return
                }
                count += 1
    
    
                Thread.sleep(forTimeInterval: 0.1)
            } while !stopped
            print("!! Stopped")
        }
    
    }