写在前面

学习雷神的博客,向雷神致敬~

看了雷神的小学期视频课,在Github上下载了simplest_ffmpeg_player的代码,为代码加上了注释,作为留存。

2019.07.24

simple_ffmpeg_play_sdl2r.cpp注释
simple_ffmpeg_player_su.cpp注释


simple_ffmpeg_play_sdl2r.cpp是单纯的SDL播放器,simple_ffmpeg_player_su是将FFmpeg读取视频文件数据然后交给SDL播放器播放的完整代码。至此,雷神小学期项目simple_ffmpeg_player模块就结束了~

1.simple_ffmpeg_play_sdl2r.cpp
/*** 最简单的SDL2播放视频的例子(SDL2播放RGB/YUV)* Simplest Video Play SDL2 (SDL2 play RGB/YUV) ** 雷霄骅 Lei Xiaohua* leixiaohua1020@126.com* 中国传媒大学/数字电视技术* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020** 本程序使用SDL2播放RGB/YUV视频像素数据。SDL实际上是对底层绘图* API(Direct3D,OpenGL)的封装,使用起来明显简单于直接调用底层* API。** 函数调用步骤如下: ** [初始化]* SDL_Init(): 初始化SDL。* SDL_CreateWindow(): 创建窗口(Window)。* SDL_CreateRenderer(): 基于窗口创建渲染器(Render)。* SDL_CreateTexture(): 创建纹理(Texture)。** [循环渲染数据]* SDL_UpdateTexture(): 设置纹理的数据。* SDL_RenderCopy(): 纹理复制给渲染器。* SDL_RenderPresent(): 显示。** This software plays RGB/YUV raw video data using SDL2.* SDL is a wrapper of low-level API (Direct3D, OpenGL).* Use SDL is much easier than directly call these low-level API.  ** The process is shown as follows:** [Init]* SDL_Init(): Init SDL.* SDL_CreateWindow(): Create a Window.* SDL_CreateRenderer(): Create a Render.* SDL_CreateTexture(): Create a Texture.** [Loop to Render data]* SDL_UpdateTexture(): Set Texture's data.* SDL_RenderCopy(): Copy Texture to Render.* SDL_RenderPresent(): Show.*/#include <stdio.h>extern "C"
{
#include "sdl/SDL.h"
};const int bpp=12;int screen_w=500,screen_h=500;
const int pixel_w=320,pixel_h=180;unsigned char buffer[pixel_w*pixel_h*bpp/8];//Refresh Event
#define REFRESH_EVENT  (SDL_USEREVENT + 1)#define BREAK_EVENT  (SDL_USEREVENT + 2)int thread_exit=0;/*** 每隔40ms发送一次消息,eventType为REFRESH_EVENT* 退出循环后,会发送一次eventType为BREAK_EVENT的事件** thread_exit:退出*/
int refresh_video(void *opaque){thread_exit=0;while (!thread_exit) {SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event);SDL_Delay(40);}thread_exit=0;//BreakSDL_Event event;event.type = BREAK_EVENT;SDL_PushEvent(&event);return 0;
}int main(int argc, char* argv[])
{if(SDL_Init(SDL_INIT_VIDEO)) {  printf( "Could not initialize SDL - %s\n", SDL_GetError()); return -1;} SDL_Window *screen; //SDL 2.0 Support for multiple windows// 创建窗口SDL_CreateWindow(SDL_WINDOW_RESIZABLE:可调节大小)screen = SDL_CreateWindow("Simplest Video Play SDL2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h,SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);if(!screen) {  printf("SDL: could not create window - exiting:%s\n",SDL_GetError());  return -1;}// 创建渲染器SDL_RendererSDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0);  Uint32 pixformat=0;//IYUV: Y + U + V  (3 planes)//YV12: Y + V + U  (3 planes)pixformat= SDL_PIXELFORMAT_IYUV;  // 创建纹理SDL_TextureSDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer,pixformat, SDL_TEXTUREACCESS_STREAMING,pixel_w,pixel_h);FILE *fp=NULL;fp=fopen("test_yuv420p_320x180.yuv","rb+");if(fp==NULL){printf("cannot open this file\n");return -1;}// 视频显示区域SDL_Rect sdlRect;  // 发送读取视频事件的线程SDL_Thread *refresh_thread = SDL_CreateThread(refresh_video,NULL,NULL);SDL_Event event;while(1){//WaitSDL_WaitEvent(&event);if(event.type==REFRESH_EVENT){/*** fread()函数用于从文件流中读取数据,其原型为:*     size_t  fread(void *buffer, size_t size, size_t count, FILE * stream);** 【参数】buffer为接收数据的地址,size为一个单元的大小,count为单元个数,stream为文件流。* fread()函数每次从stream中最多读取count个单元,每个单元大小为size个字节,将读取的数据放到buffer;文件流的位置指针后移 size * count 字节。** 【返回值】返回实际读取的单元个数。如果小于count,则可能文件结束或读取出错;可以用ferror()检测是否读取出错,用feof()函数检测是否到达文件结尾。如果size或count为0,则返回0。* 与fread()相对应的函数为fwrite(),fread() 和 fwrite() 一般用于二进制文件的输入输出*/if (fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp) != pixel_w*pixel_h*bpp/8){// Loop// SEEK_SET – 移动到文件开始处 // 更多信息参见https://fresh2refresh.com/c-programming/c-file-handling/fseek-seek_set-seek_cur-seek_end-functions-c/fseek(fp, 0, SEEK_SET);fread(buffer, 1, pixel_w*pixel_h*bpp/8, fp);}// 设置纹理数据SDL_UpdateTexture( sdlTexture, NULL, buffer, pixel_w);  //FIX: If window is resizesdlRect.x = 0;  sdlRect.y = 0;  sdlRect.w = screen_w;  sdlRect.h = screen_h;  // 清理渲染器SDL_RenderClear( sdlRenderer );   // 将纹理数据copy到渲染器SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect);  // 显示SDL_RenderPresent( sdlRenderer );  }else if(event.type==SDL_WINDOWEVENT){//If ResizeSDL_GetWindowSize(screen,&screen_w,&screen_h);}else if(event.type==SDL_QUIT){thread_exit=1;}else if(event.type==BREAK_EVENT){break;}}SDL_Quit();return 0;
}
2.simple_ffmpeg_player_su.cpp
/*** 最简单的基于FFmpeg的视频播放器2(SDL升级版)* Simplest FFmpeg Player 2(SDL Update)** 雷霄骅 Lei Xiaohua* leixiaohua1020@126.com* 中国传媒大学/数字电视技术* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020** 第2版使用SDL2.0取代了第一版中的SDL1.2* Version 2 use SDL 2.0 instead of SDL 1.2 in version 1.** 本程序实现了视频文件的解码和显示(支持HEVC,H.264,MPEG2等)。* 是最简单的FFmpeg视频解码方面的教程。* 通过学习本例子可以了解FFmpeg的解码流程。* 本版本中使用SDL消息机制刷新视频画面。* This software is a simplest video player based on FFmpeg.* Suitable for beginner of FFmpeg.** 备注:* 标准版在播放视频的时候,画面显示使用延时40ms的方式。这么做有两个后果:* (1)SDL弹出的窗口无法移动,一直显示是忙碌状态* (2)画面显示并不是严格的40ms一帧,因为还没有考虑解码的时间。* SU(SDL Update)版在视频解码的过程中,不再使用延时40ms的方式,而是创建了* 一个线程,每隔40ms发送一个自定义的消息,告知主函数进行解码显示。这样做之后:* (1)SDL弹出的窗口可以移动了* (2)画面显示是严格的40ms一帧* Remark:* Standard Version use's SDL_Delay() to control video's frame rate, it has 2* disadvantages:* (1)SDL's Screen can't be moved and always "Busy".* (2)Frame rate can't be accurate because it doesn't consider the time consumed * by avcodec_decode_video2()* SU(SDL Update)Version solved 2 problems above. It create a thread to send SDL * Event every 40ms to tell the main loop to decode and show video frames.*/#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "SDL2/SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <SDL2/SDL.h>
#ifdef __cplusplus
};
#endif
#endif//Refresh Event
#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)int thread_exit=0;
int thread_pause=0;/*** 每隔40ms发送一次消息,eventType为SFM_REFRESH_EVENT** thread_pause:暂停* thread_exit:退出*/
int sfp_refresh_thread(void *opaque){thread_exit=0;thread_pause=0;while (!thread_exit) {if(!thread_pause){SDL_Event event;event.type = SFM_REFRESH_EVENT;SDL_PushEvent(&event);}SDL_Delay(40);}thread_exit=0;thread_pause=0;//BreakSDL_Event event;event.type = SFM_BREAK_EVENT;SDL_PushEvent(&event);return 0;
}int main(int argc, char* argv[])
{// 封装格式上下文的结构体,也是统领全局的结构体,保存了视频文件封装格式的相关信息AVFormatContext    *pFormatCtx;// 视频流在文件中的位置int                i, videoindex;// 编码器上下文结构体,保存了视频(音频)编解码相关信息AVCodecContext  *pCodecCtx;// 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体AVCodec           *pCodec;// 存储一帧解码后像素(采样)数据AVFrame *pFrame,*pFrameYUV;// 存储图像数据unsigned char *out_buffer;// 存储一帧压缩编码数据AVPacket *packet;// 是否获取到数据的返回值int ret, got_picture;//------------SDL----------------int screen_w,screen_h;// 窗口SDL_Window *screen;// 渲染器SDL_Renderer* sdlRenderer;// 纹理SDL_Texture* sdlTexture;// 一个简单的矩形SDL_Rect sdlRect;// 线程SDL_Thread *video_tid;// 事件SDL_Event event;struct SwsContext *img_convert_ctx;//char filepath[]="bigbuckbunny_480x272.h265";char filepath[]="Titanic.ts";// 注册复用器,编码器等(参考FFmpeg解码流程图)av_register_all();// 进行网络组件的全局初始化(详细代码请参考第三篇文章中代码对应位置的描述)avformat_network_init();/*** Allocate an AVFormatContext.* avformat_free_context() can be used to free the context and everything* allocated by the framework within it.* 分配AVFormatContext。* avformat_free_context()可用于释放上下文以及框架在其中分配的所有内容。*/pFormatCtx = avformat_alloc_context();// 打开多媒体数据并且获得一些相关的信息(参考FFmpeg解码流程图)if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){printf("Couldn't open input stream.\n");return -1;}// 读取一部分视音频数据并且获得一些相关的信息(参考FFmpeg解码流程图)if(avformat_find_stream_info(pFormatCtx,NULL)<0){printf("Couldn't find stream information.\n");return -1;}// 每个视频文件中有多个流(视频流、音频流、字幕流等,而且可有多个),循环遍历找到视频流// 判断方式:AVFormatContext->AVStream->AVCodecContext->codec_type是否为AVMEDIA_TYPE_VIDEOvideoindex=-1;for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){videoindex=i;break;}// 如果没有视频流,返回if(videoindex==-1){printf("Didn't find a video stream.\n");return -1;}// 保存视频流中的AVCodecContextpCodecCtx=pFormatCtx->streams[videoindex]->codec;// 用于查找FFmpeg的解码器(参考FFmpeg解码流程图)pCodec=avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec==NULL){printf("Codec not found.\n");return -1;}// 初始化一个视音频编解码器的AVCodecContext(参考FFmpeg解码流程图)if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){printf("Could not open codec.\n");return -1;}// 创建AVFrame,用来存放解码后的一帧的数据pFrame=av_frame_alloc();pFrameYUV=av_frame_alloc();// av_image_get_buffer_size:返回使用给定参数存储图像所需的数据量的字节大小out_buffer=(unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,  pCodecCtx->width, pCodecCtx->height,1));// 根据指定的图像参数和提供的数组设置数据指针和线条(data pointers and linesizes)av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize,out_buffer,AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height,1);//Output Info-----------------------------printf("---------------- File Information ---------------\n");av_dump_format(pFormatCtx,0,filepath,0);printf("-------------------------------------------------\n");// sws_getContext():初始化一个SwsContextimg_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); // 初始化SDL系统if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {  printf( "Could not initialize SDL - %s\n", SDL_GetError()); return -1;} //SDL 2.0 Support for multiple windows// 保存视频的实际宽高,用于下面代码中复用screen_w = pCodecCtx->width;screen_h = pCodecCtx->height;// 创建窗口SDL_CreateWindowscreen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,screen_w, screen_h,SDL_WINDOW_OPENGL);if(!screen) {  printf("SDL: could not create window - exiting:%s\n",SDL_GetError());  return -1;}// 创建渲染器SDL_RenderersdlRenderer = SDL_CreateRenderer(screen, -1, 0);  //IYUV: Y + U + V  (3 planes)//YV12: Y + V + U  (3 planes)// 创建纹理SDL_TexturesdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);  sdlRect.x=0;sdlRect.y=0;sdlRect.w=screen_w;sdlRect.h=screen_h;// 创建一个AVPacket,用来存放下面循环获取到的未解码帧packet=(AVPacket *)av_malloc(sizeof(AVPacket));// 创建线程sfp_refresh_thread,进行视频数据的读取video_tid = SDL_CreateThread(sfp_refresh_thread,NULL,NULL);//------------SDL End------------//Event Loopfor (;;) {//WaitSDL_WaitEvent(&event);if(event.type==SFM_REFRESH_EVENT){// sfp_refresh_thread中定义的事件,视频播放的事件while(1){// 读取到的数据为空,则停止播放if(av_read_frame(pFormatCtx, packet)<0)thread_exit=1;// 循环找到视频流if(packet->stream_index==videoindex)break;}// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame// 详细讲解请查看我的源码系列文章:https://blog.csdn.net/asd501823206/article/details/97013677ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if(ret < 0){printf("Decode Error.\n");return -1;}if(got_picture){sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);//SDL---------------------------// 设置纹理数据SDL_UpdateTexture( sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0] );// 清理渲染器SDL_RenderClear( sdlRenderer );//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );// 将纹理数据copy到渲染器SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, NULL);// 显示SDL_RenderPresent( sdlRenderer );//SDL End-----------------------}av_free_packet(packet);}else if(event.type==SDL_KEYDOWN){//Pauseif(event.key.keysym.sym==SDLK_SPACE)// 空格键thread_pause=!thread_pause;}else if(event.type==SDL_QUIT){// 系统event,点击关闭时返回该event// thread_exit置为1,退出监听事件的循环thread_exit=1;}else if(event.type==SFM_BREAK_EVENT){break;}}sws_freeContext(img_convert_ctx);SDL_Quit();//--------------av_frame_free(&pFrameYUV);av_frame_free(&pFrame);avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0;
}

雷神simplest_ffmpeg_player解析(四)相关推荐

  1. 北京络捷斯特第三方物流信息系统技术解析(四) 订单录入-运输订单

    北京络捷斯特第三方物流信息系统技术解析(四) 订单录入-运输订单 2.1.3运输订单 运输订单有两个标签要编写,包括有订单信息和订单货品 订单信息界面截图: 2.1(图1) 订单货品信息截图: 2.1 ...

  2. Android Volley完全解析(四),带你从源码的角度理解Volley

    经过前三篇文章的学习,Volley的用法我们已经掌握的差不多了,但是对于Volley的工作原理,恐怕有很多朋友还不是很清楚.因此,本篇文章中我们就来一起阅读一下Volley的源码,将它的工作流程整体地 ...

  3. 解析四种大数据文件格式

    众所周知,Apache Spark支持许多种不同的数据格式,其中包括:无处不在的CSV格式.对于Web友好的JSON格式.以及常被用于大数据分析的Apache Parquet和Apache Avro. ...

  4. GB28181 码流解析( 四 )

    GB28181 码流解析RTP( 四 ) GB28181最关键的就是码流解析了,本文解析海康, 大华摄像头的码流, 解析出来h264码流. 海康, 大华摄像头的码流是 根据国标 GB/T28181-2 ...

  5. 数据结构特性解析 (四)LinkedList

    描述 LinkedList应该也是开发中比较常用的数据结构了,其基于链表数据结构实现,添加和删除效率相对比较高,而随机访问效率偏低 特点 1.LinkedList是双向不循环链表 通过查看链节点类: ...

  6. 步步为营 .NET三层架构解析 四、Model设计(四种设计方式)

    说到Model设计,我们先谈谈它的作用: Model又叫实体类,model层里面的一个类对应数据库里面的一张表, 类里面的每一个属性对应表里面的一个字段,每个属性都有自己的 GET 和 SET 方法, ...

  7. Tomcat源码解析四:Tomcat关闭过程

    我们在Tomcat启动过程(Tomcat源代码阅读系列之三)一文中已经知道Tomcat启动以后,会启动6条线程,他们分别如下: [java] view plaincopy "ajp-bio- ...

  8. 【Rxjs】 - 解析四种主题Subject

    原文地址: https://segmentfault.com/a/1190000012669794 引言 开发ngx(angular 2+)应用时,基本上到处都会用到rxjs来处理异步请求,事件调用等 ...

  9. 实战 webpack 4 配置解析四

    接上篇: 实战 webpack 4 配置解析三 WEBPACK.PROD.JS 解析 现在让我们看看我们的 webpack.prod.js 配置文件,它包含了我们正在处理项目时用于生产构建的所有设置. ...

  10. Cesium案例解析(四)——3DModels模型加载

    文章目录 1. 概述 2. 代码 3. 解析 4. 参考 1. 概述 Cesium自带的3D Models示例,展示了如何加载glTF格式三维模型数据.glTF是为WebGL量身定制的数据格式,在网络 ...

最新文章

  1. 8岁上海小学生B站教编程惊动苹果CEO,库克亲送生日祝福
  2. 第一章 简单工厂模式
  3. Linux程序之触摸,linux 触摸屏驱动编写
  4. phpcms数据库 mysql 清空与导入 - phpMyAdmin操作 - 方法篇
  5. 线性阵列、圆周阵列、曲线阵列
  6. 谷歌技术三宝之MapReduce(转)
  7. BZOJ:1001狼抓兔子
  8. java 结束循环_java如何终止多层循环
  9. VB6基础教程与源代码
  10. 2021美赛C题M奖思路
  11. xpraid安装_在Win2003/XP安装光盘中集成RAID驱动 不用软驱装RAID/SATA/SAS驱动
  12. maven上传pom文件
  13. 如何把几张图片合并成一张图片?
  14. Protel DXP 2004的元件封装/快捷键大全/PCB使用技巧
  15. 国际标准时间显示格式
  16. 常用元数据标准/受控词表/本体词汇表/的命名空间前缀及IRI链接汇总
  17. 解决:向日葵连接已断开
  18. 彩票小贩潜伏50天惊人绽放携12人合买中52万
  19. 赛效:WPS如何给文档内容添加下划线
  20. BIN,S19,M0T,SREC,HEX文件解析;FileParse(一)之文件详解

热门文章

  1. c#禁止任务管理器关闭任务
  2. uni-app 从本地项目选择图片或使用相机拍照及图片预览
  3. 四、音频如何从USB输入输出
  4. Java初学者 搭建Java 开发环境
  5. dm-thin-provision架构及实现简析
  6. web前端面试技巧-如何自我介绍?如何应对hr?
  7. 国内网络游戏开发技术现状和趋势
  8. MSP430F149程序——RS485
  9. java 提交mac地址栏_Mac系统快捷键大全 - 米扑博客
  10. linux sticky,session_sticky命令