使用ffmpeg進行視頻解碼以及圖像轉換

ffmpeg作爲一個支持非常多視頻、音頻格式的開源項目,其應用灰常廣泛。今兒在這我們就探討一下讀者對其的理解,其中不泛錯誤謬誤,望各位大大批評指教。這樣做的原因主要是官方的文檔比較匱乏。經過無數摸索,有一些經驗分享。

1、Overview

ffmpeg裏有幾個重要的概念,熟悉它們以後,事情就變得簡單多了。

avformat

AVFormatContext代表一個打開的文件或者別的媒體,總之可以說代表數據的來源。視頻和音頻是按照一定格式存儲在文件中的。這種格式僅僅指定如何把視頻和音頻流區分開來,至於視頻如何變成圖像,那是解碼。平常所說的AVI格式,也就是上面所說的格式,裏面視頻、音頻的編碼方式還是可以隨意的。

ffmpeg中的AVFormat庫可以幫助進行這一“分拆音視頻流”的過程;而AVCodec則幫助解碼視頻。

2、解碼視頻

(1)打開文件

1
2
3
4
5
6
7
8
9
10
AVInputFormat   *inputFmt;
AVFormatContext *fmtCtx = NULL;
 
inputFmt = av_find_input_format("avi"); /* 打開“AVI”的輸入格式 */
if (inputFmt == NULL) { 
  /* Error processing */ 
}
if (av_open_input_file(&fmtCtx,"/test/test.avi", inputFmt, 0, NULL) != 0) {
  /* Error processing */ 
}

這裏爲了方便說明,先暫時假設輸入的文件是AVI格式的。在很多情況下,我們都不知道文件是什麼格式的,FFMPEG提供了探測的方法,這將在下文再提到。

(2)尋找解碼器(視頻)

1
2
3
4
5
6
7
8
9
10
11
12
int i, found_stream_index;
AVCodecContext  *videoDecodeCtx = NULL;
for (i=0;i<fmtCtx->nb_streams;i++){
   if (fmtCtx->streams[i]->codec->codec_type == AVMEDIA_VIDEO){
       videoDecodeCtx = fmtCtx->streams[i]->codec;
       found_stream_index = i;
       break;
   }
}
if (decodeCtx == NULL) {
   /* 找不到視頻流,錯誤處理 */
}

當一個AVFormatContext成功打開後,在其結構內就會存着該文件內包含的所有流(視頻流、音頻流等),每個流(AVStream)都會自動打開一個解碼器上下文(AVCodecContext),即提供給解碼器的參數,它並非真正的解碼器,只是解碼器參數!
以上代碼中在文件所含的所有流中尋找視頻流並得到一個解碼器上下文。

(3)打開解碼器

1
2
3
4
5
6
7
8
9
10
11
12
AVCodec   *videoDecoder;
videoDecoder = avcodec_find_encoder(videoDecodeCtx->codec_id);
if (videoDecoder == NULL){
   /* 找不到解碼器,錯誤處理 */
}
if (videoDecoder->capabilities & CODEC_CAP_TRUNCATED){
   videoDecodeCtx->flags |= CODEC_FLAG_TRUNCATED;
}
 
if (avcodec_open(videoDecodeCtx,videoDecoder) < 0){
  /* 打不開解碼器,錯誤處理 */
}

這一步真正地去尋找一個解碼器,並使用之前獲得的參數打開它。並非任何編碼都會被支持,並非任何參數都會被解碼器都會被支持,所以一定要進行錯誤處理哦。
CODEC_CAP_TRUNCATED指明解碼器可以支持所謂“碎片輸入”,先不管它,等會兒再說。
如果都成功了,那麼解碼器就成功打開了。接下來就可以開始解碼了。

(4)解碼視頻

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
AVPacket pkt;
AVFrame *frame = avcodec_alloc_frame();
int got_picture = 0, rc = 0;
 
while (rc == 0){
  av_init_packet(&pkt);
  rc = av_read_packet(fmtCtx,&pkt); /* 獲取一個包的數據 */
  if (rc != 0) break;
  if (pkt.stream_index != found_stream_index) 
     goto free_pkt_continue; /* 不是所關心的視頻流的數據,捨去 */
  if ( avcodec_decode_video2 (videoDecodeCtx, frame, &got_picture, &pkt) < 0) {
     /* 核心函數:解碼。錯誤處理。 */
  }
 
  if (got_picture) {
    {  /* 處理獲得的圖像(存儲在AVFrame裏) */ }
    av_free(frame);
    frame = avcodec_alloc_frame();
  }
 
free_pkt_continue:
  av_free_packet(&pkt);
}

其實這個過程很簡單:
1. 讀取數據;
2. 解碼。
原始數據是以一個個AVPacket的形式存放的,而解出來的一幀圖像存在AVFrame裏。一幀圖像可以由很多AVPacket組成,所以使用一個got_picture指針來指示是否獲得一幀圖像。
之前說的是關於CAP_TRUNCATED的問題,就是表明解碼器是否支持AVFrame的邊界和AVPacket的邊界不重合。數據應該可以是零散的,解碼器的這個能力很重要,它可以處理來自數據中的任一段(對於網絡數據很有用)。

3、圖像轉換

這裏說的圖像轉換,並非類似PNG到JPG的轉換,而主要是色彩空間和大小伸縮的轉換。例如

Rating: 9.2/10 (5 votes cast)
Rating: +2 (from 2 votes)

Related posts:

  1. 用boost::shared_ptr實現COW 在boost中,shared_ptr作爲智能指針的模塊類,可以爲程序提供基於引用計數的內存管理功能。用智能指針的優點在於:當託管的對象不再被需要時,可以自動回收內存。以上的話太晦澀了,我想還是一個例子比較好。 例如在某函數內用malloc或者new申請了一片內存區用於存儲網絡接收到的數據,然後返回將該指針給函數的調用者。調用者可能又將這個指針交給了另一個線程進行數據處理,甚至交給第二進線程進行併發處理。這樣一來,你就不知道什麼時候該收回這塊內存了(i.e. 不知道何時該調用free或者delete)。如果不小心在這個線程刪除了而另一個線程還在使用,那你完蛋了!老闆該扣你工資啦! shared_ptr的作用就是每複製一次shared_ptr,就會增加內部的“引用計數”——即它保護的內存區域指針被引用了多少次,當一個shared_ptr被刪除(經常叫做析構,局部變量會在函數調用返回時析構/銷燬)時,“引用計數”就會減一,當引用計數減爲0時,說明這個指針不再被誰需要,它指向的內存區域就會被真正地釋放掉。 1、實現一個ByteArray 顧名思義,ByteArray就是管理一片內存區域。有人問爲什麼不直接使用 char *buf,請返回看以上章節。有人還可以說,我在每個函數內(Stack內),都聲明固定長度的數據區域,每次調用都複製一次即可。暫且不說固定長度的不靈活性和每次複製的時間損耗,若你存儲的是幾MB、幾十MB甚至上G的數據,你也敢在每次複製嗎? 1 2...

以上關聯文章由 Yet Another Related Posts Plugin 提供支持。

已經發布的版本:

變化:

2011 年 1 月 7 日 23:41:55 Current Revision
Content
  ffmpeg作爲一個支持非常多視頻、音頻格式的開源項目,其應用灰常廣泛。今兒在這我們就探討一下讀者對其的理解,其中不泛錯誤謬誤,望各位大大批評指教。這樣做的原因主要是官方的文檔比較匱乏。經過無數摸索,有一些經驗分享。   ffmpeg作爲一個支持非常多視頻、音頻格式的開源項目,其應用灰常廣泛。今兒在這我們就探討一下讀者對其的理解,其中不泛錯誤謬誤,望各位大大批評指教。這樣做的原因主要是官方的文檔比較匱乏。經過無數摸索,有一些經驗分享。
  <h1>1、Overview</h1>   <h1>1、Overview</h1>
  ffmpeg裏有幾個重要的概念,熟悉它們以後,事情就變得簡單多了。   ffmpeg裏有幾個重要的概念,熟悉它們以後,事情就變得簡單多了。
  <a href="http:// blog.simophin.net/wp-content/ uploads/2011/ 01/avformat.png"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: block; float: none; padding-top: 0px; border-width: 0px;" title="avformat" src="http://blog.simophin.net/ wp-content/uploads/2011/01/ avformat_thumb.png" border="0" alt="avformat" width="516" height="124" /></a>   <a href="http:// blog.simophin.net/wp-content/ uploads/2011/ 01/avformat.png"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: block; float: none; padding-top: 0px; border-width: 0px;" title="avformat" src="http://blog.simophin.net/ wp-content/uploads/2011/01/ avformat_thumb.png" border="0" alt="avformat" width="516" height="124" /></a>
  AVFormatContext代表一個打開的文件或者別的媒體,總之可以說代表數據的來源。視頻和音頻是按照一定格式存儲在文件中的。這種< strong>格式< /strong>僅僅指定如何把視頻和音頻流區分開來,至於視頻如何變成圖像,那是< strong>解碼< /strong>。平常所說的AVI格式,也就是上面所說的格式,裏面視頻、音頻的編碼方式還是可以隨意的。   AVFormatContext代表一個打開的文件或者別的媒體,總之可以說代表數據的來源。視頻和音頻是按照一定格式存儲在文件中的。這種< strong>格式< /strong>僅僅指定如何把視頻和音頻流區分開來,至於視頻如何變成圖像,那是< strong>解碼< /strong>。平常所說的AVI格式,也就是上面所說的格式,裏面視頻、音頻的編碼方式還是可以隨意的。
  + <!--more-->
  ffmpeg中的AVFormat庫可以幫助進行這一“分拆音視頻流”的過程;而AVCodec則幫助解碼視頻。   ffmpeg中的AVFormat庫可以幫助進行這一“分拆音視頻流”的過程;而AVCodec則幫助解碼視頻。
  <h1>2、解碼視頻</h1>   <h1>2、解碼視頻</h1>
  <h2>(1)打開文件</h2>   <h2>(1)打開文件</h2>
  <pre lang="C" line="1">   <pre lang="C" line="1">
  AVInputFormat *inputFmt;   AVInputFormat *inputFmt;
  AVFormatContext *fmtCtx = NULL;   AVFormatContext *fmtCtx = NULL;
  inputFmt = av_find_input_ format("avi"); /* 打開“AVI”的輸入格式 */   inputFmt = av_find_input_ format("avi"); /* 打開“AVI”的輸入格式 */
  if (inputFmt == NULL) {   if (inputFmt == NULL) {
  /* Error processing */   /* Error processing */
  }   }
  if (av_open_input_ file(&fmtCtx," /test/test.avi", inputFmt, 0, NULL) != 0) {   if (av_open_input_ file(&fmtCtx," /test/test.avi", inputFmt, 0, NULL) != 0) {
  /* Error processing */   /* Error processing */
  }   }
  </pre>   </pre>
  這裏爲了方便說明,先暫時假設輸入的文件是AVI格式的。在很多情況下,我們都不知道文件是什麼格式的,FFMPEG提供了探測的方法,這將在下文再提到。   這裏爲了方便說明,先暫時假設輸入的文件是AVI格式的。在很多情況下,我們都不知道文件是什麼格式的,FFMPEG提供了探測的方法,這將在下文再提到。
  <h2>(2)尋找解碼器(視頻)</h2>   <h2>(2)尋找解碼器(視頻)</h2>
  <pre lang="C" line="1">   <pre lang="C" line="1">
  int i, found_stream_index;   int i, found_stream_index;
  AVCodecContext *videoDecodeCtx = NULL;   AVCodecContext *videoDecodeCtx = NULL;
  for (i=0;i<fmtCtx- >nb_streams;i++){   for (i=0;i<fmtCtx- >nb_streams;i++){
  if (fmtCtx->streams[i]- >codec->codec_type == AVMEDIA_VIDEO){   if (fmtCtx->streams[i]- >codec->codec_type == AVMEDIA_VIDEO){
  videoDecodeCtx = fmtCtx->streams[i]->codec;   videoDecodeCtx = fmtCtx->streams[i]->codec;
  found_stream_index = i;   found_stream_index = i;
  break;   break;
  }   }
  }   }
  if (decodeCtx == NULL) {   if (decodeCtx == NULL) {
  /* 找不到視頻流,錯誤處理 */   /* 找不到視頻流,錯誤處理 */
  }   }
  </pre>   </pre>
  當一個AVFormatContext成功打開後,在其結構內就會存着該文件內包含的所有流(視頻流、音頻流等),每個流(AVStream)都會自動打開一個解碼器上下文(AVCodecContext),即提供給解碼器的參數,< b>它並非真正的解碼器,只是解碼器參數!</b>   當一個AVFormatContext成功打開後,在其結構內就會存着該文件內包含的所有流(視頻流、音頻流等),每個流(AVStream)都會自動打開一個解碼器上下文(AVCodecContext),即提供給解碼器的參數,< b>它並非真正的解碼器,只是解碼器參數!</b>
  以上代碼中在文件所含的所有流中尋找視頻流並得到一個解碼器上下文。   以上代碼中在文件所含的所有流中尋找視頻流並得到一個解碼器上下文。
  <h2>(3)打開解碼器</h2>   <h2>(3)打開解碼器</h2>
  <pre lang="C" line="1">   <pre lang="C" line="1">
  AVCodec *videoDecoder;   AVCodec *videoDecoder;
  videoDecoder = avcodec_find_ encoder(videoDecodeCtx->codec_id);   videoDecoder = avcodec_find_ encoder(videoDecodeCtx->codec_id);
  if (videoDecoder == NULL){   if (videoDecoder == NULL){
  /* 找不到解碼器,錯誤處理 */   /* 找不到解碼器,錯誤處理 */
  }   }
  if (videoDecoder- >capabilities & CODEC_CAP_TRUNCATED){   if (videoDecoder- >capabilities & CODEC_CAP_TRUNCATED){
  videoDecodeCtx->flags |= CODEC_FLAG_TRUNCATED;   videoDecodeCtx->flags |= CODEC_FLAG_TRUNCATED;
  }   }
  if (avcodec_open( videoDecodeCtx,videoDecoder) < 0){   if (avcodec_open( videoDecodeCtx,videoDecoder) < 0){
  /* 打不開解碼器,錯誤處理 */   /* 打不開解碼器,錯誤處理 */
  }   }
  </pre>   </pre>
  這一步真正地去尋找一個解碼器,並使用之前獲得的參數打開它。並非任何編碼都會被支持,並非任何參數都會被解碼器都會被支持,所以一定要進行錯誤處理哦。   這一步真正地去尋找一個解碼器,並使用之前獲得的參數打開它。並非任何編碼都會被支持,並非任何參數都會被解碼器都會被支持,所以一定要進行錯誤處理哦。
  CODEC_CAP_TRUNCATED指明解碼器可以支持所謂“碎片輸入”,先不管它,等會兒再說。   CODEC_CAP_TRUNCATED指明解碼器可以支持所謂“碎片輸入”,先不管它,等會兒再說。
  如果都成功了,那麼解碼器就成功打開了。接下來就可以開始解碼了。   如果都成功了,那麼解碼器就成功打開了。接下來就可以開始解碼了。
  <h2>(4)解碼視頻</h2>   <h2>(4)解碼視頻</h2>
  <pre lang="C" line="1">   <pre lang="C" line="1">
  AVPacket pkt;   AVPacket pkt;
  AVFrame *frame = avcodec_alloc_frame();   AVFrame *frame = avcodec_alloc_frame();
  int got_picture = 0, rc = 0;   int got_picture = 0, rc = 0;
- while (rc == 0){ + while (1){
  av_init_packet(&pkt);   av_init_packet(&pkt);
  rc = av_read_packet( fmtCtx,&pkt); /* 獲取一個包的數據 */   rc = av_read_packet( fmtCtx,&pkt); /* 獲取一個包的數據 */
  if (rc != 0) break;   if (rc != 0) break;
  if (pkt.stream_index != found_stream_index)   if (pkt.stream_index != found_stream_index)
  goto free_pkt_continue; /* 不是所關心的視頻流的數據,捨去 */   goto free_pkt_continue; /* 不是所關心的視頻流的數據,捨去 */
  if ( avcodec_decode_video2 (videoDecodeCtx, frame, &got_picture, &pkt) < 0) {   if ( avcodec_decode_video2 (videoDecodeCtx, frame, &got_picture, &pkt) < 0) {
  /* 核心函數:解碼。錯誤處理。 */   /* 核心函數:解碼。錯誤處理。 */
  }   }
       
  if (got_picture) {   if (got_picture) {
  { /* 處理獲得的圖像( 存儲在AVFrame裏) */ }   { /* 處理獲得的圖像( 存儲在AVFrame裏) */ }
  av_free(frame);   av_free(frame);
  frame = avcodec_alloc_frame();   frame = avcodec_alloc_frame();
  }   }
       
  free_pkt_continue:   free_pkt_continue:
  av_free_packet(&pkt);   av_free_packet(&pkt);
  }   }
  </pre>   </pre>
  其實這個過程很簡單:   其實這個過程很簡單:
  1. 讀取數據;   1. 讀取數據;
  2. 解碼。   2. 解碼。
  原始數據是以一個個AVPacket的形式存放的,而解出來的一幀圖像存在AVFrame裏。一幀圖像可以由很多AVPacket組成,所以使用一個got_ picture指針來指示是否獲得一幀圖像。   原始數據是以一個個AVPacket的形式存放的,而解出來的一幀圖像存在AVFrame裏。一幀圖像可以由很多AVPacket組成,所以使用一個got_ picture指針來指示是否獲得一幀圖像。
  之前說的是關於CAP_ TRUNCATED的問題,就是表明解碼器是否支持AVFrame的邊界和AVPacket的邊界不重合。數據應該可以是零散的,解碼器的這個能力很重要,它可以處理來自數據中的任一段(對於網絡數據很有用)。   之前說的是關於CAP_ TRUNCATED的問題,就是表明解碼器是否支持AVFrame的邊界和AVPacket的邊界不重合。數據應該可以是零散的,解碼器的這個能力很重要,它可以處理來自數據中的任一段(對於網絡數據很有用)。
  <h1>3、圖像轉換</h1>   <h1>3、圖像轉換</h1>
  + 這裏說的圖像轉換,並非類似PNG到JPG的轉換,而主要是色彩空間和大小伸縮的轉換。例如MPEG4的解碼器解出來的圖像格式是YUV420P格式,而若讓Qt來渲染圖像,則需要RGB格式以及任意的大小。
  + ffmpeg中提供swscale庫來提供圖像轉換支持。現在假設我們在上一步解碼出來的數據存放於AVFrame *frame中,我們有:
  + <pre lang="C" line="1">
  + SwsContext *swsCtx;
  + int dst_width = 320, /* 目標寬度 */
  + dst_height = 240, /* 目標高度 */
  + dst_pix_fmt = PIX_FMT_RGB24; /* 目標圖像格式 */
  + AVFrame *convertedFrame = avcodec_alloc_frame();
  + swsCtx = sws_getContext (
  + videoDecodeCtx->width, videoDecodeCtx->height, videoDecodeCtx->pix_fmt,
  + dst_width, dst_height, dst_pix_fmt, SWS_FAST_BILINEAR,
  + NULL, NULL, NULL );
  + if (swsCtx == NULL) {
- 這裏說的圖像轉換,並非類似PNG到JPG的轉換,而主要是色彩空間和大小伸縮的轉換。例如 + /* 轉換上下文初始化失敗,錯誤處理 */
  + }
  + /* 分配轉換需要的內存 */
  + avpicture_fill ( (AVPicture *)convertedFrame,dst_pix_fmt, dst_width, dst_height);
  + if (sws_scale(swsCtx,frame- >data,frame->linesize,0,
  + videoDecodeCtx- >height,convertedFrame- >data,convertedFrame->linesize) <= 0) {
  + /* 轉換失敗,錯誤處理 */
  + }
  + </pre>
  + 於是convertedFrame- >data[0]中就存有轉換好的圖像了!

Note: Spaces may be added to comparison text to allow better line wrapping.


發佈了49 篇原創文章 · 獲贊 6 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章