[3] ffmpeg基础知识以及使用示例

* 学习自雷神的博客和视频,图片来自他的课件,雷神博客:*
http://blog.csdn.net/leixiaohua1020/


日期:2016.10.1
作者:isshe
github:github.com/isshe
邮箱:[email protected]


ffmpeg的基础知识

ffmpeg的库

  • avdecoc: 编解码。
  • avformat: 封装格式的处理(flv,avi,mov)
  • swscale: 视频像素数据格式转换(常用于解码后视频的裁剪)。
  • avutil: 工具库。
  • avfilter: 滤镜特效处理。
  • avdevice: 各种设备的输入输出。
  • postproc: 后加工。
  • swresample: 音频采样数据格式转换。

前面四个是最常用的

ffmpeg的执行流程

[3] ffmpeg基础知识以及使用示例_第1张图片
* avcodec_decode_open2()这个函数是解码函数, 最主要的一个函数。
* 图中解码流程是:获取一个pakcet, 然后调用解码函数,把AVPacket结构中的data转换为AVFrame结构的data。
* AVPacket结构存储一帧压缩的编码数据。
* AVFrame结构存储一帧解码后的像素数据(对音频则是采样数据)
* AVFrame结构的元素data是双重指针,YUV数据来说包含data[0],data[1],data[2]分别存Y、U、V数据,注意每帧中U、V数据是Y数据的四分之一大小(对420P来说)。『Y:亮度数据, U,V:色差数据,由于人的眼睛对亮度更敏感,故而YUV数据中存更多的Y而减少UV的数据。当只有Y数据的时候,显示为黑白』
* 解码出来的数据可能函数无效像素。需要用sws_scale()函数处理(大概也可自编写函数处理)
* 如图:
* [3] ffmpeg基础知识以及使用示例_第2张图片
* ffmpeg的函数简介:待更新(不定期更新)

ffmpeg解码相关数据结构

[3] ffmpeg基础知识以及使用示例_第3张图片
* AVFormatContext是一个统筹全局的结构, 包含一些视频文件名,视频时长,视频码率等封装格式信息。
* AVInputFormat包含一些具体的视频格式信息,每种视频格式对应一个这个结构。
* 一般来说视频文件有两个流:视频流和音频流。有几个流就有几个AVStream数据结构, 一般视频流的index==0(也有其他情况), AVStream在AVFormatContext中是一个双重指针。
* AVCodecContext包含像素等编解码信息(对于视频)。
* AVCodec每种视/音频对应一个该结构体(例如h264).
* ffmpeg相关数据结构:待更新(不定期更新)

使用示例

代码:

  • 代码中打印相关结构信息的函数,删减了。
  • 函数的调用流程如前所述。
  • 需要注意,什么地方是什么数据。裸流数据在哪里输出,YUV数据在哪里输出。
  • 输出YUV数据要注意Y数据是UV的4倍,data有3个。
  • 找视频流的时候,需要一个一个流遍历,虽然一般index==0的那个是,但是也有例外。
  • *
#include 
//#include 

#define __STDC_CONSTANT_MACROS
extern "C"
{
#include 
#include 
#include      //裁剪
}

//输入和输出的文件名,解码前的数据放H264文件中,解码后放YUV文件中
#define FILENAME            "cuc_ieschool.flv"
#define FILENAME_YUV        "cuc_ieschool_512x288.yuv"
#define FILENAME_H264       "cuc_ieschool_512x288.H264"
#define FILENAME_INFO       "cuc_ieschool.info"

void print_AVFormatContext_info(AVFormatContext *pFormatCtx, FILE *file_stream);

int main(int argc, char *argv[])
{

     AVFormatContext    *pFormatCtx;        //统筹结构,保存封装格式相关信息
     AVCodecContext     *pCodecCtx;         //保存视/音频编解码相关信息
     AVCodec            *pCodec;            //每种编解码器对应一个结构体
     AVFrame            *pFrame;            //解码后的结构
     AVFrame            *pFrameYUV;
     AVPacket           *pPacket;           //解码前
     struct SwsContext  *img_convert_ctx;   //头文件只有一行,但是实际上这个结构体十分复杂
     int                i = 0;
     int                video_index = 0;     //为了检查哪个流是视频流,保存流数组下标
     int                y_size = 0;         //
     int                ret = 0;            //
     int                got_picture = 0;    //
     char               filename[256] = FILENAME;  //
     int                frame_cnt;          //帧数计算
     uint8_t            *out_buffer;        //???
     FILE               *fp = NULL;
     FILE               *YUVfp = NULL;

     if (argc == 2)
     {
          memcpy(filename, argv[1], strlen(argv[1])+1);
     }
     else if (argc > 2)
     {
         printf("Usage: ./*.out video_filename");
     }

     av_register_all();         //注册所有组件

     avformat_network_init();   //初始化网络,貌似暂时没用到,可以试试删除

     pFormatCtx = avformat_alloc_context(); //分配内存

     //打开文件, 注意第一个参数是指针的指针
     if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0)
     {
          printf("Couldn't open input stream.\n");
          return (-1);
     }

     //获取流信息
     if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
     {
          printf("Couldn't find stream info\n");
          return (-1);
     }

     //找出视频流, nb_streams表示视频中有几种流
     video_index = -1;
     for (i = 0; i < pFormatCtx->nb_streams; i++)
     {
          if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
          {
              video_index = i;
              break;
          }
     }
     if (-1 == video_index)
     {
          printf("Couldn't find a video stream\n");
          return (-1);
     }

     //复制编解码的信息到编解码的结构AVCodecContext结构中,
     //一方面为了操作结构中数据方便(不需要每次都从AVFormatContext结构开始一个一个指向)
     //另一方面方便函数的调用
     pCodecCtx = pFormatCtx->streams[video_index]->codec;    //可以查看源码avformat.h中定义的结构

     //找到一个和视频流对应的解码器
     pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
     if (NULL == pCodec)
     {
          printf("Couldn't found a decoder\n");
          return (-1);
     }

     //打开解码器
     if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
     {
          printf("Couldn't open decoder\n");
          return (-1);
     }

     //输出一些想要输出的信息
     fp = stdout;
     fp = fopen(FILENAME_INFO, "w+");
     if (fp == NULL)
     {
         fprintf(stderr, "fopen error\n");
         return (-1);
     }

     //输出结构中的信息以更了解这些结构。这里代码只示例一个。
     print_AVFormatContext_info(pFormatCtx, fp);

     //输出视频文件信息
     printf("------------------------file infomation-----------------------------\n");
     av_dump_format(pFormatCtx, 0, filename, 0);
     printf("--------------------------------------------------------------------\n");

     pFrame     = av_frame_alloc();
     pFrameYUV  = av_frame_alloc();
     out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
     avpicture_fill((AVPicture *)pFrameYUV, out_buffer,
             AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

     //???
     img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
             pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
             AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

     pPacket = (AVPacket *)av_malloc(sizeof(AVPacket));
     frame_cnt = 0;         //帧数计算吗?

     //打开文件
     fp = fopen(FILENAME_H264, "w+");
     if (fp == NULL)
     {
         fprintf(stderr, "h264 fopen error\n");
         return (-1);
     }

     YUVfp = fopen(FILENAME_YUV, "w+");
     if (NULL == YUVfp)
     {
         fprintf(stderr, "YUV fopen error\n");
         return (-1);
     }

     while(av_read_frame(pFormatCtx, pPacket) >= 0)
     {
          if (pPacket->stream_index == video_index)
          {
              //这里可以输出H264马流信息,这里是解码前
              fwrite(pPacket->data, pPacket->size, 1, fp);

              //解码
              ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, pPacket);
              if (ret < 0)
              {
                   printf("Decode fail\n");
                   return (-1);
              }

              if (got_picture == 0)
              {
                   printf("get picture error\n");
              }
              else
              {
                  //调整解码出来的图像,解码出来的可能含有填充的信息。
                  sws_scale(img_convert_ctx, (const uint8_t * const *)pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height,
                          pFrameYUV->data, pFrameYUV->linesize);
//                  printf("Decoded frame index: %d\n", frame_cnt);

                  // 已经解码,可以输出YUV的信息
                  // 这个data是一个数组,需要注意!一般3个:代表Y、U、V
                  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                  fwrite(pFrameYUV->data[0], 1, pCodecCtx->width * pCodecCtx->height, YUVfp);
                  fwrite(pFrameYUV->data[1], 1, pCodecCtx->width * pCodecCtx->height / 4, YUVfp);
                  fwrite(pFrameYUV->data[2], 1, pCodecCtx->width * pCodecCtx->height / 4, YUVfp);
                  frame_cnt++;
              }
          }
          av_free_packet(pPacket);
     }

     printf("Decoded frame count: %d\n", frame_cnt);

     fclose(fp);
     fclose(YUVfp);
     sws_freeContext(img_convert_ctx);
     av_frame_free(&pFrameYUV);
     av_frame_free(&pFrame);
     avcodec_close(pCodecCtx);
     avformat_close_input(&pFormatCtx);

     return 0;
}

void print_AVFormatContext_info(AVFormatContext *pFormatCtx, FILE *file_stream)
{
     fprintf(file_stream, "-------------------------AVFormatContext--------------------------\n");
     fprintf(file_stream, "ctx_flags = %d\n"
             "nb_streams = %u\n"
             "filename = %s\n"
             "start_time = %d\n"
             "duration = %d\n"
             "bit_rate = %d\n"
             "packet_size = %u\n"
             "max_delay = %d\n"
             "flags = %d\n"
             "probesize = %d\n"
             "key = %d\n"
             "keylen = %d\n"
             "nb_programs = %d\n",
             pFormatCtx->ctx_flags, pFormatCtx->nb_streams,
             pFormatCtx->filename, pFormatCtx->start_time,
             pFormatCtx->duration, pFormatCtx->bit_rate,
             pFormatCtx->packet_size, pFormatCtx->max_delay,
             pFormatCtx->flags, pFormatCtx->probesize,
             pFormatCtx->key, pFormatCtx->keylen, pFormatCtx->nb_programs);
     fprintf(file_stream, "------------------------------------------------------------------\n");
}

编译

  • 这个要注意,ubuntu下弄了好久。
  • 这里要用g++, 因为用到的库x265中,用gcc编不过。
  • 这份代码有很多警告,暂未处理。
g++ ffmpeg_decoder.c -o ffmpeg_decoder.out -O2 -Wall -g \
 -lavformat -lavcodec -lavformat -lavutil -lswresample -lswscale \
 -lx264 -lx265 -lvpx -lmp3lame -lopus -lfdk-aac -lX11 -lva -lvdpau -lva-drm \
 -lva-x11 -lvorbisenc -lvorbis -ltheoraenc -ltheoradec -ldl -lm -lpthread -lz
  • ubuntu16.04中需要包含这个库才能不出现“未定义的引用”。

结果

相关结构的信息存到.info文件,裸流数据存到h264文件,解码后数据存yuv文件。

参考资料

  • 雷神的视频,课件。
    博客:http://blog.csdn.net/leixiaohua1020/article/details/15811977/
    视频:http://blog.csdn.net/leixiaohua1020/article/details/47068015

代码下载

http://download.csdn.net/detail/i_scream_/9644386

你可能感兴趣的:(「初探」ffmpeg)