前不久在做ASR的时候,是使用sounddevice来实现录音和播放功能(参见《树莓派智能语音助手之ASR2 – sherpa-ncnn-CSDN博客》和《树莓派智能语音助手之功能整合-CSDN博客》)。这次打算在此基础上实现连续的音乐曲库播放功能,用的还是sounddevice库。废话就不多说了,直接上代码。由于属于初学,若代码中出现啥bug,还请各位见谅。
python">import os
import json
import threading
import sounddevice as sd
import soundfile as sfBASE_DIR = os.path.dirname(os.path.abspath(__file__))class MusicThread(threading.Thread):"""音乐播放线程"""def __init__(self, *args, **kwargs):super(MusicThread, self).__init__(*args, **kwargs)self._stop_event = threading.Event()self.current_frame = 0self.data = Noneself.stream = Noneself.id = 0def _db_path(self):"""获取曲目列表所在路径"""path = os.path.join(BASE_DIR, 'db/music.json')return pathdef load_list(self):"""获取曲目列表"""path = self._db_path()with open(path, 'r', encoding='utf-8') as f:return json.load(f)def stop(self):"""暂停音乐线程"""self._stop_event.set()def stopped(self):"""返回线程暂停标志"""return self._stop_event.is_set()def callback(self, outdata, frames, time, status):"""自定义的OutputStream的callback函数"""if status:print(status)chunksize = min(len(self.data) - self.current_frame, frames)outdata[:chunksize] = self.data[self.current_frame:self.current_frame + chunksize]if chunksize < frames:outdata[chunksize:] = 0raise sd.CallbackStop()self.current_frame += chunksizedef play(self, file):"""音乐播放程序"""self.data, fs = sf.read(file, always_2d=True)self.stream = sd.OutputStream(samplerate=fs, device=None, channels=self.data.shape[1],callback=self.callback, finished_callback=self.stop)with self.stream:while True:if self.stopped():# 若音乐线程已经暂停,则进行初始化self.stream = Noneself.data = Noneself.current_frame = 0breakdef setId(self, id):"""设置曲目编号"""self.id = iddef getId(self):"""获取当前曲目编号"""return self.iddef run(self):"""音乐线程执行程序"""# print("begin run the child thread")musiclist = self.load_list()while True:if self.id >= len(musiclist) or self.id < 0:self.id = 0self.play(musiclist[self.id])self.id += 1if self.stopped():if self.id == len(musiclist):# 如果音乐线程结束且曲目也到最后一首,则完全退出breakelse:# 否则只清除暂停线程标志,进入下一首曲目播放self._stop_event.clear()
代码就这么多,整个class是一个音乐播放线程,通过读取已经编辑好的音乐曲目,实现按顺序播放音乐。从第一首乐曲开始直至最后一首结束退出。调用这个class的方法如下:
python">t = music.MusicThread()
t.setId(3)
t.start()
若没有t.setId()这行,就是从第一首开始播放。更多的操作可以基于class定义好的函数自行拓展。
最后,把生成曲目列表的代码也分享上来。
python">import os
import os.path
import jsonBASE_DIR = os.path.dirname(os.path.abspath(__file__))def musicList(path):"""生成曲目列表"""files = []fileList = os.listdir(path) # 获取path目录下所有文件for filename in fileList:pathTmp = path+"/"+filename # 获取path与filename组合后的路径print(pathTmp)if os.path.isfile(pathTmp): # 判断是否为文件filetype = os.path.splitext(filename)[1]types = ['.wav', '.mp3', '.mid'] # 定义文件格式数组for k in types:if filetype == k:files.append(pathTmp)return filesdef initJson(files):"""将曲目列表保存到工程文件夹下"""path = os.path.join(BASE_DIR, 'db/music.json')with open(path, 'w', encoding='utf-8') as f:json.dump(files, f, ensure_ascii=False, indent=4)path= input("输入路径:").strip() #由用户指定文件路径
initJson(musicList(path))