Qt+ffmpeg实现视频流播放

向右看齐 2023-01-15 02:20 395阅读 0赞

1.版本

Qt版本:5.11 64位

ffmpeg版本:>3.0 64位

2.实现思路

ffmpeg拉流、解码,因为Qt不支持显示yuv数据,需要ffmpeg转换为rgb数据,再用QImage显示,这种方式性能会不好,暂时先实现这种简单的。一般情况下会用opengl渲染。

3.示例

拉流cctv1:http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8,此例子只解码视频数据,不处理音频。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d6ejk1MzIwMDQ2Mw_size_16_color_FFFFFF_t_70

代码:解码线程

  1. #pragma once
  2. #include <QThread>
  3. #include <QImage>
  4. extern "C"
  5. {
  6. #include "libavformat/avformat.h"
  7. #include "libavutil/dict.h"
  8. #include "libswscale/swscale.h"
  9. #include "libavutil/imgutils.h"
  10. };
  11. class DecodeThread : public QThread
  12. {
  13. Q_OBJECT
  14. public:
  15. DecodeThread(QObject *parent);
  16. ~DecodeThread();
  17. Q_SIGNALS:
  18. void sigData(uint8_t* rgbBuffer);
  19. public:
  20. void setUrl(QString url);
  21. void setStoped(bool stop);
  22. protected:
  23. void run();
  24. protected:
  25. double r2d(AVRational r);
  26. void decodeStream();
  27. private:
  28. QString m_url;
  29. bool m_isStop = false;
  30. private:
  31. AVFormatContext* m_pFormatCtx = NULL;
  32. AVCodecContext* m_pCodecCtx = NULL;
  33. AVPacket* m_avpacket = NULL;
  34. AVFrame *m_frame = NULL;
  35. //视频index
  36. int m_videoIndex = -1;
  37. //视频总时间,单位ms
  38. int64_t m_totalTime = 0;
  39. //视频宽度;
  40. int m_width = 0;
  41. //视频高度;
  42. int m_height = 0;
  43. //视频帧率;
  44. int m_fps = 0;
  45. };
  46. #include "DecodeThread.h"
  47. DecodeThread::DecodeThread(QObject *parent)
  48. : QThread(parent)
  49. {
  50. }
  51. DecodeThread::~DecodeThread()
  52. {
  53. if (m_pFormatCtx != NULL)
  54. {
  55. avformat_close_input(&m_pFormatCtx);
  56. avformat_free_context(m_pFormatCtx);
  57. m_pFormatCtx = NULL;
  58. }
  59. if (m_pCodecCtx != NULL)
  60. {
  61. avcodec_close(m_pCodecCtx);
  62. avcodec_free_context(&m_pCodecCtx);
  63. m_pCodecCtx = NULL;
  64. }
  65. if (m_avpacket != NULL)
  66. {
  67. av_packet_unref(m_avpacket);
  68. delete m_avpacket;
  69. m_avpacket = NULL;
  70. }
  71. if (m_frame != NULL)
  72. {
  73. av_frame_free(&m_frame);
  74. m_frame = NULL;
  75. }
  76. }
  77. void DecodeThread::setUrl(QString url)
  78. {
  79. m_url = url;
  80. }
  81. void DecodeThread::setStoped(bool stop)
  82. {
  83. m_isStop = stop;
  84. }
  85. void DecodeThread::run()
  86. {
  87. //注册所有组件 新版本已弃用
  88. av_register_all();
  89. //打开输入视频文件
  90. if (avformat_open_input(&m_pFormatCtx, m_url.toStdString().c_str(), NULL, NULL) != 0)
  91. {
  92. printf("Couldn't open input stream.\n");
  93. }
  94. if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0)
  95. {
  96. printf("Couldn't find stream information.\n");
  97. }
  98. for (int i = 0; i < m_pFormatCtx->nb_streams/*视音频流的个数*/; i++)
  99. {
  100. //查找视频
  101. if (m_pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
  102. {
  103. m_videoIndex = i;
  104. break;
  105. }
  106. }
  107. if (m_videoIndex == -1)
  108. {
  109. printf("Couldn't find a video stream.\n");
  110. }
  111. /**
  112. * 不赞成这样使用
  113. * pCodecCtx = pFormatCtx->streams[videoIndex]->codec; //指向AVCodecContext的指针
  114. */
  115. m_pCodecCtx = avcodec_alloc_context3(NULL);
  116. if (m_pCodecCtx == NULL)
  117. {
  118. printf("Could not allocate AVCodecContext\n");
  119. }
  120. avcodec_parameters_to_context(m_pCodecCtx, m_pFormatCtx->streams[m_videoIndex]->codecpar);
  121. //指向AVCodec的指针.查找解码器
  122. AVCodec *pCodec = avcodec_find_decoder(m_pCodecCtx->codec_id);
  123. if (pCodec == NULL)
  124. {
  125. printf("Codec not found pCodec\n");
  126. }
  127. //打开解码器
  128. if (avcodec_open2(m_pCodecCtx, pCodec, NULL) < 0)
  129. {
  130. printf("Could not open codec.\n");
  131. }
  132. //视频宽
  133. m_width = m_pFormatCtx->streams[m_videoIndex]->codecpar->width;
  134. //视频高
  135. m_height = m_pFormatCtx->streams[m_videoIndex]->codecpar->height;
  136. //获取帧率;
  137. m_fps = r2d(m_pFormatCtx->streams[m_videoIndex]->avg_frame_rate);
  138. if (m_fps == 0)
  139. {
  140. m_fps = 25;
  141. }
  142. //初始化AVPacket
  143. m_avpacket = new AVPacket;
  144. av_init_packet(m_avpacket);
  145. m_avpacket->data = NULL;
  146. //初始化frame,
  147. m_frame = av_frame_alloc();
  148. if (!m_frame)
  149. {
  150. printf("av_frame_alloc fail\n");
  151. }
  152. //开始解码
  153. decodeStream();
  154. }
  155. double DecodeThread::r2d(AVRational r)
  156. {
  157. return r.den == 0 ? 0 : (double)r.num / (double)r.den;
  158. }
  159. void DecodeThread::decodeStream()
  160. {
  161. while (av_read_frame(m_pFormatCtx, m_avpacket) >= 0)
  162. {
  163. if (m_avpacket->stream_index == m_videoIndex)
  164. {
  165. int ret = avcodec_send_packet(m_pCodecCtx, m_avpacket);
  166. if (ret >= 0)
  167. {
  168. ret = avcodec_receive_frame(m_pCodecCtx, m_frame);
  169. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  170. {
  171. return;
  172. }
  173. else if (ret < 0)
  174. {
  175. return;
  176. }
  177. switch (m_pCodecCtx->pix_fmt)
  178. {
  179. case AV_PIX_FMT_YUV420P:
  180. {
  181. int width = m_frame->width;
  182. int height = m_frame->height;
  183. AVFrame *pFrameRGB = av_frame_alloc();
  184. int numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, width, height);
  185. int nBGRFrameSize = av_image_get_buffer_size(AV_PIX_FMT_RGB32, width, height, 1);
  186. uint8_t* rgbBuffer = (uint8_t*)av_malloc(nBGRFrameSize);
  187. av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, rgbBuffer, AV_PIX_FMT_RGB32, width, height, 1);
  188. //改变像素格式
  189. SwsContext *img_convert_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV420P, width, height, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
  190. //颜色空间转换 yuv420p --> rgb32
  191. sws_scale(img_convert_ctx,
  192. (uint8_t const *const *)m_frame->data,
  193. m_frame->linesize, 0, height, pFrameRGB->data,
  194. pFrameRGB->linesize);
  195. //发送信号,转换后的rgb buffer
  196. emit sigData(rgbBuffer);
  197. //释放内存
  198. av_frame_free(&pFrameRGB);
  199. }break;
  200. default:
  201. {
  202. printf("default format:%d\n", m_pCodecCtx->pix_fmt);
  203. return;
  204. }
  205. }
  206. }
  207. }
  208. }
  209. }

代码:ui界面

  1. #pragma once
  2. #include <QtWidgets/QWidget>
  3. #include "ui_QtGuiApplication1.h"
  4. #include "DecodeThread.h"
  5. class QtGuiApplication1 : public QWidget
  6. {
  7. Q_OBJECT
  8. public:
  9. QtGuiApplication1(QWidget *parent = Q_NULLPTR);
  10. ~QtGuiApplication1();
  11. private:
  12. Ui::QtGuiApplication1Class ui;
  13. private Q_SLOTS:
  14. void slotBtnClicked();
  15. void slotData(uint8_t* data);
  16. private:
  17. DecodeThread *m_thead = nullptr;
  18. };
  19. #include "QtGuiApplication1.h"
  20. QtGuiApplication1::QtGuiApplication1(QWidget *parent)
  21. : QWidget(parent)
  22. {
  23. ui.setupUi(this);
  24. m_thead = new DecodeThread(this);
  25. connect(ui.pushButton, &QPushButton::clicked, this, &QtGuiApplication1::slotBtnClicked);
  26. connect(m_thead, &DecodeThread::sigData, this, &QtGuiApplication1::slotData);
  27. }
  28. QtGuiApplication1::~QtGuiApplication1()
  29. {
  30. }
  31. void QtGuiApplication1::slotBtnClicked()
  32. {
  33. QString url = ui.lineEdit->text();
  34. m_thead->setUrl(url);
  35. m_thead->start();
  36. }
  37. void QtGuiApplication1::slotData(uint8_t* data)
  38. {
  39. QImage image(data, 1920, 1080, QImage::Format_ARGB32);
  40. ui.lbImage->setPixmap(QPixmap::fromImage(image));
  41. av_free(data);
  42. }

发表评论

表情:
评论列表 (有 0 条评论,395人围观)

还没有评论,来说两句吧...

相关阅读

    相关 FFMPEG 播放 RTSP视频

    功能简介: 使用QT+FFMPEG实现了RTSP视频流播放的基础操作,点击按钮后,将拉取指定地址的RTSP流,并在QT界面中通过Label显示 开发环境: 系统环境:U