新手刚开始学习ffmpeg。
参考网上的ffmpeg资料和雷神的博客,简易做了个播放器,边学边做。
暂时未做音频,所以播放时有沙沙声。
视频的播放速度也有问题,需要再调整,后续再处理速度和音频的问题!
额,界面功能键也没做,后续再说吧。
放效果图:


该播放器是基于ffmpeg+SDL,可播放本地视频和网络URL地址的视频,适合初学者学习。

视频主要解封装过程

FFmpeg的视频解码过程主要有以下几个步骤:

  1. 初始化所有组件(所有的文件格式及其对应的CODEC) av_register_all()
  2. 打开文件 avformat_open_input()
  3. 从文件中提取流信息 avformat_find_stream_info()
  4. 在多个数据流中找到视频流 video stream(类型为MEDIA_TYPE_VIDEO
  5. 查找视频流相对应的解码器 avcodec_find_decoder
  6. 打开解码器 avcodec_open2()
  7. 为解码帧分配内存 av_frame_alloc()
  8. 从流中读取读取数据到Packet中 av_read_frame()
  9. 对视频流的帧进行解码,调用 avcodec_decode_video2()

下面放核心代码

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>#include <SDL.h>
#include <SDL_thread.h>#ifdef __MINGW32__
#undef main //防止SDL的MAIN问题
#endif#include <stdio.h>int main(int argc, char *argv[]) {AVFormatContext *pFormatCtx = NULL;int             i, videoStream;AVCodecContext  *pCodecCtx = NULL;AVCodec         *pCodec = NULL;AVFrame         *pFrame = NULL; AVPacket        packet;int             frameFinished;AVDictionary    *optionsDict = NULL;struct SwsContext *sws_ctx = NULL;SDL_Overlay     *bmp = NULL;SDL_Surface     *screen_sdl = NULL;SDL_Rect        rect;SDL_Event       event;if(argc < 2) {fprintf(stderr, "Usage: please input <file>\n");exit(1);}//初始化所有组件,只有调用了该函数,才能使用复用器和编解码器av_register_all();if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());exit(1);}//打开一个文件并解析。可解析的内容包括:视频流、音频流、视频流参数、音频流参数、视频帧索引。//该函数读取文件的头信息,并将其信息保存到AVFormatContext结构体中if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)return -1; // Couldn't open file//作用是为pFormatContext->streams填充上正确的音视频格式信息,通过av_dump_format函数输出if(avformat_find_stream_info(pFormatCtx, NULL)<0)return -1; // Couldn't find stream information//将音视频数据格式通过av_log输出到指定的文件或者控制台,删除该函数的调用没有任何的影响av_dump_format(pFormatCtx, 0, argv[1], 0);//要解码视频,首先在AVFormatContext包含的多个流中找到CODEC,类型为AVMEDIA_TYPE_VIDEOvideoStream=-1;for(i=0; i<pFormatCtx->nb_streams; i++)if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {videoStream=i;break;}if(videoStream==-1)return -1; // 找不到就结束// Get a pointer to the codec context for the video streampCodecCtx=pFormatCtx->streams[videoStream]->codec;// 寻找解码器pCodec=avcodec_find_decoder(pCodecCtx->codec_id);if(pCodec==NULL) {fprintf(stderr, "Unsupported codec!\n");return -1; // 找不到Codec}// 调用avcodec_open2打开codecif(avcodec_open2(pCodecCtx, pCodec, &optionsDict)<0)return -1; // 无法打开codec// 对 video frame进行分配空间pFrame=av_frame_alloc();//使用SDL做界面screen_sdl = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);if(!screen_sdl ) {fprintf(stderr, "SDL: could not set video mode - exiting\n");exit(1);}// 把YUV数据放到screenbmp = SDL_CreateYUVOverlay(pCodecCtx->width,pCodecCtx->height,SDL_YV12_OVERLAY,screen_sdl );sws_ctx =sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt, //定义输入图像信息(寬、高、颜色空间(像素格式))pCodecCtx->width,pCodecCtx->height,   AV_PIX_FMT_YUV420P,//定义输出图像信息(寬、高、颜色空间(像素格式))SWS_BILINEAR,//选择缩放算法(只有当输入输出图像大小不同时有效)NULL,NULL,NULL);// 读取frames数据并且保存i=0;while(av_read_frame(pFormatCtx, &packet)>=0) {if(packet.stream_index==videoStream) {// Decode video frame//作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrameavcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);if(frameFinished) {SDL_LockYUVOverlay(bmp);
//把图片转为YUV使用的格式AVPicture pict;pict.data[0] = bmp->pixels[0];pict.data[1] = bmp->pixels[2];pict.data[2] = bmp->pixels[1];pict.linesize[0] = bmp->pitches[0];pict.linesize[1] = bmp->pitches[2];pict.linesize[2] = bmp->pitches[1];sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0,pCodecCtx->height,pict.data,pict.linesize);SDL_UnlockYUVOverlay(bmp);rect.x = 0;rect.y = 0;rect.w = pCodecCtx->width;rect.h = pCodecCtx->height;SDL_DisplayYUVOverlay(bmp, &rect);}}//释放 packetav_free_packet(&packet);SDL_PollEvent(&event);switch(event.type) {case SDL_QUIT:SDL_Quit();exit(0);break;default:break;}}// 释放掉frameav_free(pFrame);//关闭打开的流和解码器avcodec_close(pCodecCtx);avformat_close_input(&pFormatCtx);return 0;
}

使用方法:编译出来的文件+本地视频/视频URL地址

./a.out xxx.mp4
./a.out url地址

下面介绍各种函数:
av_read_frame()//读取帧数据

该函数用于读取具体的音/视频帧数据
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
参数说明:
AVFormatContext *s      // 文件格式上下文,输入的AVFormatContext
AVPacket *pkt        // 这个值不能传NULL,必须是一个空间,输出的AVPacket// 返回值:return 0 is OK, <0 on error or end of file

SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)

就是初始化SDL
参数:SDL_INIT_TIMER      Initializes the timer subsystem.SDL_INIT_AUDIO      Initializes the audio subsystem.SDL_INIT_VIDEO      Initializes the video subsystem.SDL_INIT_CDROM      Initializes the cdrom subsystem.SDL_INIT_JOYSTICK   Initializes the joystick subsystem.SDL_INIT_EVERYTHING Initialize all of the above.SDL_INIT_NOPARACHUTEPrevents SDL from catching fatal signals.SDL_INIT_EVENTTHREAD

avformat_open_input

  //打开一个文件并解析。可解析的内容包括:视频流、音频流、视频流参数、音频流参数、视频帧索引。//该函数读取文件的头信息,并将其信息保存到AVFormatContext结构体中

pCodecCtx=pFormatCtx->streams[videoStream]->codec;

用一个指针指向视频流codec,为找解码器做准备

pCodec=avcodec_find_decoder(pCodecCtx->codec_id);

通过codec指针,avcodec_find_decoder为视频流找到解码器

avcodec_open2(pCodecCtx, pCodec, &optionsDict)

寻找解码器,找到就调用函数avcodec_open2打开,后面记得要关闭解码器

SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);

SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags);
前面两个参数为长宽
bpp:一般默认为0
flags:一般默认为0,其它参数如下。SDL_SWSURFACE       在系统内存中创建视频表面SDL_HWSURFACE       在视频内存中创建视频表面SDL_ASYNCBLIT       允许使用显示表面的异步更新。这通常会减慢在单个CPU上的速度,但可能会提高SMP系统的速度。SDL_ANYFORMAT       通常,如果所请求的每像素位(bpp)的视频表面不可用,SDL将模拟具有阴影表面的视频表面。通过SDL_ANYFORMAT可以防止这种情况发生,并导致SDL使用视频表面,无论其像素深度如何。                        SDL_HWPALETTE       赋予SDL专用调色板访问权SDL_DOUBLEBUF       启用硬件双缓冲;仅对SDL_HWSURFACE有效。调用SDL_Flip将翻转buf‐fer并更新屏幕。所有的绘图都将在目前没有显示的表面上进行。如果无法启用双缓冲,那么SDL_Flip将在整个屏幕上执行SDL_UpdateRectSDL_FULLSCREEN      SDL  will attempt to use a fullscreen mode. If a hardware resolution change is not possible (for what‐ever reason), the next higher resolution will be used and the display window centered on a black back‐ground.SDL_OPENGL          Create  an  OpenGL  rendering  context.  You  should  have previously set OpenGL video attributes withSDL_GL_SetAttribute.SDL_OPENGLBLIT      Create an OpenGL rendering context, like above, but allow normal blitting operations. The screen  (2D)surface may have an alpha channel, and SDL_UpdateRects must be used for updating changes to the screensurface.SDL_RESIZABLE       Create a resizable window. When the window is resized by the user a SDL_VIDEORESIZE event is generatedand SDL_SetVideoMode can be called again with the new size.SDL_NOFRAME         If  possible,  SDL_NOFRAME  causes  SDL  to  create  a  window  with no title bar or frame decoration.Fullscreen modes automatically have this flag set.

bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
pCodecCtx->height,
SDL_YV12_OVERLAY, //选择Y\V\U模式
screen_sdl ); //这个就是绑定播放窗口

函数原型:
SDL_Overlay *SDL_CreateYUVOverlay(int width, int height, Uint32 format, SDL_Surface *display);
参数解析:width、height两参数指视频的分辨率大小,format有关图片的YUV三个参数,display绑定播放窗口
//SDL_CreateYUVOverlay - Create a YUV video overlay
//这函数就是把我们的YUV图像放在屏幕上
//CreateYUVOverlay的大小为视频分辨率,DisplayYUVOverlay则为播放窗口的大小

format参数具体项解析:

       #define SDL_YV12_OVERLAY  0x32315659  /* Planar mode: Y + V + U */#define SDL_IYUV_OVERLAY  0x56555949  /* Planar mode: Y + U + V */#define SDL_YUY2_OVERLAY  0x32595559  /* Packed mode: Y0+U0+Y1+V0 */#define SDL_UYVY_OVERLAY  0x59565955  /* Packed mode: U0+Y0+V0+Y1 */#define SDL_YVYU_OVERLAY  0x55595659  /* Packed mode: Y0+V0+Y1+U0 */

avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

ffmpeg中的avcodec_decode_video2()的作用是解码一帧视频数据。
输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。
该函数的声明位于libavcodec\avcodec.h

FFmpeg里面的sws_scale库可以在一个函数里面同时实现:1.图像色彩空间转换;2.分辨率缩放;3.前后图像滤波处理。
函数struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
                                                                   int dstW, int dstH, enum AVPixelFormat dstFormat,
                                                                   int flags,
                                                        SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);
函数目的:初始化sws_scale

参数int srcW, int srcH, enum AVPixelFormat srcFormat定义输入图像信息(寬、高、颜色空间(像素格式))
参数int dstW, int dstH, enum AVPixelFormat dstFormat定义输出图像信息寬、高、颜色空间(像素格式))。
参数int flags选择缩放算法(只有当输入输出图像大小不同时有效)
//后三个参数一般为NULL
参数SwsFilter *srcFilter, SwsFilter *dstFilter分别定义输入/输出图像滤波器信息,如果不做前后图像滤波,输入NULL
参数const double *param定义特定缩放算法需要的参数,默认为NULL
函数返回SwsContext结构体,定义了基本变换信息。
如果是对一个序列的所有帧做相同的处理,函数sws_getContext只需要调用一次就可以了。
sws_getContext(w, h, YV12, w, h, NV12, 0, NULL, NULL, NULL);      // YV12->NV12 色彩空间转换
sws_getContext(w, h, YV12, w/2, h/2, YV12, 0, NULL, NULL, NULL);  // YV12图像缩小到原图1/4
sws_getContext(w, h, YV12, 2w, 2h, YN12, 0, NULL, NULL, NULL);    // YV12图像放大到原图4倍,并转换为NV12结构

int sws_scale(struct SwsContext *c,
                       const uint8_t *const srcSlice[], const int srcStride[],
                       int srcSliceY, int srcSliceH,
                       uint8_t *const dst[], const int dstStride[]);
函数目的:做转换

参数struct SwsContext *c,为上面sws_getContext函数返回值;
参数const uint8_t *const srcSlice[], const int srcStride[]定义输入图像信息(当前处理区域的每个通道数据指针,每个通道行字节数)
stride定义下一行的起始位置。stride和width不一定相同,这是因为:
1.由于数据帧存储的对齐,有可能会向每行后面增加一些填充字节这样 stride = width + N;
2.packet色彩空间下,每个像素几个通道数据混合在一起,例如RGB24,每个像素3字节连续存放,因此下一行的位置需要跳过3*width字节。
srcSlice和srcStride的维数相同,由srcFormat值来。
csp       维数        宽width      跨度stride      高
YUV420     3        w, w/2, w/2    s, s/2, s/2   h, h/2, h/2
YUYV       1        w, w/2, w/2   2s, 0, 0       h, h, h
NV12       2        w, w/2, w/2    s, s, 0       h, h/2
RGB24      1        w, w,   w     3s, 0, 0       h, 0, 0
参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。
这种设置是为了多线程并行,例如可以创建两个线程,第一个线程处理 [0, h/2-1]行,第二个线程处理 [h/2, h-1]行。并行处理加快速度。
参数uint8_t *const dst[], const int dstStride[]定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)

SDL_UnlockYUVOverlay(bmp);
SDL_DisplayYUVOverlay(bmp, &rect);

SDL_UnlockYUVOverlay:对YUV解锁,overlay展示之前必须先解锁
SDL_DisplayYUVOverlay:解码出一帧数据后就可通过调用此函数进行视频的显示

其实到这里就可以明白了,想要正常播放一个视频,就是将视频分解成一帧一帧的数据,然后再将每一帧显示出来,每一帧接连的播放合起来就是我们看到的视频。

SDL_PollEvent(&event);

SDL_PollEvent从事件队列里取出事件,判断类型,然后处理。
SDL_Event是一个结构体,其定义如下:
typedef union SDL_Event
{Uint8 type; //事件类型SDL_ActiveEvent active; //窗口焦点、输入焦点及鼠标焦点的失去和得到事件SDL_KeyboardEvent key; //键盘事件,键盘按下和释放SDL_MouseMotionEvent motion; //鼠标移动事件SDL_MouseButtonEvent button; //鼠标按键事件SDL_JoyAxisEvent jaxis; //手柄事件SDL_JoyBallEvent jball; //手柄事件SDL_JoyHatEvent jhat; //手柄事件SDL_JoyButtonEvent jbutton; //手柄事件SDL_ResizeEvent resize; //窗口大小变化事件SDL_ExposeEvent expose; //窗口重绘事件SDL_QuitEvent quit; //退出事件SDL_UserEvent user; //用户自定义事件SDL_SysWMEvent syswm; //平台相关的系统事件
} SDL_Event;

这个只是一个测试的demo,将在后续的博文再更新修正视频的播放速度问题!

Linux 基于ffplay的简易视频播放器(网络+本地)相关推荐

  1. 【基于QMediaPlayer的简易视频播放器】— 3、结合QSlider实现播放进度控制和音量控制

    基于QMediaPlayer的简易视频播放器 1.创建基本布局 2.QMediaPlayer的基本使用 3.结合QSlider实现播放进度控制和音量控制 4.重载QSlider鼠标响应事件,实现单击跳 ...

  2. 基于IJKPlayer的简易视频播放器

    写在前面 PS:没错,这就是那篇躺在草稿箱里好几个月的僵尸博客,直到现在(2017年1月中旬)才打算写完,简单总结一下知识点,以备不时之需. 现在的项目是一个电影预告的APP,必然得有个视频播放器,之 ...

  3. Linux 基于QT的mplayer视频播放器(实现进度条的拖动、播放列表等)

    UI随手做的,有点简陋 先放效果图: 功能:实现了音量的进度条,播放进度条,播放暂停,停止,快进快退等等,并且界面可以跟随窗口缩放.(进度条可拖动控制视频.音频) 下面界面视频的功能: 1.播放 用m ...

  4. C语言基于GTK+Libvlc实现的简易视频播放器(二)

    简易视频播放器-全屏播放 一.课程说明 上一次我们使用gtk+libvlc实现了一个最简单的视频播放器,可以实现点击按钮暂定和停止播放视频,以及同步显示视频播放进度,但即使作为一个视频播放器,只有这些 ...

  5. Linux上的一些开源视频播放器

    介绍Linux下的一些开源视频播放器. VLC Media Player 内置编解码器.定制选项.跨平台.支持每种视频文件格式.扩展可用于增加功能.能够处理各种文件格式和编解码器.字幕同步.音频/视频 ...

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

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

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

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  8. 基于Django框架的视频播放器设计

    基于Django框架的视频播放器设计 前言 一.简介 二.详细实现步骤 1.路由配置 2.后台代码设计(对云盘接口的访问) 3.后台代码设计(流式视频传输) 4.前端功能设计(视频播放列表) 5.前端 ...

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

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

最新文章

  1. 雷军做程序员时写的文章,太牛了!
  2. java 面试题 简书_java面试题
  3. 【直播回放】80分钟剖析GAN如何从各个方向提升图像的质量
  4. 发布CodeBuild.Net代码自动生成器 V2008 2.01(Vs2008)和架构实例源码Demo
  5. opencv笔记(6):彩色图像直方图
  6. 第二届世界智能大会,看大咖眼中的智能时代
  7. Spring搭建本地源码调试环境
  8. WEBMAX函数教程
  9. 钉钉企业内微应用对现有系统的免登和消息发送
  10. 校招22届大疆 嵌入式面经/23届投递可私戳内推!
  11. 苏州企业注册商标需要提前做好哪些工作?
  12. 试题 算法训练 后缀数组——最长重复子串
  13. 毕业设计-基于微信小程序的校园一卡通应用系统
  14. win8.1系统自带微软拼音输入法卸载教程
  15. 实训日志(十)——达芬奇调色
  16. java从入门到精通----OOP 2
  17. VS2015的下载及安装
  18. %#o,%#x什么意思
  19. “明德”网友关于《郭初阳课堂实录》一书的介绍
  20. linux netgear usb,家用四槽位 NETGEAR ReadyNAS NV+评测

热门文章

  1. SBL中的HAL和DAL层
  2. 如何将收件箱中的发件人批量导入企业云邮通讯录
  3. python 保存和读取中间变量
  4. select函数的作用!
  5. Harry Potter and the Chamber of Secrets
  6. 音视频系列--哥伦布编码和H264片段sps解析宽高信息
  7. 文件管理-----操作系统
  8. 虚拟服务器影视站设置,虚拟主机可以开电影网站吗
  9. ZZULIOJ.1107: 回文数猜想(函数专题)
  10. 如何在Mac上禁用屏幕快照预览缩略图