最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器

=====================================================

最简单的基于FFmpeg的视频播放器系列文章列表:

100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)

最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

最简单的基于FFmpeg的解码器-纯净版(不包含libavformat)

最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器

最简单的基于FFMPEG的Helloworld程序

=====================================================

本文补充记录《最简单的基于FFMPEG+SDL的视频播放器》中的两个例子:FFmpeg视频解码器和SDL像素数据播放器。这两个部分是从视频播放器中拆分出来的两个例子。FFmpeg视频解码器实现了视频数据到YUV数据的解码,而SDL像素数据播放器实现了YUV数据的显示。简而言之,原先的FFmpeg+SDL视频播放器实现了:

视频数据->YUV->显示器

FFmpeg视频解码器实现了:

视频数据->YUV

SDL像素数据播放器实现了:

YUV->显示器

FFmpeg视频解码器

源代码

[cpp] view
plain
 copy
  1. /**
  2. * 最简单的基于FFmpeg的视频解码器
  3. * Simplest FFmpeg Decoder
  4. *
  5. * 雷霄骅 Lei Xiaohua
  6. * leixiaohua1020@126.com
  7. * 中国传媒大学/数字电视技术
  8. * Communication University of China / Digital TV Technology
  9. * http://blog.csdn.net/leixiaohua1020
  10. *
  11. *
  12. * 本程序实现了视频文件解码为YUV数据。它使用了libavcodec和
  13. * libavformat。是最简单的FFmpeg视频解码方面的教程。
  14. * 通过学习本例子可以了解FFmpeg的解码流程。
  15. * This software is a simplest decoder based on FFmpeg.
  16. * It decodes video to YUV pixel data.
  17. * It uses libavcodec and libavformat.
  18. * Suitable for beginner of FFmpeg.
  19. *
  20. */
  21. #include <stdio.h>
  22. #define __STDC_CONSTANT_MACROS
  23. #ifdef _WIN32
  24. //Windows
  25. extern "C"
  26. {
  27. #include "libavcodec/avcodec.h"
  28. #include "libavformat/avformat.h"
  29. #include "libswscale/swscale.h"
  30. #include "libavutil/imgutils.h"
  31. };
  32. #else
  33. //Linux...
  34. #ifdef __cplusplus
  35. extern "C"
  36. {
  37. #endif
  38. #include <libavcodec/avcodec.h>
  39. #include <libavformat/avformat.h>
  40. #include <libswscale/swscale.h>
  41. #include <libavutil/imgutils.h>
  42. #ifdef __cplusplus
  43. };
  44. #endif
  45. #endif
  46. int main(int argc, char* argv[])
  47. {
  48. AVFormatContext *pFormatCtx;
  49. int             i, videoindex;
  50. AVCodecContext  *pCodecCtx;
  51. AVCodec         *pCodec;
  52. AVFrame *pFrame,*pFrameYUV;
  53. unsigned char *out_buffer;
  54. AVPacket *packet;
  55. int y_size;
  56. int ret, got_picture;
  57. struct SwsContext *img_convert_ctx;
  58. char filepath[]="Titanic.mkv";
  59. FILE *fp_yuv=fopen("output.yuv","wb+");
  60. av_register_all();
  61. avformat_network_init();
  62. pFormatCtx = avformat_alloc_context();
  63. if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
  64. printf("Couldn't open input stream.\n");
  65. return -1;
  66. }
  67. if(avformat_find_stream_info(pFormatCtx,NULL)<0){
  68. printf("Couldn't find stream information.\n");
  69. return -1;
  70. }
  71. videoindex=-1;
  72. for(i=0; i<pFormatCtx->nb_streams; i++)
  73. if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
  74. videoindex=i;
  75. break;
  76. }
  77. if(videoindex==-1){
  78. printf("Didn't find a video stream.\n");
  79. return -1;
  80. }
  81. pCodecCtx=pFormatCtx->streams[videoindex]->codec;
  82. pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
  83. if(pCodec==NULL){
  84. printf("Codec not found.\n");
  85. return -1;
  86. }
  87. if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
  88. printf("Could not open codec.\n");
  89. return -1;
  90. }
  91. pFrame=av_frame_alloc();
  92. pFrameYUV=av_frame_alloc();
  93. out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,  pCodecCtx->width, pCodecCtx->height,1));
  94. av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,
  95. AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height,1);
  96. packet=(AVPacket *)av_malloc(sizeof(AVPacket));
  97. //Output Info-----------------------------
  98. printf("--------------- File Information ----------------\n");
  99. av_dump_format(pFormatCtx,0,filepath,0);
  100. printf("-------------------------------------------------\n");
  101. img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
  102. pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
  103. while(av_read_frame(pFormatCtx, packet)>=0){
  104. if(packet->stream_index==videoindex){
  105. ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
  106. if(ret < 0){
  107. printf("Decode Error.\n");
  108. return -1;
  109. }
  110. if(got_picture){
  111. sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
  112. pFrameYUV->data, pFrameYUV->linesize);
  113. y_size=pCodecCtx->width*pCodecCtx->height;
  114. fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y
  115. fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U
  116. fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V
  117. printf("Succeed to decode 1 frame!\n");
  118. }
  119. }
  120. av_free_packet(packet);
  121. }
  122. //flush decoder
  123. //FIX: Flush Frames remained in Codec
  124. while (1) {
  125. ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
  126. if (ret < 0)
  127. break;
  128. if (!got_picture)
  129. break;
  130. sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
  131. pFrameYUV->data, pFrameYUV->linesize);
  132. int y_size=pCodecCtx->width*pCodecCtx->height;
  133. fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);    //Y
  134. fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);  //U
  135. fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);  //V
  136. printf("Flush Decoder: Succeed to decode 1 frame!\n");
  137. }
  138. sws_freeContext(img_convert_ctx);
  139. fclose(fp_yuv);
  140. av_frame_free(&pFrameYUV);
  141. av_frame_free(&pFrame);
  142. avcodec_close(pCodecCtx);
  143. avformat_close_input(&pFormatCtx);
  144. return 0;
  145. }

运行结果

程序运行后,会解码下面的视频文件。

 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器

解码后的YUV420P数据被保存成了一个文件。使用YUV播放器设置宽高之后可以查看YUV内容。

 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器

SDL像素数据播放器

源代码

[cpp] view
plain
 copy
  1. /**
  2. * 最简单的SDL2播放视频的例子(SDL2播放RGB/YUV)
  3. * Simplest Video Play SDL2 (SDL2 play RGB/YUV)
  4. *
  5. * 雷霄骅 Lei Xiaohua
  6. * leixiaohua1020@126.com
  7. * 中国传媒大学/数字电视技术
  8. * Communication University of China / Digital TV Technology
  9. * http://blog.csdn.net/leixiaohua1020
  10. *
  11. * 本程序使用SDL2播放RGB/YUV视频像素数据。SDL实际上是对底层绘图
  12. * API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层
  13. * API。
  14. *
  15. * 函数调用步骤如下:
  16. *
  17. * [初始化]
  18. * SDL_Init(): 初始化SDL。
  19. * SDL_CreateWindow(): 创建窗口(Window)。
  20. * SDL_CreateRenderer(): 基于窗口创建渲染器(Render)。
  21. * SDL_CreateTexture(): 创建纹理(Texture)。
  22. *
  23. * [循环渲染数据]
  24. * SDL_UpdateTexture(): 设置纹理的数据。
  25. * SDL_RenderCopy(): 纹理复制给渲染器。
  26. * SDL_RenderPresent(): 显示。
  27. *
  28. * This software plays RGB/YUV raw video data using SDL2.
  29. * SDL is a wrapper of low-level API (Direct3D, OpenGL).
  30. * Use SDL is much easier than directly call these low-level API.
  31. *
  32. * The process is shown as follows:
  33. *
  34. * [Init]
  35. * SDL_Init(): Init SDL.
  36. * SDL_CreateWindow(): Create a Window.
  37. * SDL_CreateRenderer(): Create a Render.
  38. * SDL_CreateTexture(): Create a Texture.
  39. *
  40. * [Loop to Render data]
  41. * SDL_UpdateTexture(): Set Texture's data.
  42. * SDL_RenderCopy(): Copy Texture to Render.
  43. * SDL_RenderPresent(): Show.
  44. */
  45. #include <stdio.h>
  46. extern "C"
  47. {
  48. #include "sdl/SDL.h"
  49. };
  50. const int bpp=12;
  51. int screen_w=500,screen_h=500;
  52. const int pixel_w=320,pixel_h=180;
  53. unsigned char buffer[pixel_w*pixel_h*bpp/8];
  54. //Refresh Event
  55. #define REFRESH_EVENT  (SDL_USEREVENT + 1)
  56. #define BREAK_EVENT  (SDL_USEREVENT + 2)
  57. int thread_exit=0;
  58. int refresh_video(void *opaque){
  59. thread_exit=0;
  60. while (!thread_exit) {
  61. SDL_Event event;
  62. event.type = REFRESH_EVENT;
  63. SDL_PushEvent(&event);
  64. SDL_Delay(40);
  65. }
  66. thread_exit=0;
  67. //Break
  68. SDL_Event event;
  69. event.type = BREAK_EVENT;
  70. SDL_PushEvent(&event);
  71. return 0;
  72. }
  73. int main(int argc, char* argv[])
  74. {
  75. if(SDL_Init(SDL_INIT_VIDEO)) {
  76. printf( "Could not initialize SDL - %s\n", SDL_GetError());
  77. return -1;
  78. }
  79. SDL_Window *screen;
  80. //SDL 2.0 Support for multiple windows
  81. screen = SDL_CreateWindow("Simplest Video Play SDL2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
  82. screen_w, screen_h,SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
  83. if(!screen) {
  84. printf("SDL: could not create window - exiting:%s\n",SDL_GetError());
  85. return -1;
  86. }
  87. SDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
  88. Uint32 pixformat=0;
  89. //IYUV: Y + U + V  (3 planes)
  90. //YV12: Y + V + U  (3 planes)
  91. pixformat= SDL_PIXELFORMAT_IYUV;
  92. SDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer,pixformat, SDL_TEXTUREACCESS_STREAMING,pixel_w,pixel_h);
  93. FILE *fp=NULL;
  94. fp=fopen("test_yuv420p_320x180.yuv","rb+");
  95. if(fp==NULL){
  96. printf("cannot open this file\n");
  97. return -1;
  98. }
  99. SDL_Rect sdlRect;
  100. SDL_Thread *refresh_thread = SDL_CreateThread(refresh_video,NULL,NULL);
  101. SDL_Event event;
  102. while(1){
  103. //Wait
  104. SDL_WaitEvent(&event);
  105. if(event.type==REFRESH_EVENT){
  106. if (fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp) != pixel_w*pixel_h*bpp/8){
  107. // Loop
  108. fseek(fp, 0, SEEK_SET);
  109. fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp);
  110. }
  111. SDL_UpdateTexture( sdlTexture, NULL, buffer, pixel_w);
  112. //FIX: If window is resize
  113. sdlRect.x = 0;
  114. sdlRect.y = 0;
  115. sdlRect.w = screen_w;
  116. sdlRect.h = screen_h;
  117. SDL_RenderClear( sdlRenderer );
  118. SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect);
  119. SDL_RenderPresent( sdlRenderer );
  120. }else if(event.type==SDL_WINDOWEVENT){
  121. //If Resize
  122. SDL_GetWindowSize(screen,&screen_w,&screen_h);
  123. }else if(event.type==SDL_QUIT){
  124. thread_exit=1;
  125. }else if(event.type==BREAK_EVENT){
  126. break;
  127. }
  128. }
  129. SDL_Quit();
  130. return 0;
  131. }

运行结果

程序运行后,会读取程序文件夹下的一个YUV420P文件,内容如下所示。

最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器 

接下来会将YUV内容绘制在弹出的窗口中。

最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器 

下载

Simplest FFmpeg Player

项目主页

SourceForge:https://sourceforge.net/projects/simplestffmpegplayer/

Github:https://github.com/leixiaohua1020/simplest_ffmpeg_player

开源中国:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_player

CSDN下载地址:http://download.csdn.net/detail/leixiaohua1020/8924321

本程序实现了视频文件的解码和显示(支持HEVC,H.264,MPEG2等)。

是最简单的FFmpeg视频解码方面的教程。

通过学习本例子可以了解FFmpeg的解码流程。

项目包含6个工程:

simplest_ffmpeg_player:标准版,FFmpeg学习的开始。

simplest_ffmpeg_player_su:SU(SDL Update)版,加入了简单的SDL的Event。

simplest_ffmpeg_decoder:一个包含了封装格式处理功能的解码器。使用了libavcodec和libavformat。

simplest_ffmpeg_decoder_pure:一个纯净的解码器。只使用libavcodec(没有使用libavformat)。

simplest_video_play_sdl2:使用SDL2播放YUV的例子。

simplest_ffmpeg_helloworld:输出FFmpeg类库的信息。

上一篇:最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)


下一篇:android去掉EditView的默认焦点问题