视频播放过程

首先简单介绍以下视频文件的相关知识。我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器(Container), 不同的容器格式规定了其中音视频数据的组织方式(也包括其他数据,比如字幕等)。容器中一般会封装有视频和音频轨,也称为视频流(stream)和音频 流,播放视频文件的第一步就是根据视频文件的格式,解析(demux)出其中封装的视频流、音频流以及字幕(如果有的话),解析的数据读到包 (packet)中,每个包里保存的是视频帧(frame)或音频帧,然后分别对视频帧和音频帧调用相应的解码器(decoder)进行解码,比如使用 H.264编码的视频和MP3编码的音频,会相应的调用H.264解码器和MP3解码器,解码之后得到的就是原始的图像(YUV or RGB)和声音(PCM)数据,然后根据同步好的时间将图像显示到屏幕上,将声音输出到声卡,最终就是我们看到的视频。
 
FFmpeg的API就是根据这个过程设计的,因此使用FFmpeg来处理视频文件的方法非常直观简单。下面就一步一步介绍从视频文件中解码出图片的过程。
 
声明变量
首先定义整个过程中需要使用到的变量:
int main(int argc, const char *argv[]) {  
AVFormatContext *pFormatCtx = NULL;  
int             i, videoStream;
  AVCodecContext  *pCodecCtx;  
AVCodec         *pCodec;  
AVFrame         *pFrame;  
AVFrame         *pFrameRGB;  
AVPacket        packet;  
int             frameFinished;  
int             numBytes;  
uint8_t         *buffer;
}
AVFormatContext:保存需要读入的文件的格式信息,比如流的个数以及流数据等
AVCodecCotext:保存了相应流的详细编码信息,比如视频的宽、高,编码类型等。
pCodec:真正的编解码器,其中有编解码需要调用的函数
AVFrame:用于保存数据帧的数据结构,这里的两个帧分别是保存颜色转换前后的两帧图像
AVPacket:解析文件时会将音/视频帧读入到packet中
打开文件
接下来我们打开一个视频文件。
 
1av_register_all();
av_register_all 定义在 libavformat 里,调用它用以注册所有支持的文件格式以及编解码器,从其实现代码里可以看到它会调用 avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。
 
1if( avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0 )
2    return -1;
使用新的API avformat_open_input来打开一个文件,第一个参数是一个AVFormatContext指针变量的地址,它会根据打开的文件信息填充AVFormatContext,需要注意的是,此处的pFormatContext必须为NULL或由avformat_alloc_context分配得到,这也是上一节中将其初始化为NULL的原因,否则此函数调用会出问题。第二个参数是打开的文件名,通过argv[1]指定,也就是命令行的第一个参数。后两个参数分别用于指定特定的输入格式(AVInputFormat)以及指定文件打开额外参数的AVDictionary结构,这里均留作NULL。
if( avformat_find_stream_info(pFormatCtx, NULL ) < 0 )
 
    return -1;
  av_dump_format(pFormatCtx, -1, argv[1], 0);
avformat_open_input函数只是读文件头,并不会填充流信息,因此我们需要接下来调用avformat_find_stream_info获取文件中的流信息,此函数会读取packet,并确定文件中所有的流信息,设置pFormatCtx->streams指向文件中的流,但此函数并不会改变文件指针,读取的packet会给后面的解码进行处理。
最后调用一个帮助函数av_dump_format,输出文件的信息,也就是我们在使用ffmpeg时能看到的文件详细信息。第二个参数指定输出哪条流的信息,-1表示给ffmpeg自己选择。最后一个参数用于指定dump的是不是输出文件,我们dump的是输入文件,因此一定要是0。
 
现在 pFormatCtx->streams 中已经有所有流了,因此现在我们遍历它找到第一条视频流:
videoStream = -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;
codec_type 的宏定义已经由以前的 CODEC_TYPE_VIDEO 改为 AVMEDIA_TYPE_VIDEO 了。接下来我们通过这条 video stream 的编解码信息打开相应的解码器:
 
pCodecCtx = pFormatCtx->streams[videoStream]->codec; 
 
 pCodec = avcodec_find_decoder(pCodecCtx->codec_id); 
 
if( pCodec == NULL ) 
 
    return -1; 
 
if( avcodec_open2(pCodecCtx, pCodec, NULL) < 0 ) 
 
     return -1;
 
分配图像缓存
接下来我们准备给即将解码的图片分配内存空间。
 
1pFrame = avcodec_alloc_frame();
2  if( pFrame == NULL )
3    return -1;
4  
5  pFrameRGB = avcodec_alloc_frame();
6  if( pFrameRGB == NULL )
7    return -1;
调用 avcodec_alloc_frame 分配帧,因为最后我们会将图像写成 24-bits RGB 的 PPM 文件,因此这里需要两个 AVFrame,pFrame用于存储解码后的数据,pFrameRGB用于存储转换后的数据:
 
1numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
2              pCodecCtx->height);
这里调用 avpicture_get_size,根据 pCodecCtx 中原始图像的宽高计算 RGB24 格式的图像需要占用的空间大小,这是为了之后给 pFrameRGB 分配空间:
 
1buffer = av_malloc(numBytes);
2  
3  avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
4          pCodecCtx->width, pCodecCtx->height);
接着上面的,首先是用 av_malloc 分配上面计算大小的内存空间,然后调用 avpicture_fill 将 pFrameRGB 跟 buffer 指向的内存关联起来。
 
获取图像
OK,一切准备好就可以开始从文件中读取视频帧并解码得到图像了。
 
01i = 0;
02while( av_read_frame(pFormatCtx, &packet) >= 0 ) {
03  if( packet.stream_index == videoStream ) {
04    avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
05 
06    if( frameFinished ) {
07  struct SwsContext *img_convert_ctx = NULL;
08  img_convert_ctx =
09    sws_getCachedContext(img_convert_ctx, pCodecCtx->width,
10                 pCodecCtx->height, pCodecCtx->pix_fmt,
11                 pCodecCtx->width, pCodecCtx->height,
12                 PIX_FMT_RGB24, SWS_BICUBIC,
13                 NULL, NULL, NULL);
14  if( !img_convert_ctx ) {
15    fprintf(stderr, "Cannot initialize sws conversion context\n");
16    exit(1);
17  }
18  sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data,
19        pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
20        pFrameRGB->linesize);
21  if( i++ < 50 )
22    SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
23    }
24  }
25  av_free_packet(&packet);
26}
av_read_frame 从文件中读取一个packet,对于视频来说一个packet里面包含一帧图像数据,音频可能包含多个帧(当音频帧长度固定时),读到这一帧后,如果是视频帧,则使用 avcodec_decode_video2 对packet中的帧进行解码,有时候解码器并不能从一个packet中解码得到一帧图像数据(比如在需要其他参考帧的情况下),因此会设置 frameFinished,如果已经得到下一帧图像则设置 frameFinished 非零,否则为零。所以这里我们判断 frameFinished 是否为零来确定 pFrame 中是否已经得到解码的图像。注意在每次处理完后需要调用 av_free_packet 释放读取的packet。
 
解码得到图像后,很有可能不是我们想要的 RGB24 格式,因此需要使用 swscale 来做转换,调用 sws_getCachedContext 得到转换上下文,使用 sws_scale 将图形从解码后的格式转换为 RGB24,最后将前50帧写人 ppm 文件。最后释放图像以及关闭文件:
 
01av_free(buffer);
02  av_free(pFrameRGB);
03  av_free(pFrame);
04  avcodec_close(pCodecCtx);
05  avformat_close_input(&pFormatCtx);
06  
07  return 0;
08}
09  
10static void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame)
11{
12  FILE *pFile;
13  char szFilename[32];
14  int y;
15  
16  sprintf(szFilename, "frame%d.ppm", iFrame);
17  pFile = fopen(szFilename, "wb");
18  if( !pFile )
19    return;
20  fprintf(pFile, "P6\n%d %d\n255\n", width, height);
21  
22  for( y = 0; y < height; y++ )
23    fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
24  
25  fclose(pFile);
26}

(转)引用---FFMPEG解码过程的更多相关文章

  1. AV时间戳dts,pts。从ffmpeg解码过程看过来。

    解码过程中,dts由媒体流读入的包推动(解码包中的dts标记),dts在前进.pts是在dts前进到某处(截点)而进行动作的标记. 物理时间自然流逝,dts可以被控制同步与物理时间同一脚步节奏,也可以 ...

  2. ffmpeg解码音视频过程(附代码)

    0. 引言 最近一直在使用和学习ffmpeg. 工作中需要拉流解码, 获取音频和视频数据. 这些都是使用ffmpeg处理. 因为对ffmpeg接触不多, 用的不深, 在使用的过程中经常遇到不太懂的地方 ...

  3. ffmpeg:编解码过程,基本用法

    1  术语: 什么是影片?其实就是一组(很多张)图片,时间间隔很小的连续展示出来,人们就觉得画面中的人物在动,这就是影片.那电影的实质就是N多张图片的集合.那 每张图片和帧又有什么关系呢?事实上,如果 ...

  4. FFmpeg解码H264及swscale缩放详解

    本文概要: 本文介绍著名开源音视频编解码库ffmpeg如何解码h264码流,比较详细阐述了其h264码流输入过程,解码原理,解码过程.同时,大部分应用环境下,以原始码流视频大小展示并不是最佳方式,因此 ...

  5. 最新FFMPEG解码流程

    FFMPEG解码流程: 1. 注册所有容器格式和CODEC:  av_register_all() 2. 打开文件:                    av_open_input_file() 3 ...

  6. 【图像处理】FFmpeg解码H264及swscale缩放详解

      http://blog.csdn.net/gubenpeiyuan/article/details/19548019 主题 FFmpeg 本文概要: 本文介绍著名开源音视频编解码库ffmpeg如何 ...

  7. FFMPEG解码流程

    FFMPEG解码流程:  1. 注册所有容器格式和CODEC: av_register_all()  2. 打开文件: av_open_input_file()  3. 从文件中提取流信息: av_f ...

  8. (转)FFMPEG解码流程

    http://www.douban.com/note/228831821/ FFMPEG解码流程:     1. 注册所有容器格式和CODEC: av_register_all()     2. 打开 ...

  9. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(一)用ffmpeg解码视频

    一.概述 myRTSPClient(RTSPClient)获取音视频数据之后,接下来的工作便是将音视频数据交给解码器去解码(ffmpeg),ffmpeg解码之后于是便有了呈现在终端用户(USER)面前 ...

随机推荐

  1. NUMA体系结构详解

    1. NUMA的几个概念(Node,socket,core,thread) 对于socket,core和thread会有不少文章介绍,这里简单说一下,具体参见下图: 一句话总结:socket就是主板上 ...

  2. 【转】在ASP.NET应用启动的时候初始化的几种方法

    ASP.NET 4.0 之前,有两种方法:通过Global.asax 中的 Application_Start 事件启动,或者通过定义在 App_Code 文件夹中任意类中的AppInitialize ...

  3. 了不起的 “filter(NULL IS NOT NULL)”

    经常会在执行计划中看到很奇怪的"FILTER"操作,然后看对应的执行信息是"filter(NULL IS NOT NULL)".  其实这是优化器非常聪明的“短 ...

  4. [na]锐起无盘机并发部署多台windows

    小时候很好奇这个技术,也并无卵,只是为了遂儿时愿, 用到锐起无盘部署,支持win7哦: 视频教程: 拓展:网吧电脑为什么没有硬盘? 网吧电脑没有硬盘是怎么运行的? 所需软件: https://yunp ...

  5. 【Android】6.3 ProgressDialog

    分类:C#.Android.VS2015: 创建日期:2016-02-08 一.简介 进度条对话框(ProgressDialog)常用于不能在短时间内快速完成的操作,显示进度条的目的是为了让用户明白程 ...

  6. 删除计算机里设备和驱动器中的爱奇艺、PPS、百度云、360云盘图标

    转自:http://jingyan.baidu.com/article/86f4a73e59bb3037d6526936.html 点击"开始"找到"运行" 输 ...

  7. angular学习笔记(十四)-$watch(4)

    如果需要同时监测多个属性或者对象,并且执行的是同样的回调,可以有两种选择: 1. 监测这些属性连接起来之后的值: $scope.$watch('objOne.a+objTwo.b+...', watc ...

  8. 每日英语:China Underwhelmed After First Apple Event

    Apple's roll-out of its latest iPhones landed with a thud in China, the company's biggest foreign ma ...

  9. 李洪强iOS开发之-FMDB的用法

    // //  ViewController.m //  04 - FMDB的用法 // //  Created by 李洪强 on 2017/6/6. //  Copyright © 2017年 李洪 ...

  10. mysql 5.7.15 安装配置方法图文教程(转)

    http://www.jb51.net/article/92521.htm ******************************* MySQL数据库作为关系型数据库中的佼佼者,因其体积小,速度 ...