You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python 2.7树莓派环境下,如何基于时间随机播放音频并解决检测中断问题

解决树莓派白噪音生成器:音频播放时持续时间检测的问题

嘿,我明白你的困扰了——当OMXPlayer启动播放后,你的Timeloop定时轮询就停了,没法自动根据时段切换音效对吧?这是因为当前代码把主线程完全占死了,没法同时处理定时任务和音频播放。咱们一步步来修复这个问题:

问题根源分析

  1. 阻塞式播放+递归调用play_audio函数里创建OMXPlayer实例后,直接递归调用自己,OMXPlayer的播放是阻塞主线程的,加上递归直接让主线程陷入无限循环,Timeloop的定时任务根本没机会执行。
  2. 播放器实例作用域错误stay_or_change里的player.quit()调用时,playerplay_audio里的局部变量,全局未定义,会抛出NameError
  3. 阻塞式进程操作subprocess.call会等待命令执行完成才继续,虽然这里影响不大,但换成非阻塞方式更合适。

解决方案:用线程分离音频播放与时间检测

我们可以用Python的threading模块把音频播放放到单独的线程里,这样主线程就能继续处理Timeloop的定时轮询任务。同时我们需要全局保存播放器实例,方便后续停止或切换。

修改后的完整代码

#!/usr/bin/env python
import datetime, time, os, subprocess, random, threading
from timeloop import Timeloop
from datetime import timedelta
from time import sleep
from omxplayer.player import OMXPlayer
from pathlib import Path

tl = Timeloop()
running_cycle = "off"  # 默认时段状态
current_player = None  # 全局保存当前播放器实例

# 检查当前所属时段
def check_time():
    dt_now = datetime.datetime.now()
    t_now = dt_now.time()
    t_night = datetime.time(hour=2, minute=0)
    t_twilight = datetime.time(hour=4, minute=45)
    t_morning = datetime.time(hour=7, minute=0)
    t_end = datetime.time(hour=10, minute=0)
    
    if t_night <= t_now < t_twilight:
        return "night"
    elif t_twilight <= t_now < t_morning:
        return "twilight"
    elif t_morning <= t_now < t_end:
        return "morning"
    else:
        return "off"

# 音频播放线程函数(后台运行)
def play_audio_thread(time_cycle):
    global current_player
    # 先停止当前正在播放的音频
    if current_player:
        try:
            current_player.quit()
        except Exception as e:
            print(f"停止播放器出错: {e}")
    # 清理残留的omxplayer进程(防止异常退出后进程残留)
    subprocess.Popen("killall -9 omxplayer.bin", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    # 随机选择当前时段的音频文件
    sound_dir = f"/home/pi/music/nature-sounds/{time_cycle}"
    random_file = random.choice(os.listdir(sound_dir))
    file_path = Path(f"{sound_dir}/{random_file}")
    
    # 启动新的播放器
    print(f"开始播放: {time_cycle} 时段 - 文件: {random_file} @ {time.ctime()}")
    current_player = OMXPlayer(file_path)
    # 等待当前音频播放结束
    current_player.wait_for_end()
    # 如果当前时段未改变,继续播放同时段的随机音频
    if running_cycle == time_cycle:
        play_audio_thread(time_cycle)

# 判断是否需要切换时段并执行对应操作
def stay_or_change():
    global running_cycle
    current_cycle = check_time()
    
    if running_cycle != current_cycle:
        running_cycle = current_cycle
        print(f"切换到时段: {running_cycle} @ {time.ctime()}")
        
        if current_cycle == "off":
            # 停止所有音频播放
            if current_player:
                try:
                    current_player.quit()
                except Exception as e:
                    print(f"停止播放器出错: {e}")
                subprocess.Popen("killall -9 omxplayer.bin", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                current_player = None
        else:
            # 启动新的音频播放线程(守护线程,主程序退出时自动终止)
            threading.Thread(target=play_audio_thread, args=(current_cycle,), daemon=True).start()

# 10秒定时轮询任务,检查时段变化
@tl.job(interval=timedelta(seconds=10))
def job_10s():
    print(f"10秒轮询 - 当前运行时段: {running_cycle} - 当前时间: {time.ctime()}")
    stay_or_change()

# 主程序入口
if __name__ == "__main__":
    # 启动时先检查一次时段,确保立即播放对应音频
    stay_or_change()
    tl.start(block=True)

关键修改点说明

  • 线程分离播放逻辑:用threading.Thread把音频播放放到后台线程,主线程不会被阻塞,Timeloop可以继续定时轮询时段变化。
  • 全局播放器实例:用current_player全局变量保存当前播放器,方便在切换时段时随时停止播放。
  • 移除递归调用:播放结束后通过判断当前时段是否不变,决定是否继续播放同时段音频,避免递归导致的栈溢出问题。
  • 非阻塞进程操作:用subprocess.Popen替代subprocess.call,杀死残留进程时不会阻塞主线程。
  • 启动初始化:主程序启动时先调用一次stay_or_change(),确保启动时就播放对应时段的音频。

替代方案:用subprocess后台运行OMXPlayer

如果你不想依赖OMXPlayer库,也可以直接用subprocess.Popen后台运行omxplayer命令,这样更轻量:

def play_audio_thread(time_cycle):
    global current_process
    # 停止当前进程
    if current_process:
        current_process.terminate()
        current_process.wait()
    # 清理残留进程
    subprocess.Popen("killall -9 omxplayer.bin", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
    sound_dir = f"/home/pi/music/nature-sounds/{time_cycle}"
    random_file = random.choice(os.listdir(sound_dir))
    file_path = f"{sound_dir}/{random_file}"
    
    # 后台运行omxplayer(-o local 指定输出到本地音频设备)
    current_process = subprocess.Popen(
        ["omxplayer", "-o", "local", file_path],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
    current_process.wait()
    # 时段未变则继续播放
    if running_cycle == time_cycle:
        play_audio_thread(time_cycle)

注意需要提前定义全局的current_process变量来管理后台进程。

测试与注意事项

  • 确保树莓派已安装依赖:pip install timeloopomxplayer一般树莓派官方系统自带。
  • 检查音频文件路径是否正确,确保/home/pi/music/nature-sounds/下有nighttwilightmorning三个子目录,且目录内有可播放的音频文件。
  • 线程设置为daemon=True,保证主程序退出时后台线程会自动终止,避免残留进程。

内容的提问来源于stack exchange,提问作者tshimkus

火山引擎 最新活动