在Raspberry Pi Zero中使用Python Sounddevice播放音频,然后我需要重新启动一次

发布于 2025-02-08 14:27:58 字数 6241 浏览 1 评论 0原文

我的灵感来自这个项目将PI放入旧的旋转电话中。我正在使用Python将其栩栩如生,并执行我想执行的功能。第一个非常简单:当我拿起接收器时,请播放拨号音。

但是,当我尝试使用SoundDevice模块播放音频时,它将第一次使用,然后停止工作。而且,当我说停止工作时,我再也无法使用aplay说话者检验或其他任何内容播放音频,直到重新启动为止。它使我认为在播放结束时没有正确关闭的东西。

硬件设置:

下面是我的代码类,Dioltone,本来是由我的Core应用程序运行的线程,因此在必要时可以启动/停止拨号音。底部是main,旨在测试功能。它可以很好地工作,在窗户上一遍又一遍地,但是当我在pi上运行一次时,它只能使用一次。同时,我可以aplay一遍又一遍地没有问题(前提是我没有尝试使用Python播放它)。

难道它正在锁定某些资源而不是发布吗?在该过程退出后,它以某种方式保持活跃?我在哪里寻找?可能是什么?

class DialTone(Thread):

    def __init__(self, input_queue:Queue, device:int_or_str ) -> None:
        """Initialize the dialtone class 

        Args:
            input_queue (Queue): A way to communicate with the thread
        """
        super().__init__()
        global _DIAL_TONE_FILE

        self.input_queue = input_queue
        sd.default.device = device

        # Commit the dial-tone data to memory so we can start/end quickly
        try:
            self.data, self.fs = sf.read(_DIAL_TONE_FILE, always_2d=True)
        except Exception as e:
            logging.debug( type(e).__name__ + ":" + str(e) )
    
    def run(self) -> None:
        """Play a dial tone in a continuous loop until we die
        """
        while( True ):
            cmd = self.input_queue.get(True)
            if( cmd == DialToneComms.DT_START ):
                logging.debug("DT: starting dial tone")
                sd.play(self.data, self.fs, loop=True)
                logging.debug(sd.get_status())
            elif( cmd == DialToneComms.DT_END ):
                logging.debug("DT: stopping dial tone")
                try:
                    sd.stop(ignore_errors=False)
                except Exception as e:
                    logging.error( type(e).__name__ + ":" + str(e) )
                logging.debug(sd.get_status())
            elif( cmd == DialToneComms.DT_KILL ):
                logging.debug("DT: killing self")
                try:
                    sd.stop(ignore_errors=False)
                except Exception as e:
                    logging.error( type(e).__name__ + ":" + str(e) )
                logging.debug(sd.get_status())
                break

# Test this capability
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--device",type=int_or_str,help="Audio device to use", default=1)
    parser.add_argument("--play_time",type=float,help="how long to play the dial tone",default=10.0)
    parser.add_argument("--num_cycles", type=int, help="Number of play, wait cycles", default=2)
    args = parser.parse_args()

    # Setup logging
    logging.basicConfig(level=logging.DEBUG)

    dial_tone_queue = Queue()
    print( "Instantiating DialTone" )
    dial_tone = DialTone(dial_tone_queue, args.device)
    print( "Starting DialTone Thread" )
    dial_tone.start()

    # Play the dial tone a number of iterations
    for _ in range(args.num_cycles):
        print( "Playing dialtone for {} sec".format(args.play_time))
        dial_tone_queue.put(DialToneComms.DT_START)
        time.sleep(args.play_time)
        dial_tone_queue.put(DialToneComms.DT_END)
        
        print("Waiting 2 seconds")
        time.sleep(2)

    print("Kill the thread")
    dial_tone_queue.put(DialToneComms.DT_KILL)
    print("Joining DialTone")
    dial_tone.join(_JOIN_WAIT_TIME)

    if( dial_tone.is_alive() ):
        logging.warning( "Dial tone thread is still alive! What gives?" )
    else:
        logging.warning( "Dial tone thread is nice and dead")

在PI上进行第一次运行的输出。底部的错误是我停止了没有播放的流的结果(停止电话已经杀死了它,而杀戮电话肯定只是在确保)。

Instantiating DialTone
Starting DialTone Thread
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Kill the thread
Joining DialTone
DEBUG:root:DT: killing self
ERROR:root:PortAudioError:Error stopping stream: Invalid stream pointer [PaErrorCode -9988]
DEBUG:root:
WARNING:root:Dial tone thread is nice and dead

PI上的第二次输出。请注意,事情开始发生在秩序中,尤其是在停止之后,但是音频从未第二次播放。

Instantiating DialTone
Starting DialTone Thread
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
Playing dialtone for 3.0 sec
Waiting 2 seconds
Playing dialtone for 3.0 sec
Waiting 2 seconds
Kill the thread
Joining DialTone
DEBUG:root:
DEBUG:root:DT: starting dial tone
DEBUG:root:
DEBUG:root:DT: stopping dial tone
WARNING:root:Dial tone thread is still alive! What gives?
DEBUG:root:
DEBUG:root:DT: starting dial tone
DEBUG:root:
DEBUG:root:DT: stopping dial tone
DEBUG:root:
DEBUG:root:DT: killing self
ERROR:root:PortAudioError:Error stopping stream: Invalid stream pointer [PaErrorCode -9988]
DEBUG:root:

Windows上的输出:

Instantiating DialTone
Starting DialTone Thread
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
DEBUG:root:DT: stopping dial tone
Waiting 2 seconds
DEBUG:root:
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Kill the thread
DEBUG:root:DT: killing self
Joining DialTone
ERROR:root:PortAudioError:Error stopping stream: Invalid stream pointer [PaErrorCode -9988]
DEBUG:root:
WARNING:root:Dial tone thread is nice and dead

I was inspired by this project to put a Pi into an old rotary telephone. I'm using python to bring it to life and perform the functions I would like to perform. The first of these is pretty simple: play a dial tone when I pick up the receiver.

However, when I try to play audio using the sounddevice module, it will work the first time, and then stop working. And when I say stop working, I can no longer play audio with aplay or speaker-test or anything else until I reboot. It makes me think that something is not closing out correctly at the end of playback.

Hardware Setup:

Below is the code for my class, DialTone which is meant to be a thread run by my core application so it can start/stop the dial tone whenver necessary. At the bottom is a main meant to test the capability. It works just fine, over and over on Windows, but when I run it on the Pi: just once. Meanwhile I can aplay the same file over and over without issue (provided I haven't tried to play it with python).

Could it be that it's locking some resource and not releasing it? And somehow it's staying active after the process has exited? Where would I look for that? What could it be?

class DialTone(Thread):

    def __init__(self, input_queue:Queue, device:int_or_str ) -> None:
        """Initialize the dialtone class 

        Args:
            input_queue (Queue): A way to communicate with the thread
        """
        super().__init__()
        global _DIAL_TONE_FILE

        self.input_queue = input_queue
        sd.default.device = device

        # Commit the dial-tone data to memory so we can start/end quickly
        try:
            self.data, self.fs = sf.read(_DIAL_TONE_FILE, always_2d=True)
        except Exception as e:
            logging.debug( type(e).__name__ + ":" + str(e) )
    
    def run(self) -> None:
        """Play a dial tone in a continuous loop until we die
        """
        while( True ):
            cmd = self.input_queue.get(True)
            if( cmd == DialToneComms.DT_START ):
                logging.debug("DT: starting dial tone")
                sd.play(self.data, self.fs, loop=True)
                logging.debug(sd.get_status())
            elif( cmd == DialToneComms.DT_END ):
                logging.debug("DT: stopping dial tone")
                try:
                    sd.stop(ignore_errors=False)
                except Exception as e:
                    logging.error( type(e).__name__ + ":" + str(e) )
                logging.debug(sd.get_status())
            elif( cmd == DialToneComms.DT_KILL ):
                logging.debug("DT: killing self")
                try:
                    sd.stop(ignore_errors=False)
                except Exception as e:
                    logging.error( type(e).__name__ + ":" + str(e) )
                logging.debug(sd.get_status())
                break

# Test this capability
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--device",type=int_or_str,help="Audio device to use", default=1)
    parser.add_argument("--play_time",type=float,help="how long to play the dial tone",default=10.0)
    parser.add_argument("--num_cycles", type=int, help="Number of play, wait cycles", default=2)
    args = parser.parse_args()

    # Setup logging
    logging.basicConfig(level=logging.DEBUG)

    dial_tone_queue = Queue()
    print( "Instantiating DialTone" )
    dial_tone = DialTone(dial_tone_queue, args.device)
    print( "Starting DialTone Thread" )
    dial_tone.start()

    # Play the dial tone a number of iterations
    for _ in range(args.num_cycles):
        print( "Playing dialtone for {} sec".format(args.play_time))
        dial_tone_queue.put(DialToneComms.DT_START)
        time.sleep(args.play_time)
        dial_tone_queue.put(DialToneComms.DT_END)
        
        print("Waiting 2 seconds")
        time.sleep(2)

    print("Kill the thread")
    dial_tone_queue.put(DialToneComms.DT_KILL)
    print("Joining DialTone")
    dial_tone.join(_JOIN_WAIT_TIME)

    if( dial_tone.is_alive() ):
        logging.warning( "Dial tone thread is still alive! What gives?" )
    else:
        logging.warning( "Dial tone thread is nice and dead")

Output of first run on Pi. The error at the bottom is a result of me stopping a stream that's not playing (the stop call already killed it, and the kill call is just making sure).

Instantiating DialTone
Starting DialTone Thread
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Kill the thread
Joining DialTone
DEBUG:root:DT: killing self
ERROR:root:PortAudioError:Error stopping stream: Invalid stream pointer [PaErrorCode -9988]
DEBUG:root:
WARNING:root:Dial tone thread is nice and dead

Output of 2nd Run on Pi. note that things start happening out of order, particularly after the stop, but audio never plays on the second time.

Instantiating DialTone
Starting DialTone Thread
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
Playing dialtone for 3.0 sec
Waiting 2 seconds
Playing dialtone for 3.0 sec
Waiting 2 seconds
Kill the thread
Joining DialTone
DEBUG:root:
DEBUG:root:DT: starting dial tone
DEBUG:root:
DEBUG:root:DT: stopping dial tone
WARNING:root:Dial tone thread is still alive! What gives?
DEBUG:root:
DEBUG:root:DT: starting dial tone
DEBUG:root:
DEBUG:root:DT: stopping dial tone
DEBUG:root:
DEBUG:root:DT: killing self
ERROR:root:PortAudioError:Error stopping stream: Invalid stream pointer [PaErrorCode -9988]
DEBUG:root:

Output on Windows:

Instantiating DialTone
Starting DialTone Thread
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
DEBUG:root:DT: stopping dial tone
Waiting 2 seconds
DEBUG:root:
Playing dialtone for 3.0 sec
DEBUG:root:DT: starting dial tone
DEBUG:root:
Waiting 2 seconds
DEBUG:root:DT: stopping dial tone
DEBUG:root:
Kill the thread
DEBUG:root:DT: killing self
Joining DialTone
ERROR:root:PortAudioError:Error stopping stream: Invalid stream pointer [PaErrorCode -9988]
DEBUG:root:
WARNING:root:Dial tone thread is nice and dead

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文