[讀書心得] ffmpeg tutorial (一) Making Screencaps

Media files 種類分成 music, image, video.
每個 file 自身又稱為 container, 各自的格式如下:
  -music container : AIFF(mac), WAV(windows), XMF(extensible music format), etc.
  -image container : TIFF, FITS, etc.
  -video container : 3GP, AVI, ASF, Matroska, Quick Time, MPEG, MP4, RM, etc.

container type 決定了 儲存於 file 的資訊(這不廢話...)
container 由 streams (audio/video) 所構成, 每個 stream又可以分成數個 frames.
其中 streams 被各種 codec 以 encode 方式儲存於 container. 
codec 定義了 stream 如何被 enCOde 跟 DECode, 如 H.264, Xvid, MP3. 
不過在傳送時,  streams 是以數個 decoded 的 raw frames 為單位, 稱作 packet.

一個簡單的處理程序如下:
10 OPEN video_stream FROM video.avi
20 READ packet FROM video_stream INTO frame
30 IF frame NOT COMPLETE GOTO 20
40 DO SOMETHING WITH frame
50 GOTO 20
不過這裡有幾個疑問(Q1):
    (1) packet 是固定大小的 frames? 
    (2) 一個 image 由固定大小的 frames 組成? audio 又是怎麼傳輸?
         恩, 直覺上最簡單的做法是每次收 data frames 時先檢查大小. 
         25 READ list FOR X frames transfer
(先記著, 回頭再來check)

這裡的example 是"開一個 file, 將它的 stream 取出, 在  40 "DO SOMETHING" 中將 frame存到 PPM file 中"。

1. OPEN THE FILE
 #include 
 #include 
 ...
 int main(int argc, char *argv[]){
    av_register_all();
    一開始 call av_register_all() 註冊所要用的 format 跟 codec.
    ps. 也可以選擇性註冊, 若懂它們是什麼的話... (Q2)

  1.1 LOOK AT HEADER
AVFormatContext *fmt_ctx;
int ret, i;
if ((ret = av_open_input_file(&fmt_ctx, filename, NULL, 0, NULL)))
  return ret;
    av_open_input_file() 作用是將 file 的 header information 取出, 並依照 AVFormatContext structure 存入變數 fmt_ctx. 後面3個參數則是分別指定 file format, buffer size 跟 format options. 將其設為 NULL 因為稍後 libavformat 會自行找出.

  1.2 CHECK OUT STREAM INFORMATION
  if ((err = av_find_stream_info(fmt_ctx)) <0)
      return err;
  dump_format(fmt_ctx, 0, filename, 0);
    av_find_stream_info() 會將找到的 INFO 存於 fmt_ctx->streams 中.
    fmt_ctx->streams 是 array of pointers, 可以可以透過 fmt_ctx->nb_streams 得知長度.
 
  1.3 BIND A DECODER TO EACH INPUT STREAM
for (i=0 ; inb_streams ; i++) {
  AVStream *stream = fmt_ctx->streams[i];
  AVCodec *codec;
  if (!(codec = avcodec_find_decoder(stream->codec->codec_id)))
  {
    fprintf (stderr, "Unsupported codec (id=%d) for input stream %d\n", stream->codec->codec_id, stream->index);
  }else if (avcodec_open(stream->codec, codec) < 0) {
    fprintf (stderr, "Error while opening codec for input stream %d\n", stream->index);
}
  avcode_find_decoder() 會找出 AVcodec 中第一個 match 的 codec.
  avcodec_open() 將該 codec 記錄下來.

  1.4 CREATE STORING SPACE
  因為目標是將開啟的 file 輸出成 PPM file, 所以這裡需要產生兩個 frame space 跟
  一個 raw data space:
  pFrame = avcodec_alloc_frame()
  pFrameRGB = avcodec_alloc_frame()
  nBytes = avpicture_get_size(PIX_FMT_RGB24, pCoecCtx->width, pCodecCtx->height)
  buffer  = av_malloc(nBytes * sizeof(uint8_u))
  avcodec_alloc_frame(): pFrame 儲存自file 接收的 frame, pFrameRBG 則是儲存轉換後的 frame.
  avpicture_get size(): buffer 則是 儲存 raw data.
  avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCoecCtx->width, pCodecCtx->height)
  avpicture_fill() 這裡是說將 buffer 與 pFrameRGB 作 association (不懂?!)(Q3);
  另外 AVFrame 是 AVPicture 的 superset, 所以這裡用 casting.
  NOTE: 這裡又定義一個 Picture 指 decoded 後的 frame, 即 raw data.

  1.5 READING DATA
  Read 主要分成3個步驟: Read, Convert , and Save it.
int frameFinished;
AVPacket packet;
i=0;
while (av_read_frame(fmt_ctx, &packet)>0) {
  if (packet.stream_index==videoStream) {
  avcodec_decode_video (codec_ctx, pFrame, &frameFinished, packet.data, packet.size);
   if (frameFinished) {
      img_convert ((AVPicture*)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
      if (++i<=5)
       saveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
   }
 }
 av_free_packet(&packet);
}
  這段程式碼中,
  READ:
    av_read_frame() 是自fmt_ctx依序讀一個packet, 寫到AVPacket struct (所以內部的memory allocation 也是透過它而不用我們自己初始化).(所以一次只讀一個Packet? 一個packet有幾個stream是否固定? )(Q4)
  CONVERT:
    avcodec_decode_video() 依據先前找到的 codec 將 packet 轉為 frame. 但要確保有足夠的
資訊正確 decode packet, 需要一直作 READ 直到 frameFinished 被 set.
    img_convert() 將 native format (pCodecCtx->fmt) 轉為 RGB.
  SAVE:
    saveFrame() 是將 FrameRGB 寫到 DISK(存成.PPM). (但為什麼要寫五次?)(Q5)
    
(Q1)
(Q2)
(Q3)
(Q4)
(Q5)


WARNING: This tutorial is slightly out of date.


參考來源

Comments

Popular posts from this blog

股票評價(Stock Valuation) - 股利折現模型

openwrt feed的使用

How to convert Markdown into HTML