用sounddevice实现连续的音乐曲库播放

前不久在做ASR的时候,是使用sounddevice来实现录音和播放功能(参见《树莓派智能语音助手之ASR2 – sherpa-ncnn-CSDN博客》和《树莓派智能语音助手之功能整合-CSDN博客》)。这次打算在此基础上实现连续的音乐曲库播放功能,用的还是sounddevice库。废话就不多说了,直接上代码。由于属于初学,若代码中出现啥bug,还请各位见谅。

import os
import json
import threading
import sounddevice as sd
import soundfile as sf

BASE_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 = 0
        self.data = None
        self.stream = None
        self.id = 0

    def _db_path(self):
        """获取曲目列表所在路径"""
        path = os.path.join(BASE_DIR, 'db/music.json')
        return path

    def 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:] = 0
            raise sd.CallbackStop()
        self.current_frame += chunksize

    def 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 = None
                    self.data = None
                    self.current_frame = 0
                    break

    def setId(self, id):
        """设置曲目编号"""
        self.id = id

    def getId(self):
        """获取当前曲目编号"""
        return self.id

    def 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 = 0
            self.play(musiclist[self.id])
            self.id += 1
            if self.stopped():
                if self.id == len(musiclist):
                    # 如果音乐线程结束且曲目也到最后一首,则完全退出
                    break
                else:
                    # 否则只清除暂停线程标志,进入下一首曲目播放
                    self._stop_event.clear()

代码就这么多,整个class是一个音乐播放线程,通过读取已经编辑好的音乐曲目,实现按顺序播放音乐。从第一首乐曲开始直至最后一首结束退出。调用这个class的方法如下:

t = music.MusicThread()
t.setId(3)
t.start() 

若没有t.setId()这行,就是从第一首开始播放。更多的操作可以基于class定义好的函数自行拓展。

最后,把生成曲目列表的代码也分享上来。

import os
import os.path
import json

BASE_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 files

def 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))
  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天飓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值