本文轉載
視頻播放過程
首先簡單介紹以下視頻文件的相關知識。我們平時看到的視頻文件有許多格式,比如 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中
打開文件
接下來我們打開一個視頻文件。
| 
                  1
                 | 
                  av_register_all();
                 | 
av_register_all 定義在 libavformat 里,調用它用以注冊所有支持的文件格式以及編解碼器,從其 實現代碼 里可以看到它會調用 avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。
| 
                  1
                 | 
                  if
                
                  ( 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 ;
    
  
分配圖像緩存
接下來我們準備給即將解碼的圖片分配內存空間。
| 
                  1
                 | 
                  pFrame = 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用于存儲轉換后的數據:
| 
                  1
                 | 
                  numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
                 | 
| 
                  2
                 | 
                  ??????????????
                
                  pCodecCtx->height);
                 | 
這里調用 avpicture_get_size ,根據 pCodecCtx 中原始圖像的寬高計算 RGB24 格式的圖像需要占用的空間大小,這是為了之后給 pFrameRGB 分配空間:
| 
                  1
                 | 
                  buffer = 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,一切準備好就可以開始從文件中讀取視頻幀并解碼得到圖像了。
| 
                  01
                 | 
                  i = 0;
                 | 
| 
                  02
                 | 
                  while
                
                  ( 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 文件。最后釋放圖像以及關閉文件:
| 
                  01
                 | 
                  av_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
                 | 
                  ?
                ? | 
| 
                  10
                 | 
                  static
                
                  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
                 | 
                  }
                 | 
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
 
					微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
 
					

