开源播放器mpv使用的血泪教训

最近根据需求做了一个视频采集的小工具,根据自己编程习惯,我选用了QT作为界面方案,开始一切顺利,添加关键词或链接,开始采集,采集完成预览,一切正常

后来应用户要求增加了视频在线预览的功能,根据经验与习惯选择了开源视频播放器mpv来实现,仍然比较顺利,完成后是这样

 

结果使用过程中发现一个比较奇葩的现象:不停的切换视频浏览的时候,过一段时间操作界面就会非常卡,还会时不时的出现未响应的提示!

开始排查问题原因:

1、测试时不停的切换视频,刚开始都正常,但是过一段时间后真就开始卡了,关闭或者暂停播放后操作又会比较正常,但一开始播放就出现卡的现象;

2、猜测原因可能是播放的过程中更新播放进度频率过高, 而操作界面更新事件太频繁导致软件界面无法及时响应,于是关闭进度条及时间更新,发现似乎好一点了,但还会出现;

3、期间因为mpv播放程序是单独一个线程处理,并且在切换播放视频流地址时,停止当前的播放,并开始播放新地址,完整操作代码:

import time
import mpv
from PyQt5.QtCore import QThread, pyqtSignal

from utils.tools import ignored


class VideoPlay(QThread):
    msg = pyqtSignal(str)
    pos = pyqtSignal(float)

    def __init__(self, parent=None):
        super(VideoPlay, self).__init__(parent)
        self.parent = parent
        self.video_file = None
        # 参考文档 https://pypi.org/project/python-mpv/
        self.player = mpv.MPV(
            wid=str(int(self.parent.videoShowWidget.winId())),
            # vo='x11',  # You may not need this
            log_handler=None,
            loglevel='debug'
        )
        self.pause()
        self.running = True

    def set_play_addr(self, video_file):
        self.video_file = video_file
        self.player.stop()

    def stop(self):
        self.video_file = None
        self.player.stop()

    def play(self):
        self.player.pause = False

    def pause(self):
        self.player.pause = True

    def seek(self, amount):
        try:
            self.player.seek(amount/1000, reference='absolute', precision="exact")
        except Exception as e:
            print(f'Player seek to {amount} failed! {e}')

    @property
    def duration(self):
        if not self.player.duration:
            return 0
        return float(self.player.duration)

    def _to_play(self):
        @self.player.property_observer('time-pos')
        def play_time_observer(_name, value):
            if isinstance(value, (float, int)):
                self.pos.emit(value*1000)
        try:
            self.player.play(self.video_file)
            self.player.wait_for_playback()
        except Exception as e:
            print(f'播放影片 - {e}')

    def run(self) -> None:
        while self.running:
            if self.video_file:
                self._to_play()
            time.sleep(0.2)

以为可能原因就是这个循环播放这里,于是重写了程序逻辑,切换播放地址时就终止旧线程,并开始新的播放线程,然而经测试问题依然没有解决。

4、于是怀疑mpv对象在播放大量视频后积累了较多的视频缓存,从而导致mpv对象不断变大,以至于程序负载变大,但是查看mpv文档均未发现有关说明,且监控mpv对象占用内存大小并未不断增大。

5、尝试关闭播放视频时的时间进度监控,意外发现程序不再卡顿,于是定位问题出现在时间进度监控处理函数中,屏蔽进度信号发送,但依然会出现卡顿,经过多次尝试突然发现在处理函数中增加监控值的打印后卡顿问题得到解决:

  @self.player.property_observer('time-pos')
        def play_time_observer(_name, value):
            # 加上这句,否则会出现较多数据堆积无法及时处理的问题
            print(f'{_name}={value}\n')
            if isinstance(value, (float, int)):
                self.pos.emit(value*1000)

 原因逐渐浮出水面,由于这个监控函数在视频播放中调用次数非常频繁,导致其占用了较多的cpu资源,加上python语言GIL程序锁的原因,主程序线程与播放线程共用同一个cpu核心,频繁切换播放地址导致播放线程时间监控处理函数积累了大量监控事件需要处理,主程序难以抢到cpu资源,因此界面操作出现阻塞,假死的情况,而增加print打印指令后,会使这个函数进入系统IO操作过程,主程序重新获得cpu资源,从而使主界面可以正常操作。其实这里的打印指令可以打印任何内容,不必是监控的时间数据,目的只在于让出cpu资源给主程序,经测试确实如此,至此,问题终于得到解决,历时近2天时间,终于填平此坑,血泪教训啊!

总结,程序运行过程中发现问题但不能快速定位问题原因时,可以尝试屏蔽部分功能,逐渐缩小导致问题出现的代码片段,再思考可能造成问题的原因,寻找解决方案。

 

posted @ 2024-01-23 14:50  超快排华哥  阅读(944)  评论(1)    收藏  举报