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