liudianwu |
2020-08-11 08:47 |
Qt音视频开发8-ffmpeg保存裸流
## 一、前言 最开始做的ffmpeg保存视频文件,就是直接保存的裸流数据,裸流数据一般是H264格式的数据,这种数据文件可以用部分播放器播放,由于不是标准的格式,很多播放器其实不支持的,需要安装对应的解码器才行。后面发现安装好K-Lite解码器后,连系统自带的播放器都可以正常播放H264视频流文件,而且如果同步保存了同名文件的aac音频文件放在同目录下的话,声音都能正常同步播放,可能这是播放器做的处理吧。
直接保存裸流基本上没有什么难度,大致流程就是先打开文件,然后在循环解码的地方直接将解码好的数据write到文件即可,如果采用的是定时存储的话,那就开个定时器,到了点就先关闭文件,然后重新打开新的名字的文件,这里要注意的是,rtmp视频流的话,需要添加pps sps等信息,所以在每帧写入文件前,要先用AVBitStreamFilter采用h264_mp4toannexb处理下才行。
## 二、功能特点 1. 多线程实时播放视频流+本地视频+USB摄像头等。 2. 支持windows+linux+mac,支持ffmpeg3和ffmpeg4,支持32位和64位。 3. 多线程显示图像,不卡主界面。 4. 自动重连网络摄像头。 5. 可设置边框大小即偏移量和边框颜色。 6. 可设置是否绘制OSD标签即标签文本或图片和标签位置。 7. 可设置两种OSD位置和风格。 8. 可设置是否保存到文件以及文件名。 9. 可直接拖曳文件到ffmpegwidget控件播放。 10. 支持h265视频流+rtmp等常见视频流。 11. 可暂停播放和继续播放。 12. 支持存储单个视频文件和定时存储视频文件。 13. 自定义顶部悬浮条,发送单击信号通知,可设置是否启用。 14. 可设置画面拉伸填充或者等比例填充。 15. 可设置解码是速度优先、质量优先、均衡处理。 16. 可对视频进行截图(原始图片)和截屏。 17. 录像文件存储支持裸流和MP4文件。 18. 支持qsv、dxva2、d3d11va等硬解码。 19. 支持opengl绘制视频数据,极低CPU占用。 20. 支持嵌入式linux,交叉编译即可。
## 三、效果图 [attachment=21807]
## 四、相关站点 1. 国内站点:[https://gitee.com/feiyangqingyun/QWidgetDemo](https://gitee.com/feiyangqingyun/QWidgetDemo) 2. 国际站点:[https://github.com/feiyangqingyun/QWidgetDemo](https://github.com/feiyangqingyun/QWidgetDemo) 3. 个人主页:[https://blog.csdn.net/feiyangqingyun](https://blog.csdn.net/feiyangqingyun) 4. 知乎主页:[https://www.zhihu.com/people/feiyangqingyun/](https://www.zhihu.com/people/feiyangqingyun/) 5. 体验地址:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
## 五、核心代码 ```c++ void FFmpegThread::initSave() { if (!saveFile) { return; }
//如果存储间隔大于0说明需要定时存储 if (saveInterval > 0) { QString dirName = QString("%1/%2").arg(savePath).arg(QDATE); newDir(dirName); fileName = QString("%1/%2_%3.mp4").arg(dirName).arg(fileFlag).arg(STRDATETIME); emit sig_startSave(); }
if (saveMp4) { saveVideoMp4(fileName); } else { saveVideoH264(fileName); } }
void FFmpegThread::startSave() { timerSave->start(saveInterval * 1000); }
void FFmpegThread::stopSave() { //停止存储文件以及存储定时器 closeVideo(); if (timerSave->isActive()) { timerSave->stop(); } }
void FFmpegThread::saveVideo() { if (!saveFile) { return; }
//重新设置文件名称 QString dirName = QString("%1/%2").arg(savePath).arg(QDATE); newDir(dirName); fileName = QString("%1/%2_%3.mp4").arg(dirName).arg(fileFlag).arg(STRDATETIME);
if (saveMp4) { saveVideoMp4(fileName); } else { saveVideoH264(fileName); } }
void FFmpegThread::closeVideo() { if (!saveFile) { return; }
if (saveMp4) { if (formatOut != NULL) { //写入结束标识 av_write_trailer(formatOut); avcodec_close(formatOut->streams[0]->codec); av_freep(&formatOut->streams[0]->codec); av_freep(&formatOut->streams[0]); avio_close(formatOut->pb); av_free(formatOut); initSaveOk = false; formatOut = NULL; } } else { if (fileVideo.isOpen()) { fileVideo.close(); }
if (fileAudio.isOpen()) { fileAudio.close(); } } }
void FFmpegThread::saveVideoH264(const QString &fileName) { QMutexLocker locker(&mutex); closeVideo(); if (videoStreamIndex >= 0) { fileVideo.setFileName(fileName); fileVideo.open(QFile::WriteOnly); }
//存在音频文件则同时保存音频文件 if (audioStreamIndex >= 0 && playAudio) { QString audioName = fileName; audioName = audioName.replace(QFileInfo(audioName).suffix(), "aac"); fileAudio.setFileName(audioName); fileAudio.open(QFile::WriteOnly); } } ```
|
|