使用FFmpeg+GDI解码和播放视频

矫情吗;* 2022-07-15 03:25 426阅读 0赞

完整源码

FFmpeg是跨平台的流媒体处理解决方案,包括了libavformat和libavcodec等库用来解码流媒体

本篇文章使用FFmpeg 3.1.5解码视频,现有的教程比较老了,avcodec_decode_video2等API已经不用了,用avcodec_send_packet和avcodec_receive_frame代替

API参考文档: FFmpeg Documentation

使用FFmpeg前去官网下载dev版和shared版,在项目中导入dev版的头文件和lib库,然后把shared版中的dll放到你的程序目录下

使用FFmpeg解码视频步骤:

  1. 程序开始时用av_register_all注册解码器
  2. 用avformat_open_input打开视频文件,用avformat_find_stream_info获取流信息
  3. 找到要解码的流的索引(这里用了第一个视频流)
  4. 用avcodec_find_decoder找到解码器,用avcodec_open2初始化
  5. 为了得到RGB数据用sws_getContext注册颜色转换器
  6. 用av_read_frame从流中读取packet
  7. 用avcodec_send_packet和avcodec_receive_frame解码packet,再用sws_scale转换颜色格式到RGB

Decoder.h:

  1. #pragma once
  2. extern "C"
  3. {
  4. #include <libavformat/avformat.h>
  5. #include <libavcodec/avcodec.h>
  6. #include <libswscale/swscale.h>
  7. };
  8. #include <thread>
  9. #include <memory>
  10. #include <functional>
  11. class CDecoder
  12. {
  13. public:
  14. CDecoder(LPCSTR fileName);
  15. virtual ~CDecoder();
  16. virtual void Run();
  17. virtual void Pause();
  18. virtual void Stop();
  19. virtual void GetVideoSize(SIZE& size);
  20. // 设置需要呈现时的回调函数
  21. virtual void SetOnPresent(std::function<void(BYTE*)>&& onPresent);
  22. protected:
  23. AVFormatContext* m_formatCtx = NULL;
  24. int m_videoStreamIndex = -1;
  25. AVCodecContext* m_codecCtx = NULL;
  26. AVCodec* m_codec = NULL;
  27. SwsContext* m_swsCtx = NULL;
  28. enum DecodeState{ DECODE_RUN, DECODE_PAUSE, DECODE_STOP };
  29. DecodeState m_decodeState = DECODE_STOP;
  30. std::unique_ptr<std::thread> m_decodeThread;
  31. std::unique_ptr<BYTE[]> m_imgData;
  32. SIZE m_videoSize;
  33. // 需要呈现时被调用
  34. std::function<void(BYTE*)> m_onPresent;
  35. void DecodeThread();
  36. };

Decoder.cpp:

  1. #include "stdafx.h"
  2. #include "Decoder.h"
  3. CDecoder::CDecoder(LPCSTR fileName)
  4. {
  5. int ret;
  6. // 容器
  7. ret = avformat_open_input(&m_formatCtx, fileName, NULL, NULL);
  8. ret = avformat_find_stream_info(m_formatCtx, NULL);
  9. for (UINT i = 0; i < m_formatCtx->nb_streams; i++)
  10. {
  11. if (m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
  12. {
  13. m_videoStreamIndex = i;
  14. break;
  15. }
  16. }
  17. // 解码器
  18. AVCodecParameters* codecpar = m_formatCtx->streams[m_videoStreamIndex]->codecpar;
  19. m_codec = avcodec_find_decoder(codecpar->codec_id);
  20. m_codecCtx = avcodec_alloc_context3(m_codec);
  21. ret = avcodec_open2(m_codecCtx, m_codec, NULL);
  22. m_videoSize.cx = codecpar->width;
  23. m_videoSize.cy = codecpar->height;
  24. // 颜色转换器
  25. m_swsCtx = sws_getContext(m_videoSize.cx, m_videoSize.cy, (AVPixelFormat)codecpar->format, m_videoSize.cx,
  26. m_videoSize.cy, AV_PIX_FMT_BGRA, SWS_BICUBIC, NULL, NULL, NULL);
  27. m_imgData.reset(new BYTE[m_videoSize.cx * m_videoSize.cy * 4]);
  28. }
  29. CDecoder::~CDecoder()
  30. {
  31. // 停止解码线程
  32. m_decodeState = DECODE_STOP;
  33. if (m_decodeThread != nullptr)
  34. m_decodeThread->join();
  35. sws_freeContext(m_swsCtx);
  36. avcodec_free_context(&m_codecCtx);
  37. avformat_close_input(&m_formatCtx);
  38. }
  39. void CDecoder::Run()
  40. {
  41. TRACE(_T("Run\n"));
  42. if (m_decodeState == DECODE_RUN)
  43. return;
  44. // 启动解码线程
  45. if (m_decodeThread != nullptr)
  46. m_decodeThread->join();
  47. m_decodeState = DECODE_RUN;
  48. m_decodeThread.reset(new std::thread(&CDecoder::DecodeThread, this));
  49. }
  50. void CDecoder::Pause()
  51. {
  52. TRACE(_T("Pause\n"));
  53. if (m_decodeState != DECODE_RUN)
  54. return;
  55. // 停止解码线程
  56. m_decodeState = DECODE_PAUSE;
  57. if (m_decodeThread != nullptr)
  58. {
  59. m_decodeThread->join();
  60. m_decodeThread = nullptr;
  61. }
  62. }
  63. void CDecoder::Stop()
  64. {
  65. TRACE(_T("Stop\n"));
  66. if (m_decodeState == DECODE_STOP)
  67. return;
  68. // 停止解码线程
  69. m_decodeState = DECODE_STOP;
  70. if (m_decodeThread != nullptr)
  71. {
  72. m_decodeThread->join();
  73. m_decodeThread = nullptr;
  74. }
  75. av_seek_frame(m_formatCtx, m_videoStreamIndex, 0, 0);
  76. }
  77. void CDecoder::GetVideoSize(SIZE& size)
  78. {
  79. size = m_videoSize;
  80. }
  81. void CDecoder::SetOnPresent(std::function<void(BYTE*)>&& onPresent)
  82. {
  83. m_onPresent = std::move(onPresent);
  84. }
  85. void CDecoder::DecodeThread()
  86. {
  87. AVFrame *frame = av_frame_alloc();
  88. AVPacket packet;
  89. while (m_decodeState == DECODE_RUN && av_read_frame(m_formatCtx, &packet) >= 0)
  90. {
  91. if (packet.stream_index != m_videoStreamIndex)
  92. continue;
  93. // 解码
  94. avcodec_send_packet(m_codecCtx, &packet);
  95. if (avcodec_receive_frame(m_codecCtx, frame) != 0)
  96. continue;
  97. // 转换颜色格式
  98. BYTE* pImgData = m_imgData.get();
  99. int stride = m_codecCtx->width * 4;
  100. sws_scale(m_swsCtx, frame->data, frame->linesize, 0, m_codecCtx->height, &pImgData, &stride);
  101. // 呈现
  102. if (!m_onPresent._Empty())
  103. m_onPresent(m_imgData.get());
  104. // 粗略的视频同步
  105. Sleep(DWORD(((float)m_codecCtx->time_base.num / (float)m_codecCtx->time_base.den) * 1000));
  106. }
  107. if (m_decodeState == DECODE_RUN)
  108. m_decodeState = DECODE_STOP;
  109. av_packet_unref(&packet);
  110. av_frame_free(&frame);
  111. TRACE(_T("DecodeThread结束\n"));
  112. }

使用方法:

  1. m_decoder = new CDecoder("E:\\pump it.avi");
  2. m_decoder->SetOnPresent(std::bind(&CFFmpegGDIDlg::OnPresent, this, std::placeholders::_1));
  3. m_decoder->GetVideoSize(m_videoSize);
  4. m_dc.Create(m_videoSize.cx, m_videoSize.cy, 32);
  5. m_decoder->Run();
  6. // ......
  7. void CFFmpegGDIDlg::OnPresent(BYTE* data)
  8. {
  9. m_dcLock.Lock();
  10. // RGB位图都是从下到上储存的,data不是
  11. for (int y = 0; y < m_videoSize.cy; y++)
  12. {
  13. memcpy(m_dc.GetPixelAddress(0, y), data, m_videoSize.cx * 4);
  14. data += m_videoSize.cx * 4;
  15. }
  16. m_dcLock.Unlock();
  17. Invalidate(FALSE);
  18. }

这里我只是粗略的同步视频,视频播放速度可能比实际慢,比较精确的方法参考这篇文章: Tutorial 05: Synching Video

发表评论

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

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

相关阅读