FFmpeg 4.3 H265十二:在屏幕上显示多路视频播放,可以有不同的分辨率,格式和帧率。

上图是在安防领域的要求,一般都是一个屏幕上有显示多个摄像头捕捉到的画面,这一节,我们是从文件中读取多个文件,显示在屏幕上。

一 改动UI文件

这里我们要添加两个label,为了区分我们设置一下背景色(这个是非必须的),并设置不同的objectname,分别为video1 和 video2(这个objectname是组件的唯一性标识)。

UI设计完成后长这样。

width1, width2, height1,heigth2 的值范围设定为 1-9999,表示视频的分辨率的 宽和高的范围

set_fpx1 和 set_fpx2的值范围为1-200,user可以设定 帧率

还有一个显示的fps1,fps2 ,后续会在 video1 和 video2上显示

二 构造函数中做的事情。

1. 有几个 显示 视频文件的 XSDL,就new几个XSDL,这个是必须做的。将每一个 winid 和 记住

getAllWidgetUI();

2.开启一个线程,线程函数 threadSingleViews,该线程的主要工作每隔1ms后,emit singleView()信号,进而执行槽函数 slotView。

槽函数的slotView 是在主线程,因为要刷新UI,那么 弄一个子线程的目的是啥呢?因为在子线程中不停的 sleep 1ms,因此不能放在主线程 

3.绑定信号和槽函数

       3.1 这里包括 所有的 点击 打开视频button  信号和槽函数,信号当然是 button的click 信号了,槽函数则是我们自定义的槽函数,点击 打开视频button 的操作 是选择一个 视频文件,然后将视频文件打开,并且 init sdl 。这里我们可能想到的是 开启一个线程,读取文件,然后直接show。理论上这个做法是可以的。当前的实现是没有开启线程的读取文件,显示文件的。这是因为如果有32个视频窗口,那么我们就要开启32个线程,这里教程中老师并没有使用这种方法。使用的方法是开启一个线程,在这个线程中,遍历所有的视频窗口,每隔1ms就

        3.2  子线程中emit singleView信号,进而让主线程的槽函数 slotView执行。

2.1 初始化 要显示视频的 vector<XVideoView *>

    //这个名字起的有问题,应该在构造函数中,有几个 显示 视频文件的 XSDL,就new几个XSDL,并将所有的XSDL都存放在一个 vector中,并将winid得到,为以后sdl init的时候创建 winid
    getAllWidgetUI();

void YUVRGBMultiWindowPlayer::getAllWidgetUI()
{
    //有几个显示视频的lable,就要create几个XVideoView*,注意这里只是 create了XSDL,并没有执行 XSDL的init工作。
    //那么 XSDL init的行为应该在什么时候调用呢? - 当user 点击了 button 选择文件后,再调用比较合理

    views_.push_back(XVideoView::create(XVideoView::ViewType::SDL_TYPE));
    views_.push_back(XVideoView::create(XVideoView::ViewType::SDL_TYPE));

    //将winid设置到每一个 video 内部
    views_[0]->setWinid((void *)this->ui.video1->winId());
    views_[1]->setWinid((void*)this->ui.video2->winId());

}

2.2 开启线程

这里使用 cpp的thread

    //开启线程,这里要给 thread 加前缀,是因为 QT 有一个同名函数 thread(),因此要告知 编译器是 CPP的thread,加上前缀std 
    threadSingleViews_ = std::thread(&YUVRGBMultiWindowPlayer::threadSingleViews, this);

    //该信号与槽 是在 子线程中使用,
    connect(this, &YUVRGBMultiWindowPlayer::singleView, this, &YUVRGBMultiWindowPlayer::slotView);

//子线程,每隔1ms发送一次 singleView信号,singleView 信号会truck 主线程中 槽函数 slotView执行
void YUVRGBMultiWindowPlayer::threadSingleViews()
{
    while (!is_exit_)
    {
        std::thread::id this_id = std::this_thread::get_id();

        //qDebug() << "threadSingleViews thread" << this_id;
        std::cout << "threadSingleViews Thread ID: " << this_id << std::endl;
        //qDebug() << "threadSingleViews start";
        emit singleView();
        MSleep(1);
    }
}

2.2.1 线程 发送 信号后调用的 主线程的槽函数

目的是遍历所有的 XVideoView,根据设置的setfps,读取 视频文件 成 AVFrame,并渲染AVFrame。这里如果在读取视频文件的时候,视频文件还没有open,或者说还不知道视频文件 分辨力的宽和高,则不去读取文件。那么什么时候 视频文件open呢?什么时候 知道 视频文件的分辨力和格式呢?要在user 点击 openbutton 的时候。

//  槽函数 slotView
void YUVRGBMultiWindowPlayer::slotView() {

    std::thread::id this_id = std::this_thread::get_id();

    //qDebug() << "slotView thread" << this_id;
    std::cout << "slotView Thread ID: " << this_id << std::endl;
    //得到 user 设置的 setfps。
    //qDebug() << "slotView call";
    setfps_[0] =(ui.setfps1->value());
    setfps_[1] = (ui.setfps2->value());
    //qDebug() << "slotView call  setfps_[0] = " << setfps_[0] <<  " setfps_[1] = " << setfps_[1];

    for (int i = 0; i < views_.size();i++) {
        //通过user设定的 setfps 计算多长时间 从视频文件读取数据,并显示一次。分为3步。
        //1.user设置的fps 为0时候的 error 处理
        if (setfps_[i] <= 0) {
            continue;
        }
        //2.需要的时间间隔,setfps中存储的是 user需要的帧数,例如user设定值为25,那说明40毫秒刷新一次
        //这意味着,我们需要记住上一次刷新时间,然后和现在的时间比较,如果<40毫秒就continue,大于等于40毫秒的时候才继续往下走,读取文件
        //lastshowfpstime_[32]数组用于记录上次刷新时间,lastshowfpstime_的类型为long long,是和NowMs()方法保持一致的。
        
        int flushtime = 1000 / setfps_[i];

        if (NowMs() - lastshowfpstime_[i] < flushtime) {
            //不满足条件,因此直接continue
            continue;
        }
        //3.如果满足了,说明需要到了需要读取文件和刷新视频的时机点了,首先需要给则给 lastshowfpstime赋值,
        lastshowfpstime_[i] = NowMs();
        qDebug() << "lastshowfpstime_[" << i <<"] = " << lastshowfpstime_[i];
        //这里第三步的时候,还有一个问题,就是当user 重新选择了一个视频的时候,lastshowfpstime_的值在什么时机变成0呢?
        //假设我们刚播放了一个视频,有1分钟,那么最后一次 lastshowfpstime_[i] = NowMs();这个值可能就超过1分钟了

        //到了这一步,说明真的需要然后得到
       auto avframe =  views_[i]->readAVFrameFormfile();
       if (avframe == nullptr) {
           continue;
       }
       views_[i]->DrawFrame(avframe);

       //还有一步,得到真正的fps,并显示
               //显示fps
       stringstream ss;
       ss << "fps:" << views_[i]->render_fps();
       if (i == 0)
           ui.fps1->setText(ss.str().c_str());
       else
           ui.fps2->setText(ss.str().c_str());
    }

}

2.3 给 open1 和 open2 添加信号和槽事件

    //绑定点击打开视频1 按钮,绑定的open1 槽函数
    connect(this->ui.open1, &QPushButton::clicked, this, &YUVRGBMultiWindowPlayer::open1);
    //绑定点击打开视频1 按钮,绑定的open2 槽函数
    connect(this->ui.open2, &QPushButton::clicked, this, &YUVRGBMultiWindowPlayer::open2);
    //为了扩展,我们这里可以在弄一个 void  open(int i ) 函数,以及方便后面添加更多的 ui

//点击打开视频文件1 按钮的槽函数
void YUVRGBMultiWindowPlayer::open1() {
    qDebug() << "button1 click";
    open(0);
}

//点击打开视频文件2 按钮的槽函数
void YUVRGBMultiWindowPlayer::open2() {
    qDebug() << "button2 click";
    open(1);
}

//点击打开视频文件x 按钮的槽函数
void YUVRGBMultiWindowPlayer::open(int i) {
    qDebug() << "button click " << i+1 ;
    //不管是哪一个按钮点击,目的都是打开一个 视频文件
    QString fileName = QFileDialog::getOpenFileName(this, 
        tr("Open video File"),
        "./",
        tr("Video (*.yuv *.rgba *.ragb *.rbga *.rgb)"));
    qDebug() << "fileName = " << fileName;

    //这时候要打开文件,openfile的方法可以写在 测试cpp中,也可以写在xvideoview中,因为不管是使用sdl,还是opengl,都是需要openfile的,因此我们可以写在xvideoview.h 中
    bool openfileok = views_[i]->openfile(fileName.toStdString());
    if (openfileok == false) {
        qDebug() << "views_[i]->openfile error fileName = " << fileName;
        return;
    }
   //AVFrame *  avframe = views_[i]->readAVFrameFormfile();

    //当user 点击 button 后,我们在这里得到 文件名。
    //那么下一步应该是 直接读取文件播放文件才对。在读取文件和播放文件之前,我们应该将sdl init 出来
    // sdl init 需要参数为  视频的 分辨率,视频格式,而我们的分辨率是在 界面上设定的,当然如果设定的不对,就不能正确的显示
    //这里有一个问题,就是只有在user open file的时候,才会设定 分辨率和 视频格式
    //假设我们在播放的时候,在改动 file的分辨率和 视频格式,是不起作用的
    int width = 0; //该视频的分辨率 width
    int height = 0; //该视频的分辨率 height
    QString fmtvalue = 0; //该视频的格式
    if (i ==0) {
        width = ui.width1->value();
        height = ui.height1->value();
        fmtvalue = ui.fmt1->currentText();

    }
    else if (i ==1) {
        width = ui.width2->value();
        height = ui.height2->value();
        fmtvalue = ui.fmt2->currentText();
    }
    
    //将qstring fmt 转换成 sdl认识的 格式
    XVideoView::Format fmt = XVideoView::YUV420P;
    if (fmtvalue == "YUV420P")
    {
        fmt = XVideoView::YUV420P;
    }
    else if (fmtvalue == "ARGB")
    {
        fmt = XVideoView::ARGB;
    }
    else if (fmtvalue == "RGBA")
    {
        fmt = XVideoView::RGBA;
    }
    else if (fmtvalue == "ABGR")
    {
        fmt = XVideoView::ABGR;
    }
    else if (fmtvalue == "BGRA")
    {
        fmt = XVideoView::BGRA;
    }
    //初始化窗口和材质
   bool initok =  views_[i]->Init(width, height, fmt);

   //qDebug() << "open button " << i + 1 << " initok = " << initok <<"  width_ &
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值