如何创建自定义音频类监听PC麦克风并返回音频帧?
自定义麦克风音频轨道实现(aiortc)
和你实现自定义视频轨道的思路一致,我们可以基于aiortc的AudioStreamTrack类,结合音频捕获库(这里用sounddevice,兼容性好且易于和numpy配合)来实现麦克风监听并返回音频帧的功能,完全避开bug较多的MediaPlayer。
步骤1:安装依赖
首先需要安装必要的库:
pip install aiortc sounddevice numpy
步骤2:完整实现代码
import asyncio import numpy as np import sounddevice as sd from aiortc import AudioStreamTrack from aiortc.media import AudioFrame class CustomAudioTrack(AudioStreamTrack): """ 自定义音频轨道,从麦克风捕获音频并返回aiortc兼容的AudioFrame """ def __init__(self, sample_rate=48000, channels=1): super().__init__() # 配置音频参数,aiortc默认推荐48000Hz单声道 self.sample_rate = sample_rate self.channels = channels # 计算每块音频数据的采样数(对应20ms帧,aiortc常用的帧时长) self.blocksize = int(sample_rate * 0.02) # 初始化麦克风输入流 try: self.stream = sd.InputStream( samplerate=sample_rate, channels=channels, dtype='float32', blocksize=self.blocksize ) self.stream.start() except Exception as e: print(f"麦克风初始化失败: {str(e)}") raise async def recv(self): # 获取时间戳(和视频轨道逻辑一致) pts, time_base = await self.next_timestamp() # 在线程池中执行同步的音频读取,避免阻塞事件循环 try: audio_data, overflowed = await asyncio.get_event_loop().run_in_executor( None, self.stream.read, self.blocksize ) if overflowed: print("音频缓冲区溢出,部分数据丢失") except Exception as e: print(f"音频捕获失败: {str(e)}") return # 将numpy数组转换为aiortc的AudioFrame # 注意:AudioFrame默认使用float32格式,通道数需匹配 audio_frame = AudioFrame.from_ndarray(audio_data, format=f"fltp") audio_frame.pts = pts audio_frame.time_base = time_base return audio_frame def stop(self): # 停止麦克风流,释放资源 if hasattr(self, 'stream') and self.stream.active: self.stream.stop() self.stream.close()
关键说明
- 音频参数匹配:aiortc默认使用48000Hz采样率、单声道(fltp格式,浮点32位),如果需要调整参数,需确保和后续RTC配置一致,否则会出现音频失真或无法播放的问题。
- 异步处理:由于
sounddevice的音频读取是同步操作,我们用run_in_executor将其放到线程池中执行,避免阻塞aiortc的事件循环。 - 帧时长控制:设置每块数据对应20ms的音频帧(
sample_rate * 0.02),这是WebRTC常用的帧时长,保证音频流的稳定性。 - 资源释放:实现
stop方法用于在结束时关闭麦克风流,避免资源泄漏。
使用示例
在aiortc的PeerConnection中添加自定义音频轨道:
from aiortc import RTCPeerConnection, RTCSessionDescription async def main(): pc = RTCPeerConnection() # 添加自定义音频轨道 audio_track = CustomAudioTrack() pc.addTrack(audio_track) # 后续的信令逻辑(创建offer、交换SDP等) # ... if __name__ == "__main__": asyncio.run(main())
内容的提问来源于stack exchange,提问作者Ismael Hadj
相关产品推荐
相关产品推荐

