【问题标题】:PySide/python video player issuePySide/python 视频播放器问题
【发布时间】:2015-09-14 04:48:42
【问题描述】:

我正在尝试使用 python 编写一个简单的 YUV 视频播放器。经过一些初步研究,我认为我可以使用 PySide 并开始使用它。作为第一步,我在不考虑实时性能的情况下采用了以下方法。 读取 YUV 缓冲区(420 平面)-> 将 YUV 图像转换为 RGB(32 位格式)-> 调用 PySide 实用程序进行显示。我的简单程序的基本问题是,我只能显示第一帧,其余的不显示,即使根据(下面)代码中的计数器似乎正在发生绘制事件。我将不胜感激任何 cmets 理解 (i) 我对在 QLabel/QWidget 上定期绘制/重新绘制的任何错误和缺乏理解。 (ii) 任何指向来自 YUV 或 RGB 源的基于 Python 的视频播放器/显示器的指针。

    #!/usr/bin/python

import sys
from PySide.QtCore import *
from PySide.QtGui import *
import array
import numpy as np

class VideoWin(QWidget):
    def __init__(self, width, height, f_yuv):
        QWidget.__init__(self)
        self.width = width
        self.height = height
        self.f_yuv = f_yuv
        self.setWindowTitle('Video Window')
        self.setGeometry(10, 10, width, height)
        self.display_counter = 0
        self.img = QImage(width, height, QImage.Format_ARGB32)
        #qApp.processEvents()

    def getImageBuf(self):
        return self.img.bits()

    def paintEvent(self, e):
        painter = QPainter(self)
        self.display_counter += 1
        painter.drawImage(QPoint(0, 0), self.img)
    def timerSlot(self):
        print "In timer"
        yuv = array.array('B')
        pix = np.ndarray(shape=(height, width), dtype=np.uint32, buffer=self.getImageBuf())

        for i in range(0,self.height):
            for j in range(0, self.width):
                pix[i, j] = 0

        for k in range (0, 10):
            #qApp.processEvents()
            yuv.fromfile(self.f_yuv, 3*self.width*self.height/2)
            for i in range(0, self.height):
                for j in range(0, self.width):
                    Y_val = yuv[(i*self.width)+j]
                    U_val = yuv[self.width*self.height + ((i/2)*(self.width/2))+(j/2)]
                    V_val = yuv[self.width*self.height + self.width*self.height/4 + ((i/2)*(self.width/2))+(j/2)]
                    C = Y_val - 16
                    D = U_val - 128
                    E = V_val - 128
                    R = (( 298 * C           + 409 * E + 128) >> 8)
                    G = (( 298 * C - 100 * D - 208 * E + 128) >> 8)
                    B = (( 298 * C + 516 * D           + 128) >> 8)
                    if R > 255:
                        R = 255
                    if G > 255:
                        G = 255
                    if B > 255:
                        B = 255

                    assert(int(R) < 256)
                    pix[i, j] = (255 << 24 | ((int(R) % 256 )<< 16) | ((int(G) % 256 ) << 8) | (int(B) % 256))

            self.repaint()
            print "videowin.display_counter = %d" % videowin.display_counter


if __name__ == "__main__":
    try:
        yuv_file_name = sys.argv[1]
        width = int(sys.argv[2])
        height = int(sys.argv[3])
        f_yuv = open(yuv_file_name, "rb")

        videoApp = QApplication(sys.argv)

        videowin = VideoWin(width, height, f_yuv)

        timer = QTimer()
        timer.singleShot(100, videowin.timerSlot)

        videowin.show()
        videoApp.exec_()


        sys.exit(0)
    except NameError:
        print("Name Error : ", sys.exc_info()[1])
    except SystemExit:
        print("Closing Window...")
    except Exception:
        print(sys.exc_info()[1])

我尝试了第二种方法,我尝试了创建一个 Signal 对象的组合,该对象“发射”每个解码的 RGB 图像(从 YUV 转换)作为信号,该信号被显示类中的“updateFrame”方法捕获使用 QPainter.drawImage(...) 方法显示接收到的 RGB 缓冲区/帧。 YUV-to-RGB decode--->Signal(Image buffer) --->updateFrame ---> QPainter.drawImage(...) 这也仅显示第一张图像,尽管捕获信号(获取图像)的插槽显示它被调用的次数与 YUV->RGB 转换器/解码器发送的信号一样多。我也试过在单独的线程中运行 YUV->RGB 转换器和视频显示(调用 drawImage),但结果是一样的。

请注意,在这两种情况下,我都将 RGB 像素值直接写入 QImage 对象的位缓冲区,该对象是所示代码中 VideoWin 类的一部分(注意:代码行 pix = np.ndarray( shape=(height, width), dtype=np.uint32, buffer=videowin.getImageBuf()) 获取 QImage 类的 img.bits() 缓冲区) 此外,对于这个测试,我只解码和显示视频文件的前 10 帧。 版本:Python - 2.7,Qt - 4.8.5 使用 Pyside

【问题讨论】:

  • 您的示例代码的根本问题在于,它在事件循环开始之前完成了所有处理。您需要先启动事件循环,然后调用一个进行处理的槽(您可以为此使用QTimer.singleShot,或者只是向用户界面添加一个按钮)。您可能还想考虑使用QApplication.processEvents
  • 嗨,ekhumoro,感谢您的意见。我不确定我是否明白你的意思。我已经按照上面编辑的代码尝试了更改,但这没有帮助。仅绘制第一帧,即使调用了 10 次paintEvent SLOT(对于 10 帧视频输入)。
  • 也许你应该提供你正在使用的 yuv 文件的链接,以便其他人可以测试你的代码。
  • 我在dropbox.com/s/e42iyvv40q2zw2m/test.yuv?dl=0保存了一个10帧流test.yuv
  • test.yuv的分辨率为1920x1080

标签: python qt numpy video pyside


【解决方案1】:

来自array.fromfile() 的文档:

从文件对象 f 中读取 n 个项目(作为机器值)并将它们附加到数组的末尾。 [强调]

示例代码不包含数组中的偏移量,因此会一遍又一遍地读取第一帧。一个简单的解决方法是在读取下一帧之前清除数组:

    for k in range (0, 100):
        del yuv[:]
        yuv.fromfile(self.f_yuv, 3*self.width*self.height/2)

请注意,要查看差异,您需要阅读至少 60 帧链接到的测试文件,因为前 50 帧左右都是相同的(即纯绿色背景)。

【讨论】:

  • 这是一个伟大的观点,我很愚蠢地错过了来自 doc 的这一点。但是,问题并没有完全解决。我现在看到了 YUV 文件中 10 帧中的第一帧和最后一帧。中间的帧(帧号:s 1-8)不显示。实际上只有第一帧是黑色/绿色,其余的 9 帧都有适当的演示内容。您可以使用 YUV 播放器查看它们(帧分辨率为 1920x1080)。我以这样一种方式制作了这个流,所有 10 帧都是不同的,因此即使播放 10 帧也很容易观察到变化。我会再次检查我的代码。
【解决方案2】:

我已经根据对建议的程序进行了一些修改(和扩展)来完成这项工作 Displaying a video stream in QLabel with PySide。 我在处理和显示之间添加了双缓冲机制,使用数组读取 YUV 文件,最后将 Yuv2Rgb 转换作为单独的线程运行。这对我有用 - 即按顺序显示文件中的所有帧。 这是任何建议和改进的程序。 感谢您迄今为止的所有指点! 请注意,这不是实时运行的!

#!/usr/bin/python

import sys
import time
from threading import Thread
from PySide.QtCore import *
from PySide.QtGui import *
from PIL import Image
import array
import struct
import numpy as np


class VideoDisplay(QLabel):
    def __init__(self):
        super(VideoDisplay, self).__init__()
        self.disp_counter = 0

    def updateFrame(self, image):
        self.disp_counter += 1
        self.setPixmap(QPixmap.fromImage(image))


class YuvVideoPlayer(QWidget):
    video_signal = Signal(QImage)
    video_display = None

    def __init__(self, f_yuv, width, height):
        super(YuvVideoPlayer, self).__init__()
        print "Setting up YuvVideoPlayer params"
        self.img = {}
        self.img[0] = QImage(width, height, QImage.Format_ARGB32)
        self.img[1] = QImage(width, height, QImage.Format_ARGB32)
        self.video_display = VideoDisplay()
        self.video_signal.connect(self.video_display.updateFrame)
        grid = QGridLayout()
        grid.setSpacing(10)
        grid.addWidget(self.video_display, 0, 0)
        self.setLayout(grid)
        self.setGeometry(0, 0, width, height)
        self.setMinimumSize(width, height)
        self.setMaximumSize(width, height)
        self.setWindowTitle('Control Center')
        print "Creating display thread"
        thYuv2Rgb = Thread(target=self.Yuv2Rgb, args=(f_yuv, width, height))
        print "Starting display thread"
        thYuv2Rgb.start()
        self.show()


    def Yuv2Rgb(self, f_yuv, width, height):
        '''This function gets called by an external thread'''
        try:
            yuv = array.array('B')
            pix = {}
            pix[0] = np.ndarray(shape=(height, width), dtype=np.uint32, buffer=self.img[0].bits())
            pix[1] = np.ndarray(shape=(height, width), dtype=np.uint32, buffer=self.img[1].bits())
            for i in range(0,height):
                for j in range(0, width):
                    pix[0][i, j] = 0
                    pix[1][i, j] = 0

            for k in range (0, 10):
                yuv.fromfile(f_yuv, 3*width*height/2)
                #y = yuv[0:width*height]
                for i in range(0, height):
                    for j in range(0, width):
                        Y_val = yuv[(i*width)+j]
                        U_val = yuv[width*height + ((i/2)*(width/2))+(j/2)]
                        V_val = yuv[width*height + width*height/4 + ((i/2)*(width/2))+(j/2)]

                        C = Y_val - 16
                        D = U_val - 128
                        E = V_val - 128
                        R = (( 298 * C           + 409 * E + 128) >> 8)
                        G = (( 298 * C - 100 * D - 208 * E + 128) >> 8)
                        B = (( 298 * C + 516 * D           + 128) >> 8)
                        if R > 255:
                            R = 255
                        if G > 255:
                            G = 255
                        if B > 255:
                            B = 255

                        pix[k % 2][i, j] = (255 << 24 | ((int(R) % 256 )<< 16) | ((int(G) % 256 ) << 8) | (int(B) % 256))
                self.video_signal.emit(self.img[k % 2])
                print "Complted pic num %r, disp_counter = %r" % (k, self.video_display.disp_counter)
                del yuv[:]


        except Exception, e:
            print(e)

if __name__ == "__main__":
    print "In Main"
    yuv_file_name = sys.argv[1]
    width = int(sys.argv[2])
    height = int(sys.argv[3])
    f_yuv = open(yuv_file_name, "rb")

    app = QApplication(sys.argv)
    print "Creating YuvVideoPlayer object"
    ex = YuvVideoPlayer(f_yuv, width, height)
    #ex.up_Video_callback(f_yuv, width, height)
    app.exec_()

    sys.exit(0)

【讨论】:

    猜你喜欢
    • 2014-12-21
    • 1970-01-01
    • 1970-01-01
    • 2021-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-01
    • 1970-01-01
    相关资源
    最近更新 更多