亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

學(xué)習(xí)FFmpeg API – 解碼視頻

系統(tǒng) 2156 0

本文轉(zhuǎn)載

視頻播放過(guò)程

首先簡(jiǎn)單介紹以下視頻文件的相關(guān)知識(shí)。我們平時(shí)看到的視頻文件有許多格式,比如 avi, mkv, rmvb, mov, mp4等等,這些被稱為 容器 Container ), 不同的容器格式規(guī)定了其中音視頻數(shù)據(jù)的組織方式(也包括其他數(shù)據(jù),比如字幕等)。容器中一般會(huì)封裝有視頻和音頻軌,也稱為視頻流(stream)和音頻 流,播放視頻文件的第一步就是根據(jù)視頻文件的格式,解析(demux)出其中封裝的視頻流、音頻流以及字幕(如果有的話),解析的數(shù)據(jù)讀到包 (packet)中,每個(gè)包里保存的是視頻幀(frame)或音頻幀,然后分別對(duì)視頻幀和音頻幀調(diào)用相應(yīng)的解碼器(decoder)進(jìn)行解碼,比如使用 H.264編碼的視頻和MP3編碼的音頻,會(huì)相應(yīng)的調(diào)用H.264解碼器和MP3解碼器,解碼之后得到的就是原始的圖像(YUV or RGB)和聲音(PCM)數(shù)據(jù),然后根據(jù)同步好的時(shí)間將圖像顯示到屏幕上,將聲音輸出到聲卡,最終就是我們看到的視頻。

FFmpeg的API就是根據(jù)這個(gè)過(guò)程設(shè)計(jì)的,因此使用FFmpeg來(lái)處理視頻文件的方法非常直觀簡(jiǎn)單。下面就一步一步介紹從視頻文件中解碼出圖片的過(guò)程。


???

聲明變量

首先定義整個(gè)過(guò)程中需要使用到的變量:


    
      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 :保存需要讀入的文件的格式信息,比如流的個(gè)數(shù)以及流數(shù)據(jù)等
  • AVCodecCotext :保存了相應(yīng)流的詳細(xì)編碼信息,比如視頻的寬、高,編碼類型等。
  • pCodec :真正的編解碼器,其中有編解碼需要調(diào)用的函數(shù)
  • AVFrame :用于保存數(shù)據(jù)幀的數(shù)據(jù)結(jié)構(gòu),這里的兩個(gè)幀分別是保存顏色轉(zhuǎn)換前后的兩幀圖像
  • AVPacket :解析文件時(shí)會(huì)將音/視頻幀讀入到packet中

打開(kāi)文件

接下來(lái)我們打開(kāi)一個(gè)視頻文件。

1 av_register_all();

av_register_all 定義在 libavformat 里,調(diào)用它用以注冊(cè)所有支持的文件格式以及編解碼器,從其 實(shí)現(xiàn)代碼 里可以看到它會(huì)調(diào)用 avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。

1 if ( avformat_open_input(&pFormatCtx, argv[1], NULL, NULL) != 0 )
2 ???? return -1;

使用新的API avformat_open_input 來(lái)打開(kāi)一個(gè)文件,第一個(gè)參數(shù)是一個(gè)AVFormatContext指針變量的地址,它會(huì)根據(jù)打開(kāi)的文件信息填充AVFormatContext, 需要注意的是,此處的pFormatContext必須為NULL或由 avformat_alloc_context 分配得到 ,這也是上一節(jié)中將其初始化為NULL的原因,否則此函數(shù)調(diào)用會(huì)出問(wèn)題。第二個(gè)參數(shù)是打開(kāi)的文件名,通過(guò)argv[1]指定,也就是命令行的第一個(gè)參數(shù)。后兩個(gè)參數(shù)分別用于指定特定的輸入格式( AVInputFormat )以及指定文件打開(kāi)額外參數(shù)的 AVDictionary 結(jié)構(gòu),這里均留作NULL。

? if ( avformat_find_stream_info(pFormatCtx, NULL ) < 0 )
? ???? return -1;
? ?
? ?? av_dump_format(pFormatCtx, -1, argv[1], 0);

?

avformat_open_input 函數(shù)只是讀文件頭,并不會(huì)填充流信息,因此我們需要接下來(lái)調(diào)用 avformat_find_stream_info 獲取文件中的流信息,此函數(shù)會(huì)讀取packet,并確定文件中所有的流信息,設(shè)置pFormatCtx->streams指向文件中的流,但此函數(shù)并不會(huì)改變文件指針,讀取的packet會(huì)給后面的解碼進(jìn)行處理。
最后調(diào)用一個(gè)幫助函數(shù) av_dump_format ,輸出文件的信息,也就是我們?cè)谑褂胒fmpeg時(shí)能看到的文件詳細(xì)信息。第二個(gè)參數(shù)指定輸出哪條流的信息,-1表示給ffmpeg自己選擇。最后一個(gè)參數(shù)用于指定dump的是不是輸出文件,我們dump的是輸入文件,因此一定要是0。

現(xiàn)在 pFormatCtx->streams 中已經(jīng)有所有流了,因此現(xiàn)在我們遍歷它找到第一條視頻流:

    
      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 的宏定義已經(jīng)由以前的 CODEC_TYPE_VIDEO 改為 AVMEDIA_TYPE_VIDEO 了。接下來(lái)我們通過(guò)這條 video stream 的編解碼信息打開(kāi)相應(yīng)的解碼器:

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 ;


分配圖像緩存

接下來(lái)我們準(zhǔn)備給即將解碼的圖片分配內(nèi)存空間。

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;

調(diào)用 avcodec_alloc_frame 分配幀,因?yàn)樽詈笪覀儠?huì)將圖像寫(xiě)成 24-bits RGB 的 PPM 文件,因此這里需要兩個(gè) AVFrame,pFrame用于存儲(chǔ)解碼后的數(shù)據(jù),pFrameRGB用于存儲(chǔ)轉(zhuǎn)換后的數(shù)據(jù):

1 numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
2 ?????????????? pCodecCtx->height);

這里調(diào)用 avpicture_get_size ,根據(jù) pCodecCtx 中原始圖像的寬高計(jì)算 RGB24 格式的圖像需要占用的空間大小,這是為了之后給 pFrameRGB 分配空間:

1 buffer = av_malloc(numBytes);
2 ? ?
3 ?? avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
4 ?????????? pCodecCtx->width, pCodecCtx->height);

?

接著上面的,首先是用 av_malloc 分配上面計(jì)算大小的內(nèi)存空間,然后調(diào)用 avpicture_fill 將 pFrameRGB 跟 buffer 指向的內(nèi)存關(guān)聯(lián)起來(lái)。

獲取圖像

OK,一切準(zhǔn)備好就可以開(kāi)始從文件中讀取視頻幀并解碼得到圖像了。

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 從文件中讀取一個(gè)packet,對(duì)于視頻來(lái)說(shuō)一個(gè)packet里面包含一幀圖像數(shù)據(jù),音頻可能包含多個(gè)幀(當(dāng)音頻幀長(zhǎng)度固定時(shí)),讀到這一幀后,如果是視頻幀,則使用 avcodec_decode_video2 對(duì)packet中的幀進(jìn)行解碼,有時(shí)候解碼器并不能從一個(gè)packet中解碼得到一幀圖像數(shù)據(jù)(比如在需要其他參考幀的情況下),因此會(huì)設(shè)置 frameFinished,如果已經(jīng)得到下一幀圖像則設(shè)置 frameFinished 非零,否則為零。所以這里我們判斷 frameFinished 是否為零來(lái)確定 pFrame 中是否已經(jīng)得到解碼的圖像。注意在每次處理完后需要調(diào)用 av_free_packet 釋放讀取的packet。

解碼得到圖像后,很有可能不是我們想要的 RGB24 格式,因此需要使用 swscale 來(lái)做轉(zhuǎn)換,調(diào)用 sws_getCachedContext 得到轉(zhuǎn)換上下文,使用 sws_scale 將圖形從解碼后的格式轉(zhuǎn)換為 RGB24,最后將前50幀寫(xiě)人 ppm 文件。最后釋放圖像以及關(guān)閉文件:

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 }

?

學(xué)習(xí)FFmpeg API – 解碼視頻


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 欧美亚洲图片 | 免费国产成人综合 | 一级特黄一欧美俄罗斯毛片 | 91香蕉在线观看 | 久久久噜噜噜久噜久久 | 天天夜夜操 | 色视频免费国产观看 | 久久福利免费视频 | 五月婷婷在线视频 | 天堂精品在线 | 91尤物国产尤物福利 | 奇米77777| 中文国产成人精品久久无广告 | 91精彩视频在线观看 | 中文字幕亚洲一区二区va在线 | 久久久久久久久综合 | 91视频成人 | 波多野结衣免费一区二区三区香蕉 | 国产精品毛片久久久久久久 | 中文字幕天堂久久精品 | 国产麻豆永久视频 | 国产成人福利在线 | 六月丁香色婷婷 | 日韩大乳视频中文字幕 | 国产成人精品一区二区三区 | 777午夜精品被窝影院 | 日日a.v拍夜夜添久久免费 | 五月婷婷激情五月 | 爱爱视频网站免费 | 四虎国产精品永久地址51 | 男人手机天堂 | 日韩欧美在线观看视频 | 中文字幕最新中文字幕中文字幕 | 久草热久| 松永纱奈在线观看 | 国产成人小视频 | 波多野结衣三区 | 精品国产自在久久 | 中文字幕一区中文亚洲 | 久久亚洲精品一区成人 | 久久色伊人 |