zoukankan      html  css  js  c++  java
  • ffmpeg示例一:how to use libavformat and libavcodec to read video from a file.

     to write the first five frames from "myvideofile.mpg" to disk in PPM format.

    首先简单介绍以下视频文件的相关知识。我们平时看到的视频文件有许多格式,比如 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;  //typedef unsigned char uint8_t;

    ·         AVFormatContext:保存需要读入的文件的格式信息,比如流的个数以及流数据等

    ·         AVCodecCotext:保存了相应流的详细编码信息,比如视频的宽、高,编码类型等。

    ·         pCodec:真正的编解码器,其中有编解码需要调用的函数

    ·         AVFrame:用于保存数据帧的数据结构,这里的两个帧分别是保存颜色转换前后的两帧图像

    ·         AVPacket:解析文件时会将音/视频帧读入到packet中

    打开文件

    接下来我们打开一个视频文件。

    av_register_all();

     

    av_register_all 定义在 libavformat.h 里,调用它用以注册所有支持的文件格式以及编解码器,从其实现代码里可以看到它会调用 avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。

    if( avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0 )

        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;

    分配图像缓存

    接下来我们准备给即将解码的图片分配内存空间。

    pFrame = avcodec_alloc_frame();

      if( pFrame == NULL )

        return -1;

      pFrameRGB = avcodec_alloc_frame();

      if( pFrameRGB == NULL )

    return -1;

    调用 avcodec_alloc_frame 分配帧,因为最后我们会将图像写成 24-bits RGB 的 PPM 文件,因此这里需要两个 AVFrame,pFrame用于存储解码前的数据,pFrameRGB用于存储转换后的数据

    numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,

               pCodecCtx->height);

    这里调用 avpicture_get_size,根据 pCodecCtx 中原始图像的宽高计算 RGB24 格式的图像需要占用的空间大小,这是为了之后给 pFrameRGB 分配空间:

     

     

    buffer = av_malloc(numBytes);

     

      avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,

                         pCodecCtx->width, pCodecCtx->height);

     

     

    接着上面的,首先是用 av_malloc 分配上面计算大小的内存空间,然后调用 avpicture_fill 将 pFrameRGB 跟 buffer 指向的内存关联起来。

    获取图像

    OK,一切准备好就可以开始从文件中读取视频帧并解码得到图像了。

    i = 0;

     while( av_read_frame(pFormatCtx, &packet) >= 0 ) {

       if( packet.stream_index == videoStream ) {

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

     

         if( frameFinished ) {

    struct SwsContext *img_convert_ctx = NULL;

    img_convert_ctx =

      sws_getCachedContext(img_convert_ctx, pCodecCtx->width,

                              pCodecCtx->height, pCodecCtx->pix_fmt,

                              pCodecCtx->width, pCodecCtx->height,

                              PIX_FMT_RGB24, SWS_BICUBIC,

                              NULL, NULL, NULL);

    if( !img_convert_ctx ) {

      fprintf(stderr, "Cannot initialize sws conversion context\n");

      exit(1);

    }

    sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data,

               pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,

               pFrameRGB->linesize);

    if( i++ < 50 )

      SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);

         }

       }

       av_free_packet(&packet);

     }

    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 文件。最后释放图像以及关闭文件:

    av_free(buffer);

      av_free(pFrameRGB);

      av_free(pFrame);

      avcodec_close(pCodecCtx);

      avformat_close_input(&pFormatCtx);

     

      return 0;

    }

     

    static void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame)

    {

      FILE *pFile;

      char szFilename[32];

      int y;

     

      sprintf(szFilename, "frame%d.ppm", iFrame);

      pFile = fopen(szFilename, "wb");

      if( !pFile )

        return;

      fprintf(pFile, "P6\n%d %d\n255\n", width, height);

     

      for( y = 0; y < height; y++ )

        fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);

     

      fclose(pFile);

    }

     

    总结:

    av_register_all();//初始化ffmpeg库,如果系统里面的ffmpeg没配置好这里会出错 avformat_open_input(); avformat_find_stream_info();//查找文件的流信息 av_dump_format();//dump只是个调试函数,输出文件的音、视频流的基本信息了,帧率、分辨率、音频采样等等 for(...);//遍历文件的各个流,找到第一个视频流,并记录该流的编码信息 sws_getContext();//根据编码信息设置渲染格式 avcodec_find_decoder();//在库里面查找支持该格式的解码器 avcodec_open2();//打开解码器 pFrame=avcodec_alloc_frame();//分配一个帧指针,指向解码后的原始帧 pFrameRGB=avcodec_alloc_frame();//分配一个帧指针,指向存放转换成RGB后的帧 avpicture_fill(pFrameRGB);//给pFrameRGB帧加上分配的内存; while true{      av_read_frame();//读取一个帧(到最后帧则break)      avcodec_decode_video2();//解码该帧      sws_getCachedContext()sws_scale();//把该帧转换(渲染)成RGB      SaveFrame();//对前5帧保存成ppm图形文件(这个是自定义函数,非API)      av_free_packet();//释放本次读取的帧内存 }

      av_free(buffer);   av_free(pFrameRGB);   av_free(pFrame);   avcodec_close(pCodecCtx);   avformat_close_input(&pFormatCtx);

     libavcodec:CODEC其实是Coder/Decoder的缩写,也就是编码解码器;

    libavdevice:对输出输入设备的支持;

    libavformat:对音频视频格式的解析

    libavutil:集项工具;

    libpostproc:后期效果处理;

    libswscale:视频场景比例缩放、色彩映射转换;

     

     

     

    附:

    找不到 stdint.h

    添加MSYS\mingw\include

    C不支持inline,应该改为__inline

    __restrict__改为__restrict

    连接错误:添加lib库即可

    本文源码见http://www.cnblogs.com/elesos/archive/2013/03/27/2984570.html

  • 相关阅读:
    封装ajax---基于axios
    XHR的理解和作用
    params和 query区别
    HTTP请求交互的基本过程
    http3次握手
    ES6----import * as 用法
    微信小程序真机调试:Setting data field "XXX" to undefined is invalid
    webpack详解-----optimization
    node跨域
    shell 的 功能语句--1
  • 原文地址:https://www.cnblogs.com/elesos/p/2949478.html
Copyright © 2011-2022 走看看