基于 FFMPEG + SDL2 的视频播放器

缺乏、安全感 2021-09-25 12:18 667阅读 0赞

基于 FFMPEG + SDL2 的视频播放器

本文是在雷神的 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0) 的基础上修改的,对雷神代码中 ffmpeg 弃用的函数做了修改。与雷神博客一样同样实现了普通版本和 su 版本。

SU 版特征如下:

  • SDL弹出的窗口可以移动了
  • 画面显示是严格的40ms一帧

所有代码均在 VS2017 C++17,SDL-2.0.14 版本上测试通过。

FFMPEG 解码流程

在这里插入图片描述

VideoPlayer 类声明

  1. #pragma once
  2. #ifdef __cplusplus
  3. extern "C" {
  4. #endif
  5. #include "libavcodec/avcodec.h"
  6. #include "libavformat/avformat.h"
  7. #include "libswscale/swscale.h"
  8. #include "libavutil/imgutils.h"
  9. #include "SDL.h"
  10. #ifdef __cplusplus
  11. }
  12. #endif
  13. #include <iostream>
  14. class VideoPlayer {
  15. public:
  16. VideoPlayer(const std::string& file_path);
  17. ~VideoPlayer();
  18. // call this function before use VideoPlayer.
  19. bool InitVideoPlayer();
  20. void Show();
  21. private:
  22. AVFormatContext* fmt_ctx_{ nullptr };
  23. AVCodecContext* codec_ctx_{ nullptr };
  24. AVCodec* codec_{ nullptr };
  25. AVCodecParameters *parameters_{ nullptr };
  26. struct SwsContext *sws_ctx_{ nullptr };
  27. AVFrame* frame_{ nullptr };
  28. AVFrame* yuv_frame_{ nullptr };
  29. AVPacket* packet_{ nullptr };
  30. bool is_init_{ false };
  31. int video_index_{ -1 };
  32. std::string file_path_;
  33. unsigned char* out_buffer_{ nullptr };
  34. SDL_Window* window_{ nullptr };
  35. };

测试主程序

  1. #include "VideoPlayer.h"
  2. int main(int argc, char** argv) {
  3. std::string file_path("output.mp4");
  4. VideoPlayer player(file_path);
  5. if (!player.InitVideoPlayer()) {
  6. return -1;
  7. }
  8. // 如果使用 Su 版本调用 player.SuShow()
  9. // player.SuShow()
  10. player.Show();
  11. return 0;
  12. }

VideoPlayer 实现

  1. #include "VideoPlayer.h"
  2. // 定义刷新事件
  3. #define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
  4. #define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
  5. static bool thread_exit = 0;
  6. static bool thread_pause = 0;
  7. static int SfpRefreshThread(void *opaque) {
  8. thread_exit = false;
  9. thread_pause = false;
  10. while (!thread_exit) {
  11. if (!thread_pause) {
  12. SDL_Event event;
  13. event.type = SFM_REFRESH_EVENT;
  14. SDL_PushEvent(&event);
  15. }
  16. SDL_Delay(40);
  17. }
  18. thread_exit = false;
  19. thread_pause = false;
  20. SDL_Event event;
  21. event.type = SFM_BREAK_EVENT;
  22. SDL_PushEvent(&event);
  23. return 0;
  24. }
  25. VideoPlayer::VideoPlayer(const std::string& file_path) : file_path_(file_path) {
  26. // 新版的 ffmpeg 已经不需要执行 av_register_all() 了
  27. avformat_network_init();
  28. }
  29. VideoPlayer::~VideoPlayer() {
  30. // 释放所有资源
  31. av_frame_free(&yuv_frame_);
  32. av_frame_free(&frame_);
  33. avcodec_close(codec_ctx_);
  34. avformat_close_input(&fmt_ctx_);
  35. }
  36. bool VideoPlayer::InitVideoPlayer() {
  37. fmt_ctx_ = avformat_alloc_context();
  38. if (avformat_open_input(&fmt_ctx_, file_path_.c_str(), nullptr, nullptr) != 0)
  39. {
  40. av_log(nullptr, AV_LOG_ERROR, "Couldn't open input stream");
  41. return false;
  42. }
  43. if (avformat_find_stream_info(fmt_ctx_, nullptr) < 0)
  44. {
  45. av_log(nullptr, AV_LOG_ERROR, "Couldn't find stream information");
  46. return false;
  47. }
  48. // 老版中根据 AVFormatContext->stream[video_index]->codec 的方式已被弃用
  49. // 新版本中使用 av_find_best_stream(), 在第二个参数中指定要找到的流是视频还是音频即可
  50. video_index_ = av_find_best_stream(fmt_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
  51. if (video_index_ == -1) {
  52. av_log(nullptr, AV_LOG_ERROR, "Didn't find a video stream");
  53. return false;
  54. }
  55. parameters_ = fmt_ctx_->streams[video_index_]->codecpar;
  56. codec_ = avcodec_find_decoder(parameters_->codec_id);
  57. if (!codec_) {
  58. av_log(nullptr, AV_LOG_ERROR, "Couldn't open codec");
  59. return false;
  60. }
  61. codec_ctx_ = avcodec_alloc_context3(codec_);
  62. if (avcodec_parameters_to_context(codec_ctx_, parameters_) < 0) {
  63. av_log(nullptr, AV_LOG_ERROR, "Couldn't copy params to context");
  64. return false;
  65. }
  66. // 使用 avcodec_open2 替换原先的 avcodec_open 来打开解码器
  67. if (avcodec_open2(codec_ctx_, codec_, nullptr) != 0) {
  68. av_log(nullptr, AV_LOG_ERROR, "Couldn't open codec.");
  69. return false;
  70. }
  71. frame_ = av_frame_alloc();
  72. yuv_frame_ = av_frame_alloc();
  73. // 填充 YUV 数据内容,防止因为 yuv_frame 中没有为 data 分配内存空间而报错
  74. out_buffer_ = static_cast<unsigned char*>(av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codec_ctx_->width, codec_ctx_->height, 1)));
  75. av_image_fill_arrays(yuv_frame_->data, yuv_frame_->linesize,
  76. const_cast<const uint8_t*>(out_buffer_),
  77. AV_PIX_FMT_YUV420P,
  78. codec_ctx_->width, codec_ctx_->height, 1);
  79. packet_ = av_packet_alloc();
  80. // 打印一些关于 output format 的信息,例如比特率,延时之类
  81. // 此处第二个参数指定要打印的流的索引
  82. av_dump_format(fmt_ctx_, 0, file_path_.c_str(), 0);
  83. // 创建编码转换器,通过使用 sws_scale 函数将我们读入的视频帧转换为我们需要的格式
  84. // 此处的 AV_PIX_FMT_YUV420P 指定要转换成的格式
  85. sws_ctx_ = sws_getContext(codec_ctx_->width, codec_ctx_->height, codec_ctx_->pix_fmt,
  86. codec_ctx_->width, codec_ctx_->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr);
  87. is_init_ = true;
  88. }
  89. void VideoPlayer::Show() {
  90. if (!is_init_) {
  91. av_log(nullptr, AV_LOG_ERROR, "You should init VideoPlayer first.");
  92. return;
  93. }
  94. if (SDL_Init(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))) {
  95. av_log(nullptr, AV_LOG_ERROR, "Could not initialize SDL - %s", SDL_GetError());
  96. return;
  97. }
  98. int h = codec_ctx_->height;
  99. int w = codec_ctx_->width;
  100. window_ = SDL_CreateWindow("__name__", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
  101. w, h, SDL_WINDOW_SHOWN);
  102. if (!window_) {
  103. av_log(nullptr, AV_LOG_ERROR, "SDL: could not create window - exiting:%s", SDL_GetError());
  104. return;
  105. }
  106. SDL_Renderer* renderer = SDL_CreateRenderer(window_, -1, 0);
  107. SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, w, h);
  108. SDL_Rect rect;
  109. rect.x = 0;
  110. rect.y = 0;
  111. rect.w = w;
  112. rect.h = h;
  113. while (av_read_frame(fmt_ctx_, packet_) >= 0) {
  114. // 我们在此处仅处理视频帧,不处理音频帧
  115. if (packet_->stream_index == video_index_) {
  116. avcodec_send_packet(codec_ctx_, packet_);
  117. while (avcodec_receive_frame(codec_ctx_, frame_) == 0) {
  118. sws_scale(sws_ctx_, (const unsigned char* const *)frame_->data, frame_->linesize, 0, h, yuv_frame_->data, yuv_frame_->linesize);
  119. SDL_UpdateYUVTexture(texture, &rect, yuv_frame_->data[0], yuv_frame_->linesize[0],
  120. yuv_frame_->data[1], yuv_frame_->linesize[1],
  121. yuv_frame_->data[2], yuv_frame_->linesize[2]);
  122. SDL_RenderClear(renderer);
  123. SDL_RenderCopy(renderer, texture, nullptr, &rect);
  124. SDL_RenderPresent(renderer);
  125. SDL_Delay(40);
  126. }
  127. }
  128. av_packet_unref(packet_);
  129. }
  130. // 在解码器上下文中可能还剩有以下没处理的视频,将这些视频处理完后显示
  131. while (true) {
  132. int ret = avcodec_send_packet(codec_ctx_, packet_);
  133. if (ret != 0) {
  134. break;
  135. }
  136. while (avcodec_receive_frame(codec_ctx_, frame_) == 0) {
  137. sws_scale(sws_ctx_, (const unsigned char* const *)frame_->data, frame_->linesize, 0, h, yuv_frame_->data, yuv_frame_->linesize);
  138. SDL_UpdateYUVTexture(texture, &rect, yuv_frame_->data[0], yuv_frame_->linesize[0],
  139. yuv_frame_->data[1], yuv_frame_->linesize[1],
  140. yuv_frame_->data[2], yuv_frame_->linesize[2]);
  141. SDL_RenderClear(renderer);
  142. SDL_RenderCopy(renderer, texture, nullptr, &rect);
  143. SDL_RenderPresent(renderer);
  144. SDL_Delay(40);
  145. }
  146. }
  147. sws_freeContext(sws_ctx_);
  148. SDL_Quit();
  149. }
  150. void VideoPlayer::SuShow()
  151. {
  152. if (!is_init_) {
  153. av_log(nullptr, AV_LOG_ERROR, "You should init VideoPlayer first.");
  154. return;
  155. }
  156. if (SDL_Init(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))) {
  157. av_log(nullptr, AV_LOG_ERROR, "Could not initialize SDL - %s", SDL_GetError());
  158. return;
  159. }
  160. int h = codec_ctx_->height;
  161. int w = codec_ctx_->width;
  162. window_ = SDL_CreateWindow("__name__", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
  163. w, h, SDL_WINDOW_SHOWN);
  164. if (!window_) {
  165. av_log(nullptr, AV_LOG_ERROR, "SDL: could not create window - exiting:%s", SDL_GetError());
  166. return;
  167. }
  168. SDL_Renderer* renderer = SDL_CreateRenderer(window_, -1, 0);
  169. SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, w, h);
  170. SDL_Rect rect;
  171. rect.x = 0;
  172. rect.y = 0;
  173. rect.w = w;
  174. rect.h = h;
  175. SDL_Thread *sdl_thread = SDL_CreateThread(SfpRefreshThread, nullptr, nullptr);
  176. SDL_Event event;
  177. while (true) {
  178. SDL_WaitEvent(&event);
  179. if (event.type == SFM_REFRESH_EVENT) {
  180. // 当当前的事件是需要刷新显示时,一直循环直到当前的包为视频包时结束
  181. while (true) {
  182. if (av_read_frame(fmt_ctx_, packet_) < 0) thread_exit = true;
  183. if (packet_->stream_index == video_index_) break;
  184. }
  185. avcodec_send_packet(codec_ctx_, packet_);
  186. while (avcodec_receive_frame(codec_ctx_, frame_) == 0) {
  187. sws_scale(sws_ctx_, (const unsigned char* const *)frame_->data, frame_->linesize, 0, h, yuv_frame_->data, yuv_frame_->linesize);
  188. SDL_UpdateYUVTexture(texture, &rect, yuv_frame_->data[0], yuv_frame_->linesize[0],
  189. yuv_frame_->data[1], yuv_frame_->linesize[1],
  190. yuv_frame_->data[2], yuv_frame_->linesize[2]);
  191. SDL_RenderClear(renderer);
  192. SDL_RenderCopy(renderer, texture, nullptr, nullptr);
  193. SDL_RenderPresent(renderer);
  194. }
  195. av_packet_unref(packet_);
  196. } else if (event.type == SDL_KEYDOWN) {
  197. if (event.key.keysym.sym == SDLK_SPACE) thread_pause = !thread_pause;
  198. } else if (event.type == SDL_QUIT) {
  199. thread_exit = true;
  200. } else if (event.type == SFM_BREAK_EVENT) {
  201. break;
  202. }
  203. }
  204. sws_freeContext(sws_ctx_);
  205. SDL_Quit();
  206. }

发表评论

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

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

相关阅读