上图是在安防领域的要求,一般都是一个屏幕上有显示多个摄像头捕捉到的画面,这一节,我们是从文件中读取多个文件,显示在屏幕上。
一 改动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_ &