模拟视频播放
作者:史正
邮箱:shizheng163@126.com
如有错误还请及时指正
如果有错误的描述给您带来不便还请见谅
如需交流请发送邮件,欢迎联系
- 我的csdn : https://blog.csdn.net/shizheng163
- 我的github : https://github.com/shizheng163
简述
我们知道,视频是由连续的图片组成的(见上一节:音视频基本概念), 所以这里我们先用Qt创建一个简易的视频播放界面, 读取一个文件夹,顺序播放其中的图片达到模拟视频播放的效果。
没有QT开发应用程序基础的同学建议先学习下QT应用程序开发
。
这里推荐一个介绍QT开发比较全的博客:
这一节的完整代码:
注意:
前期所有代码都是在Windows下开发的
直接渲染Jpeg等图片格式, 时间开销特别大, 单张图片超过40ms,导致播放缓慢, 正确做法应该使用opengl渲染yuv或者rgb格式的图片, 这一点以后章节有示例。
另外推荐几个界面图标素材网站
界面搭建
这一部分我们省略掉, 通过QTUI
设计界面就可以搭建简易的播放界面。
界面主要由以下两部分组成:
- 打开文件夹按钮
- 显示图片的Label
使用ffmpeg获取连续的图片
ffmpeg是一个用C语言编写的处理音视频的开源库, 我们平时所见的播放器几乎都是通过ffmpeg去编写的。
这里给出一条命令, 可以通过ffmpeg将一段视频拆分成连续的图片:
ffmpeg -i Suger.mp4 -b 3000k -ss 00:00:10 -t 10 SugerFrames/Frame_%04d.jpeg
这里-i 是输出 -b 是码率, 码率过低的话会导致画面清晰度降低, -ss 是开始时间, -t 是持续时间, 最后一个是保存位置,%04d标明编号为4位, 从1开始, 不足4位前补0。
ffmpeg可程序程序下载可通过官方网站:
选择文件夹,获取图片文件名列表
核心代码如下,如果目录中有中文名, 使用std::cout
或者printf
打印会有中文乱码, 从网上找了好多方法都没找到非qDebug()的打印可以避免这种情况, 如果你有好的解决方法请及时告知, 多谢!
string srcPath = dir;
dir.append("/*.*");
dir = QString::fromStdString(dir).replace("/", "\\\\").toStdString();
std::vector<std::string> pictures;
struct _finddata_t fileinfo;
intptr_t fileIdx = _findfirst(dir.c_str(), &fileinfo);
qDebug() << "search dir:" << dir.c_str();
if(fileIdx != -1)
{
do
{
if(!(fileinfo.attrib & _A_SUBDIR))
{
string filename(fileinfo.name);
size_t pos = filename.find_last_of('.');
if(pos != std::string::npos)
{
string extension = filename.substr(pos+1);
std::transform(extension.begin(), extension.end(), extension.begin(), std::ptr_fun<int, int>(tolower));
if(extension == "img" || extension == "png" || extension == "jpeg" || extension == "jpg")
{
qDebug() << "get picture:" << fileinfo.name;
pictures.push_back(srcPath + "/" + filename);
}
}
}
}while(_findnext(fileIdx, &fileinfo) == 0);
}
else
qDebug() << "could not find file from" << dir.c_str();
_findclose(fileIdx);
return pictures;
显示图片
这里是一个线程读取图片另一个线程在QLabel
上渲染图片
注意:
不能在非UI线程改变去修改界面,但是这里使用的是QLabel的`setPixmap`。
这里没去查询原因,但估计应该是setPixmap是修改了显示位置中内存的值,并没有更新UI。
如有错误,请及时指出,谢谢!
std::mutex mutexForMemoryPictures;
std::queue<PicturePtr> memoryPictures;
std::condition_variable conditionVar;
unsigned totolPictureNum = pictureVector.size();
unsigned curPictureNum = 0;
//禁用打开图标,用信号槽去更新UI
emit SignalBtnEnable(false);
std::thread([&](std::vector<std::string> pictures){
for(string pictureName : pictures)
{
FILE * fPict = fopen(pictureName.c_str(), "rb");
fseek(fPict, 0, SEEK_END);
PicturePtr pPicture(new Picture);
pPicture->m_len = ftell(fPict);
pPicture->m_pData = new uint8_t[pPicture->m_len];
pPicture->m_strPictureName = pictureName;
fseek(fPict, 0, SEEK_SET);
fread(pPicture->m_pData, sizeof(uint8_t), pPicture->m_len, fPict);
fclose(fPict);
unique_lock<mutex> locker(mutexForMemoryPictures);
memoryPictures.push(pPicture);
conditionVar.notify_one();
}
}, pictureVector).detach();
QImage *pimg = new QImage;
while(curPictureNum < totolPictureNum)
{
unique_lock<mutex> locker(mutexForMemoryPictures);
if(memoryPictures.empty())
conditionVar.wait(locker);
PicturePtr ptr = memoryPictures.front();
memoryPictures.pop();
locker.unlock();
curPictureNum++;
if(!pimg->loadFromData(ptr->m_pData, ptr->m_len, "jpg"))
{
qDebug() << "error: load memory picture" << ptr->m_strPictureName.c_str();
}
//渲染图片
ui->m_pLabVideoImage->setPixmap(QPixmap::fromImage(pimg->scaled(ui->m_pLabVideoImage->size())));
string textName(ptr->m_strPictureName);
textName.append("\t\t");
textName.append(to_string(curPictureNum));
textName.append("/");
textName.append(to_string(totolPictureNum));
//进度条显示文本:当前显示到哪张图片
ui->m_pLabProcessBar->setText(textName.c_str());
}
delete pimg;
//重置界面显示
this->ResetControls();
}