forked from xiaozhi/xiaozhi-esp32
81 lines
2.3 KiB
Python
81 lines
2.3 KiB
Python
# 播放p3格式的音频文件
|
||
import opuslib
|
||
import struct
|
||
import numpy as np
|
||
import sounddevice as sd
|
||
import time
|
||
import argparse
|
||
|
||
def play_p3_file(input_file):
|
||
"""
|
||
播放p3格式的音频文件
|
||
p3格式: [1字节类型, 1字节保留, 2字节长度, Opus数据]
|
||
"""
|
||
# 初始化Opus解码器
|
||
sample_rate = 16000 # 采样率固定为16000Hz
|
||
channels = 1 # 单声道
|
||
decoder = opuslib.Decoder(sample_rate, channels)
|
||
|
||
# 帧大小 (60ms)
|
||
frame_size = int(sample_rate * 60 / 1000)
|
||
|
||
# 打开音频流
|
||
stream = sd.OutputStream(
|
||
samplerate=sample_rate,
|
||
channels=channels,
|
||
dtype='int16'
|
||
)
|
||
stream.start()
|
||
|
||
try:
|
||
with open(input_file, 'rb') as f:
|
||
print(f"正在播放: {input_file}")
|
||
|
||
while True:
|
||
# 读取头部 (4字节)
|
||
header = f.read(4)
|
||
if not header or len(header) < 4:
|
||
break
|
||
|
||
# 解析头部
|
||
packet_type, reserved, data_len = struct.unpack('>BBH', header)
|
||
|
||
# 读取Opus数据
|
||
opus_data = f.read(data_len)
|
||
if not opus_data or len(opus_data) < data_len:
|
||
break
|
||
|
||
# 解码Opus数据
|
||
pcm_data = decoder.decode(opus_data, frame_size)
|
||
|
||
# 将字节转换为numpy数组
|
||
audio_array = np.frombuffer(pcm_data, dtype=np.int16)
|
||
|
||
# 播放音频
|
||
stream.write(audio_array)
|
||
|
||
# 等待一帧的时间
|
||
time.sleep(60 / 1000) # 60ms
|
||
|
||
# 播放结束后添加1秒静音,避免破音
|
||
silence = np.zeros(int(sample_rate), dtype=np.int16)
|
||
stream.write(silence)
|
||
time.sleep(1) # 等待1秒
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n播放已停止")
|
||
finally:
|
||
stream.stop()
|
||
stream.close()
|
||
print("播放完成")
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description='播放p3格式的音频文件')
|
||
parser.add_argument('input_file', help='输入的p3文件路径')
|
||
args = parser.parse_args()
|
||
|
||
play_p3_file(args.input_file)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|