PyQt 多线程播放视频(QThread、QRunnable、moveToThread三种线程播放方式)

PyQt QThread、QRunnable、moveToThread三种线程方式实现视频的播放

搜索PyQt视频播放方法的时候,大多数都是先将视频进行解码,获得图像帧后,用QTime定时器和QThread的方式来控制QLabel中图像的更新,其实除此之t外PyQ还可以通过QRunnable、moveToThread方法来对线程进行操作。我在这里做一个简单的总结(以下方法主要针对实时视频,录像的快进和快退功能暂时不做探讨)。

先看一下效果:
PyQt 多线程播放视频(QThread、QRunnable、moveToThread三种线程播放方式)_第1张图片
通常情况下会将视频的解码和播放都放在同一个线程中,但是视频解码,图像帧操作的时候都会消耗一定的时间。为了保证视频播放的实时性,我将解码和播放分为两个线程,通过队列来传递图像帧。这样也方便更换视频的解码方式。
解码线程代码如下:

Decode2Play = Queue()
class cvDecode(QThread):
    def __init__(self):
        super(cvDecode, self).__init__()
        self.threadFlag = 0     #   控制线程退出
        self.video_path = ""    #   视频文件路径
        self.changeFlag = 0     #   判断视频文件路径是否更改
        self.cap = cv2.VideoCapture()
    def run(self):
        while self.threadFlag:
            if self.changeFlag == 1 and self.video_path !="":
                self.changeFlag = 0
                self.cap = cv2.VideoCapture(r""+self.video_path)

            if self.video_path !="":
                if self.cap.isOpened():
                    ret, frame = self.cap.read()
                    time.sleep(0.04)   # 控制读取录像的时间,连实时视频的时候改成time.sleep(0.001),多线程的情况下最好加上,否则不同线程间容易抢占资源

                    # 下面两行代码用来控制循环播放的,如果不需要可以删除
                    if frame is None:
                        self.cap = cv2.VideoCapture(r"" + self.video_path)

                    if ret:
                        Decode2Play.put(frame)  # 解码后的数据放到队列中
                    del frame  # 释放资源
                else:
                    #   控制重连
                    self.cap = cv2.VideoCapture(r"" + self.video_path)
                    time.sleep(0.01)

视频导入和播放/暂停功能

   #   视频导入功能
    def load_path(self):
        self.btn_pause.setEnabled(True)
        #   设置文件扩展名过滤,注意用双分号间隔
        fileName, filetype = QFileDialog.getOpenFileName(self, "选取文件", "./", "Excel Files (*.mp4);;Excel Files (*.avi)")

        self.decodework.changeFlag = 1
        self.decodework.video_path = r""+fileName
        self.playwork.playFlag = 1

    #   暂停/播放功能
    def pause_video(self):
        if self.btn_pause.text()=="暂停":
            self.btn_pause.setText("播放")
            self.playwork.playFlag = 0
        else:
            self.btn_pause.setText("暂停")
            self.playwork.playFlag = 1
  • QThread

该方法是比较常见的方法,通过在继承QThread的play_Work类中重写run方法来实现:
线程类方法:

class play_Work(QThread):
    def __init__(self):
        super(play_Work, self).__init__()
        self.threadFlag = 0         #   控制线程退出
        self.playFlag = 0           #   控制播放/暂停
        self.playLabel = QLabel()   #   初始化QLabel对象

    def run(self):
        while self.threadFlag:
            if not Decode2Play.empty():
                frame = Decode2Play.get()
                if self.playFlag == 1:
                    frame = cv2.resize(frame, (760, 480), cv2.INTER_LINEAR)
                    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    qimg = QImage(frame.data, frame.shape[1], frame.shape[0],
                                  QImage.Format_RGB888)  # 在这里可以对每帧图像进行处理,
                    self.playLabel.setPixmap(QPixmap.fromImage(qimg))   #   图像在QLabel上展示
            time.sleep(0.001)

初始化线程:

   	def init_work(self):
        self.decodework = cvDecode()
        self.decodework.threadFlag = 1
        self.decodework.start()

        self.playwork = play_Work()
        self.playwork.threadFlag = 1
        self.playwork.playLabel = self.label_video
        self.playwork.start()

关闭线程:

    def closeEvent(self, event):
        print("关闭线程")
        # Qt需要先退出循环才能关闭线程
        if self.decodework.isRunning():
            self.decodework.threadFlag = 0
            self.decodework.quit()
        if self.playwork.isRunning():
            self.playwork.threadFlag = 0
            self.playwork.quit()
  • QRunnable

使用该方法时类需要继承PyQt的线程池QRunnable,重写run方法,需要注意的是使用QRunnable方法时,不能在类中使用pyqtSignal。
使用该方法时,play_Work类只需将前面的QThread改成QRunnable:

class play_Work(QRunnable):

初始化时:

    def init_work(self):
        self.decodework = cvDecode()
        self.decodework.threadFlag = 1
        self.decodework.start()

        self.playwork = play_Work()
        self.playwork.threadFlag = 1
        self.playwork.playLabel = self.label_video

        self.pool = QThreadPool()  # Qt线程池
        self.pool.globalInstance()
        self.pool.setMaxThreadCount(10)  # 设置最大线程
        self.pool.start(self.playwork)

关闭线程:

    def closeEvent(self, event):
        print("关闭线程")
        # Qt需要先退出循环才能关闭线程
        if self.decodework.isRunning():
            self.decodework.threadFlag = 0
            self.decodework.quit()
        self.playwork.threadFlag = 0
        self.pool.clear()
  • moveToThread

该方法通过创建一个线程,将创建的线程与类方法进行绑定之后实现的,相当于在线程中操作类方法。使用时play_Work继承的是QObject,所以不需要重写run方法。
类方法:

class play_Work(QObject):
    def __init__(self):
        super(play_Work, self).__init__()
        self.threadFlag = 0         #   控制线程退出
        self.playFlag = 0           #   控制播放/暂停
        self.playLabel = QLabel()   #   初始化QLabel对象

    #   不需要重写run方法
    def play(self):
        while self.threadFlag:
            if not Decode2Play.empty():
                frame = Decode2Play.get()
                if self.playFlag == 1:
                    frame = cv2.resize(frame, (760, 480), cv2.INTER_LINEAR)
                    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    qimg = QImage(frame.data, frame.shape[1], frame.shape[0],
                                  QImage.Format_RGB888)  # 在这里可以对每帧图像进行处理,
                    self.playLabel.setPixmap(QPixmap.fromImage(qimg))   #   图像在QLabel上展示
            time.sleep(0.001)

初始化时:

    def init_work(self):
        self.decodework = cvDecode()
        self.decodework.threadFlag = 1
        self.decodework.start()

        self.playwork = play_Work()
        self.playwork.threadFlag = 1
        self.playwork.playLabel = self.label_video
        self.play_thread = QThread()    # 创建线程
        self.playwork.moveToThread(self.play_thread)
        self.play_thread.started.connect(self.playwork.play)    # 线程与类方法进行绑定
        self.play_thread.start()

关闭线程:

   def closeEvent(self, event):
        print("关闭线程")
        # Qt需要先退出循环才能关闭线程
        if self.decodework.isRunning():
            self.decodework.threadFlag = 0
            self.decodework.quit()
        if self.play_thread.isRunning():
            self.playwork.threadFlag = 0
            self.play_thread.quit()
  • 总结

上述的三种线程创建和使用都各有优缺,按需使用,具体的优缺请自行百度。cvDecode类也可以使用上述的其他线程方式启动,可以自行尝试。然后相信大家都发现了,此代码启动的时候,线程是空跑状态会占用一定资源。大家可以自行修改。最后我懒,先放个百度的代码链接,如果链接失效评论或者私信告知一下,以后有时间再上传到GitHub上:
链接:https://pan.baidu.com/s/1pQbyaSM8LfdMu58iDsL3nQ
提取码:zaqk

你可能感兴趣的:(其他,多线程,python,pyqt5)