FFmpeg框架的基礎(chǔ)知識(shí)

ffmpeg是一個(gè)非常有用的命令行程序村砂,它可以用來轉(zhuǎn)碼媒體文件烂斋。它是領(lǐng)先的多媒體框架FFmpeg的一部分,其有很多功能础废,比如解碼汛骂、編碼、轉(zhuǎn)碼评腺、混流帘瞭、分離、轉(zhuǎn)化為流蒿讥、過濾以及播放幾乎所有的由人和機(jī)器創(chuàng)建的媒體文件蝶念。
在這個(gè)框架中包含有各種工具,每一個(gè)用于完成特定的功能诈悍。例如祸轮,ffserver能夠?qū)⒍嗝襟w文件轉(zhuǎn)化為用于實(shí)時(shí)廣播的流兽埃,ffprobe用于分析多媒體流侥钳,ffplay可以當(dāng)作一個(gè)簡(jiǎn)易的媒體播放器,ffmpeg則能夠轉(zhuǎn)換多媒體文件格式柄错。

FFMPEG從功能上劃分為幾個(gè)模塊舷夺,分別為核心工具(libutils)、媒體格式(libavformat)售貌、編解碼(libavcodec)给猾、設(shè)備(libavdevice)和后處理(libavfilter, libswscale, libpostproc),分別負(fù)責(zé)提供公用的功能函數(shù)颂跨、實(shí)現(xiàn)多媒體文件的讀包和寫包敢伸、完成音視頻的編解碼、管理音視頻設(shè)備的操作以及進(jìn)行音視頻后處理恒削。

libavutil是一個(gè)包含簡(jiǎn)化編程功能的庫池颈,其中包括隨機(jī)數(shù)生成器,數(shù)據(jù)結(jié)構(gòu)钓丰,數(shù)學(xué)代碼躯砰,核心多媒體工具等更多東西。
libavcodec是一個(gè)包含音頻/視頻解碼器和編碼器的庫携丁。
libavformat是一個(gè)包含了多媒體格式的分離器和混流器的庫琢歇。
libavdevice是一個(gè)包含輸入輸出設(shè)備的庫,用于捕捉和渲染很多來自常用的多媒體輸入/輸出軟件框架的數(shù)據(jù),包括Video4Linux李茫,Video4Linux2揭保,VfW和ALSA。
libavfilter是一個(gè)包含媒體過濾器的庫魄宏。AVFilter可以給視音頻添加各種濾鏡效果掖举。可以給視頻添加水印娜庇,給YUV數(shù)據(jù)加特效塔次。
libswscale是一個(gè)用于執(zhí)行高度優(yōu)化的圖像縮放和顏色空間/像素格式轉(zhuǎn)換操作的庫。
libswresample是一個(gè)用于執(zhí)行高度優(yōu)化的音頻重采樣名秀,重新矩陣和取樣格式轉(zhuǎn)換操作的庫励负。

在視頻解碼前,先了解以下幾個(gè)基本的概念:

編解碼器(CODEC):能夠進(jìn)行視頻和音頻壓縮(CO)與解壓縮(DEC)匕得,是視頻編解碼的核心部分继榆。
容器/多媒體文件(Container/File):沒有了解視頻的編解碼之前,總是錯(cuò)誤的認(rèn)為平常下載的電影的文件的后綴(avi汁掠,mkv略吨,rmvb等)就是視頻的編碼方式。事實(shí)上考阱,剛才提到的幾種文件的后綴
并不是視頻的編碼方式翠忠,只是其封裝的方式。一個(gè)視頻文件通常有視頻數(shù)據(jù)乞榨、音頻數(shù)據(jù)以及字幕等秽之,封裝的格式?jīng)Q定這些數(shù)據(jù)在文件中是如何的存放的,封裝在一起音頻吃既、視頻等數(shù)據(jù)組成的多媒體文件考榨,也可以叫做容器(其中包含了視音頻數(shù)據(jù))。所以鹦倚,只看多媒體文件的后綴名是難以知道視音頻的編碼方式的河质。
流數(shù)據(jù) Stream,例如視頻流(Video Stream)震叙,音頻流(Audio Stream)掀鹅。流中的數(shù)據(jù)元素被稱為幀F(xiàn)rame。一個(gè)多媒體文件包含有多個(gè)流(視頻流 video stream捐友,音頻流 audio stream淫半,字幕等);流是一種抽象的概念匣砖,表示一連串的數(shù)據(jù)元素科吭;
流中的數(shù)據(jù)元素稱為幀F(xiàn)rame昏滴。

FFMPEG視頻解碼流程:
通常來說,F(xiàn)Fmpeg的視頻解碼過程有以下幾個(gè)步驟:

1. 注冊(cè)所有容器格式及其對(duì)應(yīng)的CODEC: av_register_all()
2. 打開文件: av_open_input_file()
3. 從文件中提取流信息: av_find_stream_info()
4. 窮舉所有的流对人,查找其中種類為CODEC_TYPE_VIDEO的視頻流video stream
5. 查找對(duì)應(yīng)的解碼器: avcodec_find_decoder()
6. 打開編解碼器: avcodec_open2()
7. 為解碼幀分配內(nèi)存: avcodec_alloc_frame()
8. 不停地從碼流中提取出幀數(shù)據(jù)到Packet中: av_read_frame()
9. 判斷幀的類型谣殊,對(duì)于視頻幀調(diào)用: avcodec_decode_video2()
10. 解碼完后,釋放解碼器: avcodec_close()
11. 關(guān)閉輸入文件: avformat_close_input_file()

解碼過程的具體說明

  1. 注冊(cè)
    av_register_all該函數(shù)注冊(cè)支持的所有的文件格式(容器)及其對(duì)應(yīng)的CODEC牺弄,只需要調(diào)用一次姻几,故一般放在main函數(shù)中。也可以注冊(cè)某個(gè)特定的容器格式势告,但通常來說不需要這么做蛇捌。

  2. 打開文件
    avformat_open_input該函數(shù)讀取文件的頭信息,并將其信息保存到AVFormatContext結(jié)構(gòu)體中咱台。其調(diào)用如下

    AVFormatContext* pFormatCtx = nullptr;  
    avformat_open_input(&pFormatCtx, filenName, nullptr, nullptr)  
    

第一個(gè)參數(shù)是AVFormatContext結(jié)構(gòu)體的指針络拌,第二個(gè)參數(shù)為文件路徑;第三個(gè)參數(shù)用來設(shè)定輸入文件的格式回溺,如果設(shè)為null春贸,將自動(dòng)檢測(cè)文件格式;第四個(gè)參數(shù)用來填充AVFormatContext一些字段以及Demuxer的private選項(xiàng)遗遵。
AVFormatContext包含有較多的碼流信息參數(shù)萍恕,通常由avformat_open_input創(chuàng)建并填充關(guān)鍵字段。

  1. 獲取必要的CODEC參數(shù)
    avformat_open_input通過解析多媒體文件或流的頭信息及其他的輔助數(shù)據(jù)车要,能夠獲取到足夠多的關(guān)于文件允粤、流和CODEC的信息,并將這些信息填充到AVFormatContext結(jié)構(gòu)體中屯蹦。但任何一種多媒體格式(容器)提供的信息都是有限的维哈,而且不同的多媒體制作軟件對(duì)頭信息的設(shè)置也不盡相同绳姨,在制作多媒體文件的時(shí)候難免會(huì)引入一些錯(cuò)誤登澜。也就是說,僅僅通過avformat_open_input并不能保證能夠獲取所需要的信息飘庄,所以一般要使用

    avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)
    avformat_find_stream_info主要用來獲取必要的CODEC參數(shù)脑蠕,設(shè)置到ic->streams[i]->codec。
    

在解碼的過程中跪削,首先要獲取到各個(gè)stream所對(duì)應(yīng)的CODEC類型和id谴仙,CODEC的類型和id是兩個(gè)枚舉值,其定義如下:

   enum AVMediaType { 
      AVMEDIA_TYPE_UNKNOWN = -1,     
      AVMEDIA_TYPE_VIDEO,     
      AVMEDIA_TYPE_AUDIO,     
      AVMEDIA_TYPE_DATA, 
      AVMEDIA_TYPE_SUBTITLE,    
      AVMEDIA_TYPE_ATTACHMENT,     
      AVMEDIA_TYPE_NB
    }; 

   enum CodecID { 
     CODEC_ID_NONE,     /* video codecs */ 
     CODEC_ID_MPEG1VIDEO, 
     CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding     
     CODEC_ID_MPEG2VIDEO_XVMC,     
     CODEC_ID_H261,     
     CODEC_ID_H263, 
     ...
   }

通常碾盐,如果多媒體文件具有完整而正確的頭信息晃跺,通過avformat_open_input即可用獲得這兩個(gè)參數(shù)。

  1. 打開解碼器
    經(jīng)過上面的步驟毫玖,已經(jīng)將文件格式信息讀取到了AVFormatContext中掀虎,要打開流數(shù)據(jù)相應(yīng)的CODEC需要經(jīng)過下面幾個(gè)步驟
    找到視頻流 video stream
    一個(gè)多媒體文件包含有多個(gè)原始流凌盯,例如 movie.mkv這個(gè)多媒體文件可能包含下面的流數(shù)據(jù)

    原始流 1 h.264 video
    原始流 2 aac audio for Chinese
    原始流 3 aac audio for English
    原始流 4 Chinese Subtitle
    原始流 5 English Subtitle
    

要解碼視頻,首先要在AVFormatContext包含的多個(gè)流中找到CODEC類型為AVMEDIA_TYPE_VIDEO烹玉,代碼如下:

   //查找視頻流 video stream
   int videoStream = -1;
   for (int 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; // 沒有找到視頻流video stream  

結(jié)構(gòu)體AVFormatContext中的streams字段是一個(gè)AVStream指針的數(shù)組驰怎,包含了文件所有流的描述,上述上述代碼在該數(shù)組中查找CODEC類型為
AVMEDIA_TYPE_VIDEO的流的下標(biāo)二打。

根據(jù)codec_id找到相應(yīng)的CODEC县忌,并打開結(jié)構(gòu)體AVCodecContext描述了CODEC上下文,包含了眾多CODEC所需要的參數(shù)信息继效。

AVCodecContext* pCodecCtxOrg = nullptr; 
AVCodec* pCodec = nullptr;
pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context
// 找到video stream的 decoder
pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id); 
// open codec
if (avcodec_open2(pCodecCtxOrg , pCodec, nullptr) < 0)
   return -1; // Could open codec  

上述代碼症杏,首先通過codec_id找到相應(yīng)的CODEC,然后調(diào)用avcodec_open2打開相應(yīng)的CODEC瑞信。

  1. 讀取數(shù)據(jù)幀并解碼
    已經(jīng)有了相應(yīng)的解碼器鸳慈,下面的工作就是將數(shù)據(jù)從流中讀出,并解碼為沒有壓縮的原始數(shù)據(jù)

    AVPacket packet; 
    while (av_read_frame(pFormatCtx, &packet) >= 0)
    {
         if (packet.stream_index == videoStream)
         {
             int frameFinished = 0;
             avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
             if (frameFinished)
             {
                 doSomething();
             }
         }
     
    } 
    

上述代碼調(diào)用av_read_frame將數(shù)據(jù)從流中讀取數(shù)據(jù)到packet中喧伞,并調(diào)用avcodec_decode_video2對(duì)讀取的數(shù)據(jù)進(jìn)行解碼走芋。

  1. 關(guān)閉
    需要關(guān)閉avformat_open_input打開的輸入流,avcodec_open2打開的CODEC

    avcodec_close(pCodecCtxOrg);
    avformat_close_input(&pFormatCtx);  
    

也就是說多媒體文件中潘鲫,主要有兩種數(shù)據(jù):流Stream 及其數(shù)據(jù)元素 幀F(xiàn)rame翁逞,在FFmpeg自然有與這兩種數(shù)據(jù)相對(duì)應(yīng)的抽象:AVStream和AVPacket。
使用FFmpeg的解碼溉仑,數(shù)據(jù)的傳遞過程可歸納如下:

調(diào)用avformat_open_input打開流挖函,將信息填充到AVFormatContext中
調(diào)用av_read_frame從流中讀取數(shù)據(jù)幀到 AVPacket,AVPacket保存仍然是未解碼的數(shù)據(jù)浊竟。
調(diào)用avcodec_decode_video2將AVPacket的數(shù)據(jù)解碼怨喘,并將解碼后的數(shù)據(jù)填充到AVFrame中,AVFrame中保存的是解碼后的原始數(shù)據(jù)振定。
解碼流程.png

結(jié)構(gòu)體的存儲(chǔ)空間的分配與釋放

FFmpeg并沒有垃圾回收機(jī)制必怜,所分配的空間都需要自己維護(hù)。而由于視頻處理過程中數(shù)據(jù)量是非常大后频,對(duì)于動(dòng)態(tài)內(nèi)存的使用更要謹(jǐn)慎梳庆。
本小節(jié)主要介紹解碼過程使用到的結(jié)構(gòu)體存儲(chǔ)空間的分配與釋放。

AVFormatContext 在FFmpeg中有很重要的作用卑惜,描述一個(gè)多媒體文件的構(gòu)成及其基本信息膏执,存放了視頻編解碼過程中的大部分信息。通常該結(jié)構(gòu)體由avformat_open_input分配存儲(chǔ)空間露久,在最后調(diào)用avformat_input_close關(guān)閉更米。

AVStream 描述一個(gè)媒體流,在解碼的過程中毫痕,作為AVFormatContext的一個(gè)字段存在,不需要單獨(dú)的處理。

AVpacket 用來存放解碼之前的數(shù)據(jù)者祖,它只是一個(gè)容器,其data成員指向?qū)嶋H的數(shù)據(jù)緩沖區(qū)瘤旨,在解碼的過程中可有av_read_frame創(chuàng)建和填充AVPacket中的數(shù)據(jù)緩沖區(qū),
當(dāng)數(shù)據(jù)緩沖區(qū)不再使用的時(shí)候可以調(diào)用av_free_apcket釋放這塊緩沖區(qū)竖伯。

AVFrame 存放從AVPacket中解碼出來的原始數(shù)據(jù)存哲,其必須通過av_frame_alloc來創(chuàng)建,通過av_frame_free來釋放七婴。和AVPacket類似祟偷,AVFrame中也有一塊數(shù)據(jù)緩存空間,在調(diào)用av_frame_alloc的時(shí)候并不會(huì)為這塊緩存區(qū)域分配空間打厘,需要使用其他的方法修肠。在解碼的過程使用了兩個(gè)AVFrame,這兩個(gè)AVFrame分配緩存空間的方法也不相同

一個(gè)AVFrame用來存放從AVPacket中解碼出來的原始數(shù)據(jù)户盯,這個(gè)AVFrame的數(shù)據(jù)緩存空間通過調(diào)avcodec_decode_video分配和填充嵌施。
另一個(gè)AVFrame用來存放將解碼出來的原始數(shù)據(jù)變換為需要的數(shù)據(jù)格式(例如RGB,RGBA)的數(shù)據(jù)莽鸭,這個(gè)AVFrame需要手動(dòng)的分配數(shù)據(jù)緩存空間吗伤。代碼如下:

     AVFrame *pFrameYUV;
     pFrameYUV = av_frame_alloc();
     // 手動(dòng)為 pFrameYUV分配數(shù)據(jù)緩存空間
     int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P,pCodecCtx->widht,pCodecCtx->width);
     uint8_t *buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
     // 將分配的數(shù)據(jù)緩存空間和AVFrame關(guān)聯(lián)起來
     avpicture_fill((AVPicture *)pFrameYUV, buffer, AV_PIX_FMT_YUV420P,pCodecCtx->width,  pCodecCtx->height)

首先計(jì)算需要緩存空間大小,調(diào)用av_malloc分配緩存空間硫眨,最后調(diào)用avpicture_fill將分配的緩存空間和AVFrame關(guān)聯(lián)起來足淆。
調(diào)用av_frame_free來釋放AVFrame,該函數(shù)不止釋放AVFrame本身的空間礁阁,還會(huì)釋放掉包含在其內(nèi)的其他對(duì)象動(dòng)態(tài)申請(qǐng)的空間巧号,例如上面的緩存空間。

av_malloc和av_free姥闭,F(xiàn)Fmpeg并沒有提供垃圾回收機(jī)制丹鸿,所有的內(nèi)存管理都要手動(dòng)進(jìn)行。av_malloc只是在申請(qǐng)內(nèi)存空間的時(shí)候會(huì)考慮到內(nèi)存對(duì)齊(2字節(jié)泣栈,4字節(jié)對(duì)齊)卜高,
其申請(qǐng)的空間要調(diào)用av_free釋放。

調(diào)用的函數(shù)

av_register_all 這個(gè)函數(shù)不用多說了南片,注冊(cè)庫所支持的容器格式及其對(duì)應(yīng)的CODEC。
avformat_open_input 打開多媒體文件流庭敦,并讀取文件的頭疼进,將讀取到的信息填充到AVFormatContext結(jié)構(gòu)體中。在使用結(jié)束后秧廉,要調(diào)用avformat_close_input關(guān)閉打開的流
avformat_find_stream_info 上面提到伞广,avformat_open_input只是讀取文件的頭來得到多媒體文件的信息拣帽,但是有些文件沒有文件頭或者文件頭的格式不正確,這就造成只調(diào)用
avformat_open_input可能得不到解碼所需要的必要信息嚼锄,需要調(diào)用avformat_find_stream_info進(jìn)一步得到流的信息减拭。

通過上面的三個(gè)函數(shù)已經(jīng)獲取了對(duì)多媒體文件進(jìn)行解碼的所需要信息,下面要做的就是根據(jù)這些信息得到相應(yīng)的解碼器区丑。
結(jié)構(gòu)體AVCodecContext描述了編解碼器的上下文信息拧粪,包含了流中所使用的關(guān)于編解碼器的所有信息,可以通過 AVFormatContext->AVStream->AVCodecContext來得到沧侥,在有了AVCodecContext后可霎,可以通過codec_id來找到相應(yīng)的解碼器,具體代碼如下:

AVCodec* pCodec = nullptr;
pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context
// 找到video stream的 decoder
pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);  
avcodec_find_decoder 可以通過codec_id或者名稱來找到相應(yīng)的解碼器宴杀,返回值是一個(gè)AVCodec的指針癣朗。
avcodec_open2 打開相應(yīng)的編解碼器
av_read_frame 從流中讀取數(shù)據(jù)幀暫存到AVPacket中
avcodec_decode_video2 從AVPacket中解碼數(shù)據(jù)到AVFrame中

經(jīng)過以上的過程,AVFrame中的數(shù)據(jù)緩存中存放的就是解碼后的原始數(shù)據(jù)了旺罢。整個(gè)流程梳理如下:

解碼代碼流程.png

(1)RGB轉(zhuǎn)換成YUV

Y = 0.299R + 0.587G + 0.114B
U = 0.567(B - Y)
V = 0.713(R - Y)

值得注意的是旷余,Y值范圍為[0, 1.0]、UV值范圍都是[-0.5, 0.5]扁达,在Accelerate框架的vImage_CVUtilities.h有描述荣暮。

(2)YUV轉(zhuǎn)換成RGB

R = Y + 1.402V
G = Y - 0.344U - 0.714V
B = Y + 1.772U

視音頻技術(shù)主要包含以下幾點(diǎn):封裝技術(shù),視頻壓縮編碼技術(shù)以及音頻壓縮編碼技術(shù)罩驻。如果考慮到網(wǎng)絡(luò)傳輸?shù)脑捤胨郑€包括流媒體協(xié)議技術(shù)。

視頻播放器播放一個(gè)互聯(lián)網(wǎng)上的視頻文件惠遏,需要經(jīng)過以下幾個(gè)步驟:解協(xié)議砾跃,解封裝,解碼視音頻节吮,視音頻同步抽高。

解協(xié)議的作用,就是將流媒體協(xié)議的數(shù)據(jù)透绩,解析為標(biāo)準(zhǔn)的相應(yīng)的封裝格式數(shù)據(jù)翘骂。視音頻在網(wǎng)絡(luò)上傳播的時(shí)候,常常采用各種流媒體協(xié)議帚豪,例如HTTP碳竟,RTMP,或是MMS等等狸臣。這些協(xié)議在傳輸視音頻數(shù)據(jù)的同時(shí)莹桅,也會(huì)傳輸一些信令數(shù)據(jù)。這些信令數(shù)據(jù)包括對(duì)播放的控制(播放烛亦,暫停诈泼,停止)懂拾,或者對(duì)網(wǎng)絡(luò)狀態(tài)的描述等。解協(xié)議的過程中會(huì)去除掉信令數(shù)據(jù)而只保留視音頻數(shù)據(jù)铐达。例如岖赋,采用RTMP協(xié)議傳輸?shù)臄?shù)據(jù),經(jīng)過解協(xié)議操作后瓮孙,輸出FLV格式的數(shù)據(jù)唐断。
解封裝的作用,就是將輸入的封裝格式的數(shù)據(jù)衷畦,分離成為音頻流壓縮編碼數(shù)據(jù)和視頻流壓縮編碼數(shù)據(jù)栗涂。封裝格式種類很多,例如MP4祈争,MKV斤程,RMVB,TS菩混,F(xiàn)LV忿墅,AVI等等,它的作用就是將已經(jīng)壓縮編碼的視頻數(shù)據(jù)和音頻數(shù)據(jù)按照一定的格式放到一起沮峡。例如疚脐,F(xiàn)LV格式的數(shù)據(jù),經(jīng)過解封裝操作后邢疙,輸出H.264編碼的視頻碼流和AAC編碼的音頻碼流棍弄。
解碼的作用,就是將視頻/音頻壓縮編碼數(shù)據(jù)疟游,解碼成為非壓縮的視頻/音頻原始數(shù)據(jù)呼畸。音頻的壓縮編碼標(biāo)準(zhǔn)包含AAC,MP3颁虐,AC-3等等蛮原,視頻的壓縮編碼標(biāo)準(zhǔn)則包含H.264,MPEG2另绩,VC-1等等儒陨。解碼是整個(gè)系統(tǒng)中最重要也是最復(fù)雜的一個(gè)環(huán)節(jié)。通過解碼笋籽,壓縮編碼的視頻數(shù)據(jù)輸出成為非壓縮的顏色數(shù)據(jù)蹦漠,例如YUV420P,RGB等等干签;壓縮編碼的音頻數(shù)據(jù)輸出成為非壓縮的音頻抽樣數(shù)據(jù)津辩,例如PCM數(shù)據(jù)。
視音頻同步的作用容劳,就是根據(jù)解封裝模塊處理過程中獲取到的參數(shù)信息喘沿,同步解碼出來的視頻和音頻數(shù)據(jù),并將視頻音頻數(shù)據(jù)送至系統(tǒng)的顯卡和聲卡播放出來竭贩。

一般來說蚜印,視頻同步指的是視頻和音頻同步,也就是說播放的聲音要和當(dāng)前顯示的畫面保持一致留量。想象以下窄赋,看一部電影的時(shí)候只看到人物嘴動(dòng)沒有聲音傳出;或者畫面是激烈的戰(zhàn)斗場(chǎng)景楼熄,而聲音不是槍炮聲卻是人物說話的聲音忆绰,這是非常差的一種體驗(yàn)。
在視頻流和音頻流中已包含了其以怎樣的速度播放的相關(guān)數(shù)據(jù)可岂,視頻的幀率(Frame Rate)指示視頻一秒顯示的幀數(shù)(圖像數(shù))错敢;音頻的采樣率(Sample Rate)表示音頻一秒播放的樣本(Sample)的個(gè)數(shù)÷拼猓可以使用以上數(shù)據(jù)通過簡(jiǎn)單的計(jì)算得到其在某一Frame(Sample)的播放時(shí)間稚茅,以這樣的速度音頻和視頻各自播放互不影響,在理想條件下平斩,其應(yīng)該是同步的亚享,不會(huì)出現(xiàn)偏差。但绘面,理想條件是什么大家都懂得欺税。如果用上面那種簡(jiǎn)單的計(jì)算方式,慢慢的就會(huì)出現(xiàn)音視頻不同步的情況揭璃。要不是視頻播放快了晚凿,要么是音頻播放快了,很難準(zhǔn)確的同步塘辅。這就需要一種隨著時(shí)間會(huì)線性增長的量晃虫,視頻和音頻的播放速度都以該量為標(biāo)準(zhǔn),播放快了就減慢播放速度扣墩;播放快了就加快播放的速度哲银。所以呢,視頻和音頻的同步實(shí)際上是一個(gè)動(dòng)態(tài)的過程呻惕,同步是暫時(shí)的荆责,不同步則是常態(tài)。以選擇的播放速度量為標(biāo)準(zhǔn)亚脆,快的等待慢的做院,慢的則加快速度,是一個(gè)你等我趕的過程。

播放速度標(biāo)準(zhǔn)量的的選擇一般來說有以下三種:

將視頻同步到音頻上键耕,就是以音頻的播放速度為基準(zhǔn)來同步視頻寺滚。視頻比音頻播放慢了,加快其播放速度屈雄;快了村视,則延遲播放。
將音頻同步到視頻上酒奶,就是以視頻的播放速度為基準(zhǔn)來同步音頻蚁孔。
將視頻和音頻同步外部的時(shí)鐘上,選擇一個(gè)外部時(shí)鐘為基準(zhǔn)惋嚎,視頻和音頻的播放速度都以該時(shí)鐘為標(biāo)準(zhǔn)杠氢。

DTS和PTS
上面提到,視頻和音頻的同步過程是一個(gè)你等我趕的過程另伍,快了則等待鼻百,慢了就加快速度。這就需要一個(gè)量來判斷(和選擇基準(zhǔn)比較)质况,到底是播放的快了還是慢了愕宋,或者正以同步的速度播放。在視音頻流中的包中都含有DTS和PTS结榄,就是這樣的量(準(zhǔn)確來說是PTS)中贝。DTS,Decoding Time Stamp臼朗,解碼時(shí)間戳邻寿,告訴解碼器packet的解碼順序;PTS视哑,Presentation Time Stamp绣否,顯示時(shí)間戳,指示從packet中解碼出來的數(shù)據(jù)的顯示順序挡毅。
視音頻都是順序播放的蒜撮,其解碼的順序不應(yīng)該就是其播放的順序么,為啥還要有DTS和PTS之分呢跪呈。對(duì)于音頻來說段磨,DTS和PTS是相同的,也就是其解碼的順序和解碼的順序是相同的耗绿,但對(duì)于視頻來說情況就有些不同了苹支。
視頻的編碼要比音頻復(fù)雜一些,特別的是預(yù)測(cè)編碼是視頻編碼的基本工具误阻,這就會(huì)造成視頻的DTS和PTS的不同债蜜。這樣視頻編碼后會(huì)有三種不同類型的幀:

I幀 關(guān)鍵幀晴埂,包含了一幀的完整數(shù)據(jù),解碼時(shí)只需要本幀的數(shù)據(jù)寻定,不需要參考其他幀儒洛。
P幀 P是向前搜索,該幀的數(shù)據(jù)不完全的特姐,解碼時(shí)需要參考其前一幀的數(shù)據(jù)晶丘。
B幀 B是雙向搜索黍氮,解碼這種類型的幀是最復(fù)雜唐含,不但需要參考其一幀的數(shù)據(jù),還需要其后一幀的數(shù)據(jù)沫浆。

I幀的解碼是最簡(jiǎn)單的捷枯,只需要本幀的數(shù)據(jù);P幀也不是很復(fù)雜专执,值需要緩存上一幀的數(shù)據(jù)即可淮捆,總體來說都是線性,其解碼順序和顯示順序是一致的本股。B幀就比較復(fù)雜了攀痊,需要前后兩幀的順序,并且不是線性的拄显,也是造成了DTS和PTS的不同的“元兇”苟径,也是在解碼后有可能得不到完整Frame的原因。(更多I,B,P幀的信息可參考)
假如一個(gè)視頻序列躬审,要這樣顯示I B B P棘街,但是需要在B幀之前得到P幀的信息,因此幀可能以這樣的順序來存儲(chǔ)I P B B承边,這樣其解碼順序和顯示的順序就不同了遭殉,這也是DTS和PTS同時(shí)存在的原因。DTS指示解碼順序博助,PTS指示顯示順序险污。所以流中可以是這樣的:

Stream : I P B B
DTS      1 2 3 4
PTS      1 4 2 3

通常來說只有在流中含有B幀的時(shí)候,PTS和DTS才會(huì)不同富岳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛔糯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子城瞎,更是在濱河造成了極大的恐慌渤闷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脖镀,死亡現(xiàn)場(chǎng)離奇詭異飒箭,居然都是意外死亡狼电,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門弦蹂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肩碟,“玉大人,你說我怎么就攤上這事凸椿∠髌恚” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵脑漫,是天一觀的道長髓抑。 經(jīng)常有香客問我,道長优幸,這世上最難降的妖魔是什么吨拍? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮网杆,結(jié)果婚禮上羹饰,老公的妹妹穿的比我還像新娘。我一直安慰自己碳却,他們只是感情好队秩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昼浦,像睡著了一般馍资。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上座柱,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天迷帜,我揣著相機(jī)與錄音,去河邊找鬼色洞。 笑死戏锹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的火诸。 我是一名探鬼主播锦针,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼置蜀!你這毒婦竟也來了奈搜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤盯荤,失蹤者是張志新(化名)和其女友劉穎馋吗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秋秤,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宏粤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年脚翘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绍哎。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡来农,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崇堰,到底是詐尸還是另有隱情沃于,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布海诲,位于F島的核電站繁莹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏饿肺。R本人自食惡果不足惜蒋困,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敬辣。 院中可真熱鬧,春花似錦零院、人聲如沸溉跃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撰茎。三九已至,卻和暖如春打洼,著一層夾襖步出監(jiān)牢的瞬間龄糊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工募疮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炫惩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓阿浓,卻偏偏與公主長得像他嚷,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芭毙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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