xviewer.h 中的回放页面的三个槽函数:
void SelectCamera(QModelIndex index);//选择摄像机129
void SelectDate(QDate date); //选择日期129
void PlayVideo(QModelIndex index); //选择时间播放视频129
SelectCamera槽函数解析:
点击相机列表日历显示该相机所录制的视频,如有则日期标为红色并放大字体,如四号有录制的视频那么四号变红放大点击进去可以回放视频
void XViewer::SelectCamera(QModelIndex index)//选择摄像机
{
qDebug() << "SelectCamera" << index.row();
auto conf = XCameraConfig::Instance();
XCameraData cam = conf->GetCam(index.row()); //获取相机参数
if (cam.name[0] == '\0')
{
return;
}
//相机视频存储路径
stringstream ss;
ss << cam.save_path << "/" << index.row() << "/";
//遍历此目录
QDir dir(C(ss.str().c_str()));
if (!dir.exists())
return;
//获取目录下文件列表
QStringList filters;
filters << "*.mp4" << "*.avi";
dir.setNameFilters(filters);//筛选
ui.cal->ClearDate();
//所有文件列表
auto files = dir.entryInfoList();
for (auto file : files)
{
//"cam_2020_09_04_17_54_58.mp4"
QString filename = file.fileName();
//去掉cam_ 和 .mp4
auto tmp = filename.left(filename.size() - 4);
tmp = tmp.right(tmp.length() - 4);
//2020_09_04_17_54_58
auto dt = QDateTime::fromString(tmp, "yyyy_MM_dd_hh_mm_ss");
qDebug() << dt.date();//yyyy_MM_dd形式
ui.cal->AddDate(dt.date());
//qDebug() << file.fileName();
130
XCamVideo video;
video.datetime = dt;
video.filepath = file.absoluteFilePath();
cam_videos[dt.date()].push_back(video);
130
}
//重新显示日期
ui.cal->showNextMonth();
ui.cal->showPreviousMonth();
}
上面用到的结构体 和数据结构map
struct XCamVideo
{
QString filepath;
QDateTime datetime;
};
static map<QDate, vector<XCamVideo> > cam_videos;//通过点击QDate来索引一组数据
-
qDebug() << "SelectCamera" << index.row();
:- 打印调试信息,显示被选中的摄像机的行号。
-
auto conf = XCameraConfig::Instance();
:- 获取
XCameraConfig
类的单例实例,假设这是一个配置管理器。
- 获取
-
XCameraData cam = conf->GetCam(index.row());
:- 通过行号获取对应的摄像机数据
XCameraData
。
- 通过行号获取对应的摄像机数据
-
if (cam.name[0] == '\0') { return; }
:- 检查摄像机的名称是否为空,如果是,则返回,终止函数执行。
stringstream ss; ss << cam.save_path << "/" << index.row() << "/";
:- 创建一个
stringstream
对象,将摄像机视频存储路径和行号组合成一个完整的目录路径。 QDir dir(C(ss.str().c_str()));
:- 创建一个
QDir
对象,表示生成的目录路径。
- 创建一个
if (!dir.exists()) return;
:- 检查目录是否存在,如果不存在,返回,终止函数执行
QStringList filters; filters << "*.mp4" << "*.avi";
:- 创建一个
QStringList
对象,并添加视频文件的扩展名过滤器。
- 创建一个
dir.setNameFilters(filters);
:- 设置
QDir
对象的名称过滤器,使其只包含指定扩展名的文件。
- 设置
ui.cal->ClearDate();
:- 清除日历控件中的所有日期。
auto files = dir.entryInfoList();
:- 获取目录中的所有文件列表
for (auto file : files)
:- 遍历所有文件。
QString filename = file.fileName();
:- 获取文件名。
auto tmp = filename.left(filename.size() - 4);
:- 去掉文件名的扩展名部分(后四个字符)。
tmp = tmp.right(tmp.length() - 4);
:- 去掉文件名前缀部分(前四个字符)。
auto dt = QDateTime::fromString(tmp, "yyyy_MM_dd_hh_mm_ss");
:- 将处理后的文件名转换为
QDateTime
对象。
- 将处理后的文件名转换为
qDebug() << dt.date();
:- 打印调试信息,显示日期部分。
ui.cal->AddDate(dt.date());
:- 将日期添加到日历控件中。
XCamVideo video;
:- 创建一个
XCamVideo
对象。
- 创建一个
video.datetime = dt; video.filepath = file.absoluteFilePath();
:- 设置
XCamVideo
对象的日期时间和文件路径。
- 设置
cam_videos[dt.date()].push_back(video);
:- 将视频对象添加到以日期为键的
cam_videos
映射中,该映射在vector中
- 将视频对象添加到以日期为键的
ui.cal->showNextMonth(); ui.cal->showPreviousMonth();
:- 重新显示日历控件的日期,确保更新后的日期显示正确。
总结
这个槽函数 SelectCamera(QModelIndex index)
主要完成以下任务:
- 获取选中的摄像机的配置信息。
- 构造摄像机视频文件存储路径。
- 检查路径是否存在,并筛选出
.mp4
和.avi
文件。 - 清除日历控件中的日期。
- 遍历筛选出的文件列表,提取文件名中的日期信息并转换为
QDateTime
对象。 - 将日期添加到日历控件中,并将视频文件的信息保存到
cam_videos
映射中。 - 重新显示日历控件,以反映更新后的日期。
SelectDate槽函数解析:
点击日历上的日期显示该日期录制的视频时间列表
void XViewer::SelectDate(QDate date) //选择日期
{
qDebug() << "SelectDate" << date.toString();
vector<XCamVideo> dates = cam_videos[date];
//dates存的以下数据
//struct XCamVideo
//{
// QString filepath;
// QDateTime datetime;
//};
ui.time_list->clear();
for (auto d : dates)
{
//再QListwidget控件上添加项,显示的是具体时间QTime类型
QListWidgetItem* item = new QListWidgetItem(d.datetime.time().toString());
//将视频文件的路径存储在列表项的用户角色数据中。
//Qt::UserRole 是一个预定义的角色值,用于存储用户自定义的数据。
//在点击QListwidget列表项的时候会用到(PlayVideo槽函数中)
item->setData(Qt::UserRole, d.filepath);
ui.time_list->addItem(item);
}
}
auto dates = cam_videos[date];
:- 从
cam_videos
映射中获取与选定日期相关的视频数据列表。cam_videos
是一个以日期为键、以视频数据列表为值的映射。
- 从
ui.time_list->clear();
:- 清空
time_list
列表控件,以便重新填充新的数据。
- 清空
-
for (auto d : dates)
:- 遍历选定日期的视频数据列表。
-
auto item = new QListWidgetItem(d.datetime.time().toString());
:- 创建一个新的
QListWidgetItem
项目,将视频文件的时间信息(提取自d.datetime
)设置为列表项的显示文本。
- 创建一个新的
item->setData(Qt::UserRole, d.filepath);
:- 将视频文件的路径存储在列表项的用户角色数据中。
Qt::UserRole
是一个预定义的角色值,用于存储用户自定义的数据。
- 将视频文件的路径存储在列表项的用户角色数据中。
ui.time_list->addItem(item);
:- 将创建的列表项添加到
time_list
列表控件中。
- 将创建的列表项添加到
总结
槽函数 SelectDate(QDate date)
的主要功能是:
- 获取选定日期的视频文件数据列表。
- 清空当前时间列表控件。
- 遍历视频数据列表,为每个视频创建一个列表项,显示视频的时间信息。
- 将视频文件路径作为自定义数据存储在列表项中。
- 将列表项添加到时间列表控件中。
PlayVideo槽函数解析:
void XViewer::PlayVideo(QModelIndex index) //选择时间播放视频
{
qDebug() << "PlayVideo" << index.row();
auto item = ui.time_list->currentItem();
if (!item)return;
QString path = item->data(Qt::UserRole).toString();
qDebug() << path;
//建一个新的QT类 XPlayVideo,一个新的UI视图用于渲染录像播放录像
static XPlayVideo play;
play.Open(path.toLocal8Bit());
play.show();
}
其中XPlayVideo类的Open函数在 XplayVideo.cpp文件中:
XplayVideo.cpp
#include "xplayvideo.h"
void XPlayVideo::timerEvent(QTimerEvent* ev)
{
if (!view_)return;
auto f = decode_.GetFrame();
if (!f)return;
view_->DrawFrame(f);
XFreeFrame(&f);
}
void XPlayVideo::Close()
{
//关闭上次数据
demux_.Stop();
decode_.Stop();
if (view_)
{
view_->Close();
delete view_;
view_ = nullptr;
}
}
void XPlayVideo::closeEvent(QCloseEvent* ev)
{
Close();
}
bool XPlayVideo::Open(const char* url)
{
if (!demux_.Open(url)) //解封装
{
return false;
}
auto vp = demux_.CopyVideoPara();
if (!vp)
return false;
if (!decode_.Open(vp->para))//解码
{
return false;
}
demux_.set_next(&decode_);
if (!view_)
view_ = XVideoView::Create();
view_->set_win_id((void*)winId());
if (!view_->Init(vp->para)) //SDL渲染
return false;
//demux_.set_syn_type(XSYN_VIDEO);
demux_.Start();
decode_.Start();
return true;
}
XPlayVideo::XPlayVideo(QWidget* parent)
: QWidget(parent)
{
ui.setupUi(this);
startTimer(10);
}
XPlayVideo::~XPlayVideo()
{
Close();
}
XPlayVideo::Open函数解析
if (!demux_.Open(url))
:
- 尝试打开并解封装(demux)视频流。如果失败,返回
false
终止操作。
auto vp = demux_.CopyVideoPara();
:
- 复制视频参数。如果获取视频参数失败,返回
false
终止操作。 vp
是一个包含视频流参数的对象。
if (!decode_.Open(vp->para))
:
- 尝试使用视频参数打开解码器。如果失败,返回
false
终止操作。
demux_.set_next(&decode_);
:
- 设置解封装器的下一个处理模块为解码器。可能意味着解封装器在解封装出数据后会将其传递给解码器进行解码。
if (!view_) view_ = XVideoView::Create();
:
- 如果渲染视图对象
view_
尚未创建,则创建一个新的渲染视图对象。
view_->set_win_id((void*)winId());
:
- 设置渲染视图对象的窗口 ID,以便渲染图像。
winId()
可能是一个返回窗口 ID 的方法。
if (!view_->Init(vp->para))
:
- 初始化渲染视图对象。如果初始化失败,返回
false
终止操作。 vp->para
传递视频参数用于初始化。
-
demux_.Start();
和decode_.Start();
:- 启动解封装器和解码器。
demux_.Start()
开始解封装过程。decode_.Start()
开始解码过程。
-
return true;
:- 如果所有步骤成功,返回
true
表示打开视频流成功。
- 如果所有步骤成功,返回
执行上面代码的Open函数中的demux_.start启动主线程后有一个控制播放速度的功能模块,如果不进行控制播放速度,播放录像的视频速度非常快,以下是解封装的主线程函数代码:
demux_task.cpp
void XDemuxTask::Main()
{
AVPacket pkt;
while (!is_exit_)
{
if (!demux_.Read(&pkt))
{
//读取失败
cout << "-" << flush;
if (!demux_.is_connected())//如果断开连接,需要重新连接
{
Open(url_, timeout_ms_);
}
this_thread::sleep_for(1ms);
continue;
}
cout << "." << flush;
//播放速度控制132 132 132 132 132 132
if (syn_type_ == XSYN_VIDEO &&
pkt.stream_index == demux_.video_index())
{
auto dur = demux_.RescaleToMs(pkt.duration, pkt.stream_index);
if (dur <= 0)
dur = 40;
//pkt.duration
MSleep(dur);
}
Next(&pkt);
av_packet_unref(&pkt);
this_thread::sleep_for(1ms);
}
}
其中调用了RescaleToMs:把duration转成毫秒的函数位于xformat.cpp中
xformat.cpp
//把pts dts duration 值转为毫秒 132 132
long long XFormat::RescaleToMs(long long pts, int index)
{
unique_lock<mutex> lock(mux_);
if (!c_ || index <0 || index > c_->nb_streams)return 0;
auto in_timebase = c_->streams[index]->time_base;
AVRational out_timebase = { 1,1000 };//输出timebase 毫秒
return av_rescale_q(pts, in_timebase, out_timebase);
}
该函数将转换时间基数调整播放速度
运行结果:
没设置XplayVideo窗口的可拖动功能所以这个窗口固定的