1. 程式人生 > >[3] ffmpeg基礎知識以及使用示例

[3] ffmpeg基礎知識以及使用示例

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

ffmpeg的基礎知識

ffmpeg的庫

  • avdecoc: 編解碼。
  • avformat: 封裝格式的處理(flv,avi,mov)
  • swscale: 視訊畫素資料格式轉換(常用於解碼後視訊的裁剪)。
  • avutil: 工具庫。
  • avfilter: 濾鏡特效處理。
  • avdevice: 各種裝置的輸入輸出。
  • postproc: 後加工。
  • swresample: 音訊取樣資料格式轉換。

前面四個是最常用的

ffmpeg的執行流程

這裡寫圖片描述
* 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()函式處理(大概也可自編寫函式處理)
* 如圖:
* 這裡寫圖片描述


* ffmpeg的函式簡介:待更新(不定期更新)

ffmpeg解碼相關資料結構

這裡寫圖片描述
* AVFormatContext是一個統籌全域性的結構, 包含一些視訊檔名,視訊時長,視訊位元速率等封裝格式資訊。
* AVInputFormat包含一些具體的視訊格式資訊,每種視訊格式對應一個這個結構。
* 一般來說視訊檔案有兩個流:視訊流和音訊流。有幾個流就有幾個AVStream資料結構, 一般視訊流的index==0(也有其他情況), AVStream在AVFormatContext中是一個雙重指標。
* AVCodecContext包含畫素等編解碼資訊(對於視訊)。
* AVCodec每種視/音訊對應一個該結構體(例如h264).
* ffmpeg相關資料結構:待更新(不定期更新)

使用示例

程式碼:

  • 程式碼中列印相關結構資訊的函式,刪減了。
  • 函式的呼叫流程如前所述。
  • 需要注意,什麼地方是什麼資料。裸流資料在哪裡輸出,YUV資料在哪裡輸出。
  • 輸出YUV資料要注意Y資料是UV的4倍,data有3個。
  • 找視訊流的時候,需要一個一個流遍歷,雖然一般index==0的那個是,但是也有例外。
  • *
#include <stdio.h>
//#include <libavformat/avformat.h>

#define __STDC_CONSTANT_MACROS
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>     //裁剪
}

//輸入和輸出的檔名,解碼前的資料放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檔案。

參考資料

程式碼下載