当前位置:   article > 正文

(四)qt中使用ffmpeg播放视频,可暂停恢复

qt中使用ffmpeg播放视频

一、在qt中添加ffmpeg库及头文件

  1. INCLUDEPATH += /usr/local/ffmpeg/include
  2. LIBS += -L/usr/local/lib -lavutil -lavcodec -lavformat -lswscale

二、详细代码

FFempegVideoDecode 视频解码类(放入线程中)

ffmpegvideodecode.h

  1. #ifndef FFMPEGVIDEODECODE_H
  2. #define FFMPEGVIDEODECODE_H
  3. #include <QObject>
  4. #include <QThread>
  5. #include <QDebug>
  6. #include <QTime>
  7. struct AVCodec;
  8. struct AVCodecContext;
  9. struct AVFrame;
  10. struct AVFormatContext;
  11. struct SwsContext;
  12. struct AVPacket;
  13. class FFmpegVideoDecode : public QObject
  14. {
  15. Q_OBJECT
  16. public:
  17. explicit FFmpegVideoDecode(QObject *parent = nullptr);
  18. ~FFmpegVideoDecode();
  19. bool initFFmpeg(QString fileName);
  20. void clear();
  21. private:
  22. void inputError(int ret, QString funName);
  23. signals:
  24. void sigToStart();
  25. void sigToUpdateImage(const QImage &image);
  26. public slots:
  27. void onStartPlay(QString name);
  28. void onStopPlay();
  29. void onFinish();
  30. void onUpdateRead();
  31. private:
  32. AVFormatContext* pFormatCtx = nullptr;
  33. AVCodecContext* pCodecCtx = nullptr;
  34. AVFrame* pAvFrame = nullptr;
  35. AVFrame* pFrameRGB32 = nullptr;
  36. AVPacket* packet = nullptr;
  37. uint8_t *out_buffer = nullptr;
  38. SwsContext *img_convert_ctx = nullptr;
  39. int videoIndex;
  40. bool is_stop = false;
  41. bool is_finish = true;
  42. };
  43. #endif // FFMPEGVIDEODECODE_H

ffmpegvideodecode.cpp

  1. #include "ffmpegvideodecode.h"
  2. extern "C"
  3. {
  4. #include "libavutil/avutil.h"
  5. #include "libavcodec/avcodec.h"
  6. #include "libavformat/avformat.h"
  7. #include "libswscale/swscale.h"
  8. #include "libavutil/imgutils.h"
  9. }
  10. #include <QEventLoop>
  11. #include <QPixmap>
  12. #include <QTimer>
  13. /**
  14. * @brief 非阻塞延时
  15. * @param msec 延时毫秒
  16. */
  17. void sleepMsec(int msec)
  18. {
  19. if(msec <= 0) return;
  20. QEventLoop loop; //定义一个新的事件循环
  21. QTimer::singleShot(msec, &loop, SLOT(quit()));//创建单次定时器,槽函数为事件循环的退出函数
  22. loop.exec(); //事件循环开始执行,程序会卡在这里,直到定时时间到,本循环被退出
  23. }
  24. FFmpegVideoDecode::FFmpegVideoDecode(QObject *parent) : QObject(parent)
  25. {
  26. connect(this,&FFmpegVideoDecode::sigToStart,this,&FFmpegVideoDecode::onUpdateRead);
  27. }
  28. FFmpegVideoDecode::~FFmpegVideoDecode()
  29. {
  30. clear();
  31. }
  32. bool FFmpegVideoDecode::initFFmpeg(QString fileName)
  33. {
  34. clear();
  35. /*
  36. * avformat_network_init主要就是初始化win socket和openssl。但是由于我使用linux(ubuntu),未使用openssl支持,所以该函数对我平台没有任何意义。
  37. * 执行网络库的全局初始化。这是可选的,不再推荐。此函数仅用于解决旧GnuTLS或OpenSSL库的线程安全问题。
  38. 如果libavformat链接到这些库的较新版本,或者不使用它们,则不需要调用此函数。否则,您需要在启动使用该函数的任何其他线程之前调用该函数。
  39. 一旦删除对旧GnuTLS和OpenSSL库的支持,此函数将被弃用,并且此函数没有任何用途
  40. *.
  41. //avformat_network_init();
  42. /*
  43. * int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);
  44. * AVDictionary是FFmpeg中用于存储元数据的一种数据结构,它基本上是一个简单的哈希表,用于存储字符串类型的键值对。它可以被用来传递多种数据,例如视频解码参数、音频参数、字幕参数等等。
  45. * 其中,pm是要添加键值对的AVDictionary对象的指针指针;key是要添加的键名;value是要添加的键值;flags是一些标志位,可选项有:
  46. 0:默认,如果已经定义一个关键字,则将其删除并替换为新的键值对。
  47. AV_DICT_APPEND:如果关键字已经定义,则将其与新的值合并。
  48. AV_DICT_DONT_OVERWRITE:如果已经存在键名,则不会执行任何操作。
  49. */
  50. AVDictionary* dict = nullptr;
  51. //使用 TCP 方式
  52. av_dict_set(&dict, "rtsp_transport", "tcp", 0);
  53. //设置 接收包间隔最大延迟,微秒
  54. av_dict_set(&dict, "max_delay", "200", 0);
  55. //在进行网络操作时允许的最大等待时间。5秒
  56. av_dict_set(&dict, "timeout", "5000000", 0);
  57. //设置阻塞超时,否则可能在流断开时连接发生阻塞,微秒
  58. av_dict_set(&dict, "stimeout", "3000000", 0);
  59. //设置 find_stream_info 最大时长,微秒
  60. //av_dict_set(&dict, "analyzeduration", "1000000", 0);
  61. /*
  62. * avformat_open_input 打开输入媒体流
  63. * 参数ps包含一切媒体相关的上下文结构,有它就有了一切,本函数如果打开媒体成功,
  64. 会返回一个AVFormatContext的实例.
  65. 参数url媒体文件名或URL.
  66. 参数fmt是要打开的媒体格式的操作结构,因为是读,所以是inputFormat.此处可以
  67. 传入一个调用者定义的inputFormat,对应命令行中的 -f xxx段,如果指定了它,
  68. 在打开文件中就不会探测文件的实际格式了,以它为准了.
  69. 参数options是对某种格式的一些操作,是为了在命令行中可以对不同的格式传入
  70. 特殊的操作参数而建的, 为了了解流程,完全可以无视它.
  71. */
  72. /*
  73. * avformat_alloc_context用于分配一个AVFormatContext结构体并初始化它
  74. */
  75. pFormatCtx = avformat_alloc_context();
  76. int ret = avformat_open_input(&pFormatCtx,
  77. fileName.toStdString().c_str(),
  78. NULL,
  79. &dict);
  80. // 释放参数字典
  81. if(dict)
  82. av_dict_free(&dict);
  83. if (ret != 0)
  84. {
  85. inputError(ret,"avformat_open_input");
  86. clear();
  87. return false;
  88. }
  89. /*
  90. * 用于找到媒体文件中的流信息,也就是获取视频和音频的相关数据,如编码格式、分辨率、采样率等
  91. * 获取多媒体流的信息(视频文件信息),一个视频文件中可能会同时包括视频文件、音频文件、字幕文件等多个媒体流。
  92. */
  93. ret = avformat_find_stream_info(pFormatCtx, NULL);
  94. if (ret < 0)
  95. {
  96. inputError(ret,"avformat_find_stream_info");
  97. clear();
  98. return false;
  99. }
  100. qint64 totalTime = pFormatCtx->duration / (AV_TIME_BASE / 1000); // 计算视频总时长(毫秒)
  101. qDebug() << QString("视频总时长:%1 ms,[%2]").arg(totalTime).arg(QTime::fromMSecsSinceStartOfDay(int(totalTime)).toString("HH:mm:ss zzz"));
  102. /*
  103. * 通过AVMediaType枚举查询视频流ID(也可以通过遍历查找)
  104. */
  105. /*
  106. * int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags);
  107. * 查找最佳匹配的媒体流的函数
  108. * ic:AVFormatContext指针,表示输入的媒体文件上下文。
  109. type:要查找的媒体流类型,可以是音频流、视频流或字幕流等。
  110. wanted_stream_nb:期望的媒体流索引号,可以是特定的索引号,也可以是AV_NOPTS_VALUE(-1)表示任意流。
  111. related_stream:前一个相关流的索引号,如果没有前一个相关流,则传入-1。
  112. decoder_ret:返回解码器指针。
  113. flags:查找最佳流的标志位,默认为0。
  114. 返回值:
  115. 找到的最佳匹配媒体流的索引号,如果找不到则返回AVERROR_STREAM_NOT_FOUND。
  116. */
  117. videoIndex = -1;
  118. videoIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
  119. /*
  120. for (int i = 0; i < pFormatCtx->nb_streams; ++i)
  121. {
  122. if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
  123. videoIndex = i;
  124. break;
  125. }
  126. }
  127. */
  128. if (videoIndex < 0)
  129. {
  130. inputError(videoIndex,"av_find_best_stream");
  131. clear();
  132. return false;
  133. }
  134. /*
  135. * AVCodecParameters 用于保存音视频流的基本参数信息。
  136. * 主要成员说明
  137. * codec_id:编解码器的ID,取值为枚举类型AVCodecID中的一种。
  138. * width:视频帧宽度。
  139. * height:视频帧高度。
  140. */
  141. AVCodecParameters* parmeter = pFormatCtx->streams[videoIndex]->codecpar;
  142. /*
  143. * avcodec_find_decoder 通过解码器ID获取视频解码器(新版本返回值必须使用const)
  144. */
  145. const AVCodec* _pCodec = avcodec_find_decoder(parmeter->codec_id);
  146. if (_pCodec == NULL)
  147. {
  148. inputError(AVERROR_DECODER_NOT_FOUND,"avcodec_find_decoder");
  149. clear();
  150. return false;
  151. }
  152. /*
  153. * avcodec_alloc_context3 用于分配一个编解码器上下文(AVCodecContext)结构体
  154. * AVCodecContext结构的主要作用是设置编码过程的参数
  155. */
  156. pCodecCtx = avcodec_alloc_context3(_pCodec);//初始化一个编解码上下文
  157. /*
  158. * avcodec_parameters_to_context 该函数用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中
  159. */
  160. ret = avcodec_parameters_to_context(pCodecCtx, parmeter);
  161. if (ret < 0)
  162. {
  163. inputError(ret,"avcodec_parameters_to_context");
  164. clear();
  165. return false;
  166. }
  167. /*
  168. * avcodec_open2 用于打开编解码器并初始化其上下文
  169. */
  170. ret = avcodec_open2(pCodecCtx, _pCodec, NULL);
  171. if (ret < 0)
  172. {
  173. inputError(ret,"avcodec_open2");
  174. clear();
  175. return false;
  176. }
  177. //分配AVFrame并将其字段设置为默认值。
  178. pAvFrame = av_frame_alloc();
  179. pFrameRGB32 = av_frame_alloc(); //存储解码后转换的RGB数据
  180. //保存RGB32
  181. /*
  182. * av_image_get_buffer_size 用于计算图像缓冲区的大小
  183. */
  184. /*
  185. * av_image_fill_arrays 用于将图像数据填充到AVFrame结构体中,瓜分malloc分配到的内存
  186. * 参数const uint8_t *src之后的逻辑中就没再用了->对分配内存瓜分
  187. */
  188. int size = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height,4);
  189. out_buffer = (uint8_t *)av_malloc(size);
  190. ret = av_image_fill_arrays(pFrameRGB32->data,pFrameRGB32->linesize, out_buffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height,1);
  191. if (ret < 0)
  192. {
  193. inputError(ret,"av_image_fill_arrays");
  194. clear();
  195. return false;
  196. }
  197. //分配AVPacket并将其字段设置为默认值。
  198. packet = av_packet_alloc();
  199. /*
  200. * av_new_packet 用于分配一个新的AVPacket结构体
  201. */
  202. av_new_packet(packet, pCodecCtx->width * pCodecCtx->height);//分配packet的有效载荷并初始化其字段
  203. //qDebug() << "***********视频信息**********";
  204. //av_dump_format(pFormatCtx, 0, fileName.toStdString().c_str(), 0);
  205. //qDebug() << "*****************************";
  206. return true;
  207. }
  208. void FFmpegVideoDecode::onStartPlay(QString name)
  209. {
  210. bool flag = initFFmpeg(name);
  211. is_stop = false;
  212. if(flag)
  213. {
  214. is_finish = false;
  215. Q_EMIT sigToStart();
  216. }
  217. else
  218. {
  219. qDebug()<<"初始化ffmpeg失败!";
  220. }
  221. }
  222. void FFmpegVideoDecode::onStopPlay()
  223. {
  224. is_stop = !is_stop;
  225. }
  226. void FFmpegVideoDecode::onFinish()
  227. {
  228. qDebug()<<"关闭视频";
  229. is_finish = true;
  230. }
  231. void FFmpegVideoDecode::clear()
  232. {
  233. if(pFrameRGB32)
  234. {
  235. av_frame_free(&pFrameRGB32);
  236. pFrameRGB32 = nullptr;
  237. }
  238. if(pAvFrame)
  239. {
  240. av_frame_free(&pAvFrame);
  241. pAvFrame = nullptr;
  242. }
  243. if(pCodecCtx)
  244. {
  245. avcodec_close(pCodecCtx);
  246. pCodecCtx = nullptr;
  247. }
  248. // 因为avformat_flush不会刷新AVIOContext (s->pb)。如果有必要,在调用此函数之前调用avio_flush(s->pb)。
  249. if(pFormatCtx && pFormatCtx->pb)
  250. {
  251. avio_flush(pFormatCtx->pb);
  252. }
  253. if(pFormatCtx)
  254. {
  255. avformat_flush(pFormatCtx); // 清理读取缓冲
  256. avformat_close_input(&pFormatCtx);
  257. pFormatCtx = nullptr;
  258. }
  259. if (img_convert_ctx)
  260. {
  261. sws_freeContext(img_convert_ctx);
  262. img_convert_ctx = nullptr;
  263. }
  264. if(out_buffer)
  265. {
  266. //delete [] out_buffer;
  267. av_free(out_buffer);
  268. out_buffer = nullptr;
  269. }
  270. }
  271. void FFmpegVideoDecode::inputError(int ret, QString funName)
  272. {
  273. char *error = new char[1024];
  274. memset(error, 0, 1024);// 将数组置零
  275. av_strerror(ret, error, 1024);
  276. qDebug()<<"函数名为:"<<funName<<" 的函数发生错误,错误原因:"<<QString::fromStdString(error)<<" 返回值:"<<ret;
  277. delete[] error;
  278. error = NULL;
  279. }
  280. void FFmpegVideoDecode::onUpdateRead()
  281. {
  282. //设置sws_scale转换格式为AV_PIX_FMT_RGB32
  283. /*
  284. * sws_getContext 用于创建一个SWSContext结构体
  285. * struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
  286. int dstW, int dstH, enum AVPixelFormat dstFormat,
  287. int flags, SwsFilter *srcFilter,
  288. SwsFilter *dstFilter, const double *param);
  289. * 参数srcW和srcH分别表示源图像的宽度和高度
  290. * 参数srcFormat表示源图像的像素格式
  291. * 参数dstFormat表示目标图像的像素格式
  292. * 参数flags表示转换的标志;
  293. * 参数srcFilter和dstFilter分别表示源图像和目标图像的滤波器;
  294. * 参数param表示转换的参数。
  295. */
  296. img_convert_ctx = sws_getContext(pCodecCtx->width,
  297. pCodecCtx->height,
  298. pCodecCtx->pix_fmt,
  299. pCodecCtx->width,
  300. pCodecCtx->height,
  301. AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
  302. while (!is_finish)
  303. {
  304. //方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态
  305. if (is_stop)
  306. {
  307. continue;
  308. }
  309. //方法2:将子线程阻塞式延时修改未非阻塞式,可直接修改视频的暂停状态
  310. // while (is_stop && !is_finish)
  311. // {
  312. // sleepMsec(200);
  313. // }
  314. /*
  315. * int av_read_frame(AVFormatContext *s, AVPacket *pkt);用于从输入流中读取一帧数据。
  316. * 参数s是一个指向AVFormatContext指针的指针,表示要读取数据的输入流;
  317. * 参数pkt是一个指向AVPacket指针的指针,表示要存储读取到的数据的AVPacket结构体。
  318. */
  319. if (av_read_frame(pFormatCtx, packet) >= 0)
  320. {
  321. if (videoIndex == packet->stream_index) //此流是视频流
  322. {
  323. /*
  324. * int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);用于将AVPacket结构体中的数据发送到解码器。
  325. * 参数avctx是一个指向AVCodecContext指针的指针,表示要发送数据的解码器;
  326. * 参数avpkt是一个指向AVPacket指针的指针,表示要发送的数据。
  327. */
  328. /*
  329. * int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);用于从解码器中接收解码后的帧数据。
  330. * 参数avctx是一个指向AVCodecContext指针的指针,表示要接收数据的解码器;
  331. * 参数frame是一个指向AVFrame指针的指针,表示要存储接收到的数据的AVFrame结构体。
  332. */
  333. int ret = avcodec_send_packet(pCodecCtx, packet);
  334. if (ret < 0)
  335. {
  336. inputError(ret,"avcodec_send_packet");
  337. continue;
  338. }
  339. ret = avcodec_receive_frame(pCodecCtx, pAvFrame);
  340. if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  341. {
  342. //拉流显示视频时一开始报错: 返回值:-11,资源暂时不可用
  343. //代表解码器暂时没有数据可读,需要输入更多的AVPacket。
  344. //在代码中设定碰到这个错误时直接continue让avcodec_send_packet()对解码器输入更多数据
  345. continue;
  346. }
  347. else if(ret < 0)
  348. {
  349. inputError(ret,"avcodec_receive_frame");
  350. continue;
  351. }
  352. /*
  353. * sws_scale 用于将图像从一个像素格式转换为另一个像素格式。
  354. * 可以在同一个函数里实现:1.图像色彩空间转换, 2:分辨率缩放,3:前后图像滤波处理
  355. */
  356. sws_scale(img_convert_ctx,
  357. (const uint8_t* const*)pAvFrame->data,
  358. pAvFrame->linesize,
  359. 0,
  360. pCodecCtx->height,
  361. pFrameRGB32->data,
  362. pFrameRGB32->linesize);
  363. QImage image((uchar*)pFrameRGB32->data[0], pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB32);
  364. Q_EMIT sigToUpdateImage(image);
  365. //sleepMsec->非阻塞(点击播放可直接重头播放) QThread::msleep(40)->阻塞
  366. //方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态
  367. QThread::msleep(40);
  368. //方法2:将子线程阻塞式延时修改未非阻塞式,可直接修改视频的暂停状态
  369. // sleepMsec(40);
  370. }
  371. /*
  372. * av_packet_free和av_packet_unref都是FFmpeg库中的函数,用于释放AVPacket结构体中的资源。
  373. * av_packet_free函数会释放AVPacket结构体中的所有资源,包括存储数据的缓冲区等。
  374. * av_packet_unref函数只会释放AVPacket结构体中的部分资源,例如释放存储数据的缓冲区等,但不会释放AVPacket结构体本身。
  375. */
  376. av_packet_unref(packet);
  377. }
  378. else
  379. {
  380. qDebug()<<"视频播放到结尾,播放结束";
  381. is_finish = true;
  382. //break;
  383. }
  384. }
  385. }

VideoPlayer类(视频显示)

videoplayer.h

  1. #ifndef VIDEOPLAYER_H
  2. #define VIDEOPLAYER_H
  3. #include <QWidget>
  4. #include <QThread>
  5. #include <QMutex>
  6. #include "ffmpegvideodecode.h"
  7. namespace Ui {
  8. class VideoPlayer;
  9. }
  10. class VideoPlayer : public QWidget
  11. {
  12. Q_OBJECT
  13. public:
  14. explicit VideoPlayer(QWidget *parent = 0);
  15. ~VideoPlayer();
  16. signals:
  17. void sigToStart(QString name);
  18. void sigToStop();
  19. void sigToFinish();
  20. public slots:
  21. void onUpdateImage(const QImage &image);
  22. protected:
  23. void paintEvent(QPaintEvent *event) override;
  24. private:
  25. Ui::VideoPlayer *ui;
  26. FFmpegVideoDecode *ffmpeg_decode;
  27. QThread *thread_decode;
  28. QPixmap pixmap_video;
  29. QMutex m_mutex;
  30. };
  31. #endif // VIDEOPLAYER_H

videoplayer.cpp

  1. #include "videoplayer.h"
  2. #include "ui_videoplayer.h"
  3. #include <QPainter>
  4. VideoPlayer::VideoPlayer(QWidget *parent) :
  5. QWidget(parent),
  6. ui(new Ui::VideoPlayer)
  7. {
  8. ui->setupUi(this);
  9. ffmpeg_decode = new FFmpegVideoDecode;
  10. thread_decode = new QThread;
  11. ffmpeg_decode->moveToThread(thread_decode);
  12. connect(thread_decode,&QThread::finished,ffmpeg_decode,&FFmpegVideoDecode::deleteLater);
  13. connect(thread_decode,&QThread::finished,thread_decode,&QThread::deleteLater);
  14. connect(ffmpeg_decode,&FFmpegVideoDecode::sigToUpdateImage,this,&VideoPlayer::onUpdateImage);
  15. connect(this,&VideoPlayer::sigToStart,ffmpeg_decode,&FFmpegVideoDecode::onStartPlay);
  16. connect(this,&VideoPlayer::sigToFinish,ffmpeg_decode,&FFmpegVideoDecode::onFinish,Qt::DirectConnection);
  17. //方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态
  18. connect(this,&VideoPlayer::sigToStop,ffmpeg_decode,&FFmpegVideoDecode::onStopPlay,Qt::DirectConnection);
  19. //方法2:将子线程阻塞式延时修改未非阻塞式,可直接修改视频的暂停状态
  20. // connect(this,&VideoPlayer::sigToStop,ffmpeg_decode,&FFmpegVideoDecode::onStopPlay);
  21. thread_decode->start();
  22. }
  23. VideoPlayer::~VideoPlayer()
  24. {
  25. delete ui;
  26. Q_EMIT sigToFinish();
  27. thread_decode->quit();
  28. thread_decode->wait();
  29. }
  30. void VideoPlayer::onUpdateImage(const QImage &image)
  31. {
  32. m_mutex.lock();
  33. pixmap_video = QPixmap::fromImage(image);
  34. m_mutex.unlock();
  35. update();
  36. }
  37. void VideoPlayer::paintEvent(QPaintEvent *event)
  38. {
  39. if(!pixmap_video.isNull())
  40. {
  41. QPainter painter(this);
  42. m_mutex.lock();
  43. QPixmap pixmap = pixmap_video.scaled(this->size(), Qt::KeepAspectRatio);
  44. m_mutex.unlock();
  45. int x = (this->width() - pixmap.width()) / 2;
  46. int y = (this->height() - pixmap.height()) / 2;
  47. painter.drawPixmap(x, y, pixmap);
  48. }
  49. QWidget::paintEvent(event);
  50. }

Widget类(主界面)

widget.h

  1. #include "videoplayer.h"
  2. #include "ui_videoplayer.h"
  3. #include <QPainter>
  4. VideoPlayer::VideoPlayer(QWidget *parent) :
  5. QWidget(parent),
  6. ui(new Ui::VideoPlayer)
  7. {
  8. ui->setupUi(this);
  9. ffmpeg_decode = new FFmpegVideoDecode;
  10. thread_decode = new QThread;
  11. ffmpeg_decode->moveToThread(thread_decode);
  12. connect(thread_decode,&QThread::finished,ffmpeg_decode,&FFmpegVideoDecode::deleteLater);
  13. connect(thread_decode,&QThread::finished,thread_decode,&QThread::deleteLater);
  14. connect(ffmpeg_decode,&FFmpegVideoDecode::sigToUpdateImage,this,&VideoPlayer::onUpdateImage);
  15. connect(this,&VideoPlayer::sigToStart,ffmpeg_decode,&FFmpegVideoDecode::onStartPlay);
  16. connect(this,&VideoPlayer::sigToFinish,ffmpeg_decode,&FFmpegVideoDecode::onFinish,Qt::DirectConnection);
  17. //方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态
  18. connect(this,&VideoPlayer::sigToStop,ffmpeg_decode,&FFmpegVideoDecode::onStopPlay,Qt::DirectConnection);
  19. //方法2:将子线程阻塞式延时修改未非阻塞式,可直接修改视频的暂停状态
  20. // connect(this,&VideoPlayer::sigToStop,ffmpeg_decode,&FFmpegVideoDecode::onStopPlay);
  21. thread_decode->start();
  22. }
  23. VideoPlayer::~VideoPlayer()
  24. {
  25. delete ui;
  26. Q_EMIT sigToFinish();
  27. thread_decode->quit();
  28. thread_decode->wait();
  29. }
  30. void VideoPlayer::onUpdateImage(const QImage &image)
  31. {
  32. m_mutex.lock();
  33. pixmap_video = QPixmap::fromImage(image);
  34. m_mutex.unlock();
  35. update();
  36. }
  37. void VideoPlayer::paintEvent(QPaintEvent *event)
  38. {
  39. if(!pixmap_video.isNull())
  40. {
  41. QPainter painter(this);
  42. m_mutex.lock();
  43. QPixmap pixmap = pixmap_video.scaled(this->size(), Qt::KeepAspectRatio);
  44. m_mutex.unlock();
  45. int x = (this->width() - pixmap.width()) / 2;
  46. int y = (this->height() - pixmap.height()) / 2;
  47. painter.drawPixmap(x, y, pixmap);
  48. }
  49. QWidget::paintEvent(event);
  50. }

widget.cpp

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include <QFileDialog>
  4. Widget::Widget(QWidget *parent) :
  5. QWidget(parent),
  6. ui(new Ui::Widget)
  7. {
  8. ui->setupUi(this);
  9. player = new VideoPlayer(ui->widget_play);
  10. ui->horizontalLayout->addWidget(player);
  11. }
  12. Widget::~Widget()
  13. {
  14. delete ui;
  15. }
  16. void Widget::on_pushButton_start_clicked()
  17. {
  18. Q_EMIT player->sigToFinish();
  19. Q_EMIT player->sigToStart(ui->lineEdit_path->text());
  20. ui->pushButton_stop->setText("暂停");
  21. }
  22. void Widget::on_pushButton_stop_clicked()
  23. {
  24. Q_EMIT player->sigToStop();
  25. if(ui->pushButton_stop->text() == "暂停")
  26. ui->pushButton_stop->setText("恢复");
  27. else if(ui->pushButton_stop->text() == "恢复")
  28. ui->pushButton_stop->setText("暂停");
  29. }
  30. void Widget::on_pushButton_select_clicked()
  31. {
  32. QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
  33. "/home",
  34. tr("Video (*.mp4 *.flv)"));
  35. if(fileName.isEmpty()) return;
  36. ui->lineEdit_path->setText(fileName);
  37. qDebug()<<"选择文件:"<<fileName;
  38. }

widget.ui

三、运行

1.显示本地视频

直接选择本地视频文件即可,显示界面如下:

2.显示拉流视频

启动mediamtx流媒体服务器(相关内容看上一篇笔记)

./mediamtx

循环推送本地视频到mediamtx服务器

ffmpeg -re -stream_loop -1 -i /home/li/1.mp4 -c copy -f rtsp rtsp://127.0.0.1:8554/stream

即可通过流地址显示视频,界面显示如下:

 代码下载地址:

https://download.csdn.net/download/m0_67254672/89118955

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号