FFmpeg筆記(三)-- 解碼過程

術(shù)語

容器/文件(Conainer/File):即特定格式的多媒體文件慎璧,比如 MP4偷溺、flv逮栅、mov等悴势。

媒體流(Stream):表示時間軸上的一段連續(xù)數(shù)據(jù),如一段聲音數(shù)據(jù)措伐、一段視頻數(shù)據(jù)或一段字幕數(shù)據(jù)特纤,可以是壓縮的,也可以是非壓縮的侥加,壓縮的數(shù)據(jù)需要關(guān)聯(lián)特定的編解碼器捧存。

數(shù)據(jù)幀/數(shù)據(jù)包(Frame/Packet):通常,一個媒體流是由大量的數(shù)據(jù)幀組成的担败,對于壓縮數(shù)據(jù)昔穴,幀對應(yīng)著編解碼器的最小處理單元,分屬于不同媒體流的數(shù)據(jù)幀交錯存儲于容器之中提前。

編解碼器:編解碼器是以幀(針對視頻吗货,音頻沒有幀的概念,可以理解為一段音頻數(shù)據(jù))為單位實現(xiàn)壓縮數(shù)據(jù)和原始數(shù)據(jù)之間的相互轉(zhuǎn)換的狈网。

解碼過程

1.導(dǎo)入頭文件宙搬。
#import <libavformat/avformat.h>
#import <libswscale/swscale.h>
#import <libswresample/swresample.h>
#import <libavutil/pixdesc.h>
#include <libavutil/imgutils.h>
2.注冊協(xié)議、格式拓哺、編解碼器勇垛。

調(diào)用FFmpeg的注冊協(xié)議、格式與編解碼器的方法士鸥,確保所有的格式與編解碼器都被注冊到了FFmpeg框架中闲孤。

av_register_all(); //新庫已經(jīng)廢棄了這個函數(shù),不再需要注冊
3.創(chuàng)建上下文烤礁,打開媒體文件源讼积。

注冊了格式以及編解碼器之后,接下來就應(yīng)該打開對應(yīng)的媒體文件了鸽凶,當然該文件既可能是本地磁盤的文件币砂,也可能是網(wǎng)絡(luò)媒體資源的一個鏈接,如果是網(wǎng)絡(luò)鏈接玻侥,則會涉及不同的協(xié)議,比如RTMP亿蒸、HTTP等協(xié)議的視頻源凑兰。

AVFormatContext *formatCtx = avformat_alloc_context();
avformat_open_input(&formatCtx, path, NULL, NULL);
avformat_find_stream_info(formatCtx, NULL);
4.尋找各個流掌桩,并且打開對應(yīng)的解碼器。

這一步我們要尋找出各個流姑食,然后找到流中對應(yīng)的解碼器波岛,并且打開它。
尋找音視頻流:

for(int i = 0; i < formatCtx->nb_streams; i++) { 
    AVStream* stream = formatCtx->streams[i]; 
    if(AVMEDIA_TYPE_VIDEO == stream->codec->codec_type) {
       // 視頻流
       videoStreamIndex = i;
    } else if(AVMEDIA_TYPE_AUDIO == stream->codec->codec_type ) {
       // 音頻流
       audioStreamIndex = i;
    }
}

打開音頻流解碼器:

AVCodecContext * audioCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(audioCodecCtx, audioStream->codecpar);
AVCodec *codec = avcodec_find_decoder(audioCodecCtx ->codec_id);
if(!codec) {
    // 找不到對應(yīng)的音頻解碼器 
}
int openCodecErrCode = 0;
if ((openCodecErrCode = avcodec_open2(codecCtx, codec, NULL)) < 0) {
     // 打開音頻解碼器失敗
}

打開視頻流解碼器:


AVCodecContext *videoCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(videoCodecCtx, videoStream->codecpar);
AVCodec *codec = avcodec_find_decoder(videoCodecCtx->codec_id); 
if(!codec) {
    // 找不到對應(yīng)的視頻解碼器 
}
int openCodecErrCode = 0;
if ((openCodecErrCode = avcodec_open2(codecCtx, codec, NULL)) < 0) {
    // 打開視頻解碼器失敗
 }
5.初始化解碼后數(shù)據(jù)的結(jié)構(gòu)體音半。

分配出解碼之后的數(shù)據(jù)所存放的內(nèi)存空間则拷,以及進行格式轉(zhuǎn)換需要用到的對象。
構(gòu)建音頻的格式轉(zhuǎn)換對象以及音頻解碼后數(shù)據(jù)存放的對象:

SwrContext *swrContext = NULL;
if(audioCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
     // 如果不是我們需要的數(shù)據(jù)格式,則重新采樣
     swrContext = swr_alloc_set_opts(NULL,outputChannel, AV_SAMPLE_FMT_S16, outSampleRate,
        in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);

     if(!swrContext || swr_init(swrContext)) {
         if(swrContext) {
            swr_free(&swrContext);
        }
     }
     audioFrame = av_frame_alloc();
}

構(gòu)建視頻的格式轉(zhuǎn)換對象以及視頻解碼后數(shù)據(jù)存放的對象:

AVFrame *videoFrame = avcodec_alloc_frame();
//創(chuàng)建緩沖區(qū)
av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                         videoCodecCtx->width,
                         videoCodecCtx->height,
                         1);
//開辟一塊內(nèi)存空間
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
//緩沖區(qū)數(shù)據(jù)填充
av_image_fill_arrays(avframe_yuv420p->data,
                     avframe_yuv420p->linesize,
                     out_buffer,
                     AV_PIX_FMT_YUV420P,
                     videoCodecCtx->width,
                     videoCodecCtx->height,
                     1);
//初始化視頻數(shù)據(jù)存儲對象
swsContext = sws_getContext(swsContext, videoCodecCtx->width,videoCodecCtx->height, videoCodecCtx->pix_fmt, videoCodecCtx->width, videoCodecCtx->height, PIX_FMT_YUV420P, SWS_FAST_BILINEAR,NULL, NULL, NULL); 

6.讀取流內(nèi)容并且解碼曹鸠。

讀取一部分流中的數(shù)據(jù)(壓縮數(shù)據(jù))煌茬, 然后將壓縮數(shù)據(jù)作為解碼器的輸入,解碼器將其解碼為原始數(shù)據(jù)(裸數(shù) 據(jù))彻桃,之后就可以將原始數(shù)據(jù)寫入文件了:

AVPacket packet;
while(true) {
  if(av_read_frame(formatContext, &packet)) { // End Of File
    break;
  }
  int packetStreamIndex = packet.stream_index; 
  if(packetStreamIndex == videoStreamIndex) {
      int send_result = avcodec_send_packet(videoCodecCtx, packet);
      avcodec_receive_frame(videoCodecCtx, videoFrame);
      if(send_result == 0) {
          //處理視頻數(shù)據(jù)坛善,見【7.】
      }
      if(gotFrame) {
          self->handleVideoFrame(); 
      }
  } else if(packetStreamIndex == audioStreamIndex) {
      int send = avcodec_send_packet(audioCodecCtx, packet);
      avcodec_receive_frame(audioCodecCtx, audioFrame);
      if(send == 0) {
          //處理音頻數(shù)據(jù),見【7.】
      }
  }
}
7.處理解碼后的裸數(shù)據(jù)邻眷。

解碼之后會得到裸數(shù)據(jù)眠屎,音頻就是PCM數(shù)據(jù),視頻就是YUV數(shù)據(jù)肆饶。下面將其處理成我們所需要的格式并且進行寫文件改衩。 音頻裸數(shù)據(jù)的處理:

void* audioData;
int numFrames;
if(swrContext) {
   int bufSize = av_samples_get_buffer_size(NULL, channels,(int)(audioFrame->nb_samples * channels),AV_SAMPLE_FMT_S16, 1);
   if (!_swrBuffer || _swrBufferSize < bufSize) {
      swrBufferSize = bufSize;
      swrBuffer = realloc(_swrBuffer, _swrBufferSize); 
   }
   Byte *outbuf[2] = { _swrBuffer, 0 }; 
   numFrames = swr_convert(_swrContext, outbuf,(int)(audioFrame->nb_samples * channels), (const uint8_t **)_audioFrame->data, audioFrame->nb_samples);
   audioData = swrBuffer;
 } else {
   audioData = audioFrame->data[0]; 
   numFrames = audioFrame->nb_samples;
}

接收到音頻裸數(shù)據(jù)之后,就可以直接寫文件了驯镊,比如寫到文件 audio.pcm中葫督。
視頻裸數(shù)據(jù)的處理:

uint8_t* luma;
uint8_t* chromaB;
uint8_t* chromaR;
if(videoCodecCtx->pix_fmt == AV_PIX_FMT_YUV420P ||
   videoCodecCtx->pix_fmt == AV_PIX_FMT_YUVJ420P) { 

   luma = copyFrameData(videoFrame->data[0],videoFrame->linesize[0], 
                         videoCodecCtx->width, videoCodecCtx->height);
   chromaB = copyFrameData(videoFrame->data[1], videoFrame->linesize[1],         
                            videoCodecCtx->width / 2, videoCodecCtx->height / 2);
   chromaR = copyFrameData(videoFrame->data[2], videoFrame->linesize[2], 
                            videoCodecCtx->width / 2, videoCodecCtx->height / 2);
} else { 
   sws_scale(_swsContext,(const uint8_t **)videoFrame->data, videoFrame->linesize,0,
            videoCodecCtx->height,picture.data,picture.linesize);
   luma = copyFrameData(picture.data[0],picture.linesize[0], 
                       videoCodecCtx->width, videoCodecCtx->height);
   chromaB = copyFrameData(picture.data[1], picture.linesize[1], 
                          videoCodecCtx->width / 2, videoCodecCtx->height / 2);
   chromaR = copyFrameData(picture.data[2], picture.linesize[2], 
                          videoCodecCtx->width / 2, videoCodecCtx->height / 2);
}

接收到Y(jié)UV數(shù)據(jù)之后也可以直接寫入文件了,比如寫到文件 video.yuv中阿宅。

8.關(guān)閉所有資源候衍。

解碼結(jié)束或中途退出需要將用到的FFmpeg框架中的資源,包括 FFmpeg框架對外的連接資源等全都釋放掉洒放。
關(guān)閉音頻資源:

if (swrBuffer) {
   free(swrBuffer);
   swrBuffer = NULL;
   swrBufferSize = 0;
}
if (swrContext) { 
   swr_free(&swrContext); 
   swrContext = NULL;
}
if (audioFrame) {
   av_free(audioFrame);
   audioFrame = NULL;
}
if (audioCodecCtx) { 
   avcodec_close(audioCodecCtx);
   audioCodecCtx = NULL;
}

關(guān)閉視頻資源:

if (swsContext) { 
   sws_freeContext(swsContext); 
   swsContext = NULL;
}
if (pictureValid) {
   avpicture_free(&picture);
   pictureValid = false;
}
if (videoFrame) { 
   av_free(videoFrame); 
   videoFrame = NULL;
}
if (videoCodecCtx) {
   avcodec_close(videoCodecCtx);
   videoCodecCtx = NULL;
}

關(guān)閉連接資源:

if (formatCtx) { 
  avformat_close_input(&formatCtx); 
  formatCtx = NULL;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛉鹿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子往湿,更是在濱河造成了極大的恐慌妖异,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件领追,死亡現(xiàn)場離奇詭異他膳,居然都是意外死亡,警方通過查閱死者的電腦和手機绒窑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門棕孙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事蟀俊∏掌蹋” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵肢预,是天一觀的道長矛洞。 經(jīng)常有香客問我,道長烫映,這世上最難降的妖魔是什么沼本? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮锭沟,結(jié)果婚禮上抽兆,老公的妹妹穿的比我還像新娘。我一直安慰自己冈钦,他們只是感情好郊丛,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瞧筛,像睡著了一般厉熟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上较幌,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天揍瑟,我揣著相機與錄音,去河邊找鬼乍炉。 笑死绢片,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的岛琼。 我是一名探鬼主播底循,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼槐瑞!你這毒婦竟也來了熙涤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤困檩,失蹤者是張志新(化名)和其女友劉穎祠挫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悼沿,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡等舔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了糟趾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慌植。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡甚牲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涤浇,到底是詐尸還是另有隱情鳖藕,我是刑警寧澤魔慷,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布只锭,位于F島的核電站,受9級特大地震影響院尔,放射性物質(zhì)發(fā)生泄漏蜻展。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一邀摆、第九天 我趴在偏房一處隱蔽的房頂上張望纵顾。 院中可真熱鬧,春花似錦栋盹、人聲如沸施逾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汉额。三九已至,卻和暖如春榨汤,著一層夾襖步出監(jiān)牢的瞬間蠕搜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工收壕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妓灌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓蜜宪,卻偏偏與公主長得像虫埂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子圃验,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容