音視頻開發-FFmpeg


  音視頻開發是個非常復雜的,龐大的開發話題,初涉其中,先看一下結合 OEIP(開源項目) 新增例子.

  

    可以打開flv,mp4類型文件,以及rtmp協議音視頻數據,聲音的播放使用SDL。

  

    把采集的麥/聲卡數據混合並與采集的視頻信息寫入媒體文件或是RMTP協議中。

圖片主要屬性

  包含長/寬/通道數/像素格式(U8/U16/F32),以及排列格式RGBA/YUV。其中通道與像素格式,如在opencv中,CV_8UC1/CV_8UC4,表示1個通道與4個通道的U8格式。而排列格式,簡單的分為RGBA類的,如BGRA,BGR,R這些,一般用於游戲里的紋理,RGBA/BGR/R本身有表示通道格式的意思,所以后面組合RGBA32,RGBAF32用來表示通道像素里數據格式。而YUV類型,一般用在媒體類型,如采集設備,音視頻文件,推拉流傳輸等,YUV格式有二個組成部分。一是UV對應像素個數,如YUV420,YUV422,YUV444,他們的相同點就是一個像素點對應一個Y,不同點是,如YUV420表示一個U/V對應4個像素點,422一個UV對應二個像素點,UV444則表示一個UV對應一個像素點,YUV像素點數據格式一般是U8。

  我們可以來比較一些常用格式最終占用字節大小,如1080P下。

  • RGBA32 1920*1080*4=8,294,400‬ 其中4是每個像素包含RGBA四個通道。
  • YUV420 1920*1080 + 980*540*2 前面是Y占用大小,后面是UV占用大小。
  • YUV422 1920*1080 + 980*1080*2 前面是Y占用大小,后面是UV占用大小
  • YUV444 1920*1080*3 每個像素一個YUV三通道。

  第二是排序格式,如YUV420可以細分YUV420I,YUV420P,YUV420SP(NV12),簡單來說,后綴是P是表示YUV這三個分開存放,而SP是Y單獨分開,UV交織在一起,而I就更簡單了,YUV交織在一起,簡單來說,和RGBA格式差不多排列。

  YUV420P/YUV422P 一般用來視頻文件與推拉流傳輸上,我想可能原因是Y用來表示亮度,人眼最敏感部分,早期黑白電視只有Y大家一樣能看,這種格式最方便兼容,直接把UV丟棄就行,Y/U/V可能在內存分布上不連續。其中YUV420I/YUV422I 這種交織的一般是采集設備所用,內存上連續,方便處理,YUV420SP(NV12) 這種一般也是采集設備所有,這種格式簡單來說,一是方便只使用Y,二是UV可以和Y的寬度統一,后面align的話,YUV也是統一一個,內存上也方便連續。

  在OEIP中的DX11與對應CUDA模塊中,可以看到YUV各種格式與RGB轉化的相關代碼。

音頻基本屬性如下

  采樣率:如人耳聽到最高頻率是22kHz,如要完全重現頻率,需要采樣率是頻率*2,所以一般來說44100是很常見的采樣率。

  聲道數:常見單聲道,雙聲道,也有不常見的更多聲道。

  數據格式:一般是S16(16位有符號整合),F32(32位浮點),別的不算常用如U8,D64,這二個表示的分貝范圍一個太小,一個太大。

  和圖像類型,多個聲道的表示方法也有P(平面),I(交織),類似,音頻采集數據一般是I,傳輸P,在OEIP項目中,相應格式一般都轉成單聲道S16,也就不需要管理是P還是I。

音視頻開發基本概念

  有了上面基本認識,我們再來認識一些概念(以下是本人的一些基本理解,如果有錯,歡迎大家指出)。

  編碼:把原始音視頻數據壓縮,簡單來說,1080PYUV420的大小1920*1080*3/2=3,110,400 Byte,1秒25楨的話就有差不多78M。視頻如YUV->H264,音頻PCM->ACC過程就是編碼。在FFmpeg中,類似過程就是AVFrame->AVPacket

  解碼:把壓縮后的音視頻轉換成原始音視頻數據,H264->YUV,ACC->PCM過程。在FFmpeg中,就是AVPacket->AVFrame.

  在編碼與解碼中,圖像每楨原始大小取決原始圖像大小根據長寬,像素排序和格式組成,而音頻每楨對於特定編解碼器是每個通道固定多少采樣點,如AAC是1024(也有特殊情況2048的),MP3是1152,相應字段在AVFrame.nb_sampes,AVCodecContext.frame_size里表示.這樣AAC中雙通道 U16每楨數據量就在2*sizeof(U16)*1024.而碼率決定了編碼質量,一般情況下,碼率高質量好,但是生成文件或是網絡占用就好,合適的碼率網上有介紹,1080P下一般用4M的碼率,你用1M也行,但是畫面動的時候可能就糊了,碼率控制也有不同的控制策略,根據需求選擇自己的控制策略,這部分網上有詳細的講解。

  媒體文件:FLV/MP4這些,不同媒體格式把編碼的信息用不同的方式保存,不同媒體格式支持不同的編碼格式,大部媒體格式都支持h264/acc編碼信息,所以這二個編碼格式比較常用。

  多媒體協議:RTMP/RTSP這些,在媒體文件之上封裝網絡傳輸與控制的相關信息。

  音視頻流:流分為音頻流,視頻流,字幕流等等這些,其中 媒體文件里可能包含一個或多個音視頻流,而每個視頻流是相同屬性(長寬,像素格式等)的原始視頻數據編碼成的信息流。

  復用:舉個例子,把一個音頻流與一個視頻流合成一個媒體文件,就是復用。

  解復用:如上,把一個媒體文件分解成相應的音頻流與視頻流。

FFmpeg主要對象

  AVFormatContext:多媒體協議或是媒體文件,如果是協議,會解析出協議里包含的媒體文件信息,這個類主要如今讀/寫壓縮包,讀/寫文件頭與文件尾等方法。你可以把這對象認為是一個媒體文件。

  AVCodec:編解碼,注意編碼與解碼或是用同一codecId,但是對象不同,這個對象主要包含一些函數指針,告訴如何把frame->packet/packet->frame.

  AVCodecContext:編解碼環境,簡單來說,AVCodec是說如何編解碼,這個就是告訴他相應屬性設置,如對應視頻來說,長寬,以及編碼相應設置是否包含B楨,GOP是多少都在這,可以這么理解,我們假設AVCodec與AVCodecContext如果是一個類,那么AVCodec相當於里面的方法集合,AVCodecContext相當於里面的變量集合。

  AVStream:媒體文件一般來說至少包含一個音頻流或是視頻流,在復用/解復用到編解碼之間是個承上啟下的關系。你可以理解AVStream包含音視頻編碼的信息列表。AVStream也要包含相應的AVCodecContext包含的編解碼信息,后面會講這二者信息在復用與解復用從那復制到那。

  AVFrame:音視頻原始信息,包含一個定長的數據信息。

  AVPacket:音視頻編碼信息,包含一個不定長的數據信息。

FFmpeg常見API分析

  讀一個媒體文件相應動作與API解析。

  avformat_open_input 根據媒體文件/協議地址打開AVFormatContext。

  avformat_find_stream_info 查找AVFormatContext里對應的音視頻流索引。

  avcodec_find_decoder 根據索引打開對應音頻與視頻流解碼器。

  avcodec_alloc_context3 根據解碼器生成解碼器環境。

  avcodec_parameters_to_context 把流的解碼器參數(圖像長寬,音頻基本屬性以及frame_size)復制到解碼器環境.

  avcodec_open2 打開解碼器環境。

  av_read_frame 從媒體文件AVFormatContext讀每個AVPacket。

  avcodec_send_packet 根據對應AVPacket的索引,發給對應流的解碼器解碼。

  avcodec_receive_frame 得到解碼器解碼后的原始數據,如在視頻流中,因P楨B楨關系,一個AVPacket並不一定能得到一個AVFrame,比如P楨要考慮前后,所以可能到后幾個Packet的時候,一下讀出多楨數據,所以avcodec_send_packet/avcodec_receive_frame的寫法會是這樣一個情況。

  寫入媒體文件相應動作與API解析(非IO模式):

  avformat_alloc_output_context2 根據對應格式生成一個AVFormatContext,不同格式會固定一些數據,比如上FLV格式,音頻流與視頻流的時間基就是毫秒,我試着改過這值,后面也會在avformat_write_header之后重新改回來。

  avcodec_find_encoder/avcodec_find_encoder_by_name 選擇自己想要的編碼器。

  avcodec_alloc_context3 選擇選擇的編碼器生成編碼器環境,不同與上面 的解碼過程,這里我們要自己填充相應信息,如圖像編碼需要知道長寬,碼率,gop等設置。

  avcodec_open2 打開解碼器環境。

  avformat_new_stream 生成相應音視頻流信息,填充對應編碼器到AVFormatContext里。

  avcodec_parameters_from_context 把編碼器設置的參數復制到流中。

  avio_open 協議的解析以及協議的操作指針,如何讀寫協議信息,協議頭,協議內容等。

  avformat_write_header 寫入頭信息。

  avcodec_send_frame 把末壓縮數據給編碼器。

  avcodec_receive_packet 拿到編碼后的數據,和解碼類似,P楨決定不可能一Frame一packet,可能要前后幾個Frame,才能得到一系列的packet.

  av_interleaved_write_frame 把編碼后的音視頻數據交叉寫入媒體文件中

  av_write_trailer 結束寫入,根據寫入的所有數據填充一部分需要計算的值。

  還有一種IO模式,可以利用關鍵楨圖像與音頻數據直接寫入IO中,然后直接從楨中讀取相應音視頻流的屬性拿來直接用,用來不確定視頻流長寬等情況下使用。

  從API可以看讀寫的差異,讀的媒體文件AVFormatContext里面的信息全有,讀到流,從流里得到解碼信息,打開解碼器,從AVFormatContext讀每個包,用解碼器解碼包。寫入媒體文件就是生成一個空白的AVFormatContext,然后打開選擇的編碼器,生成流,然后寫入流中每楨數據,使用編碼器編碼后定稿文件。

  圖像上相關的坑,媒體文件一般使用是YUV的P格式,這個格式YUV分塊保存,還有相應align的概念,舉個例子,假設你寬是1080,但是在YUV分塊中,Y寬度可能是1088(假設當時使用32定齊),其中每行數據索引處1080-1087以0填充,相應的圖像處理我全部提出來在OEIP處理,在OEIP中圖像數據全給GPU處理,需要的是緊湊數據,所以需要用av_image_copy_to_buffer/av_image_fill_arrays處理。

  音頻上相關的坑,媒體文件多聲道也是用的P格式,而音頻采集與播放設備一般用的I格式,所以一般要用swr_convert轉換,注意一定要理解相應音頻參數,傳入的值會影響生成的BUFFER塊大小,可能會導致閃退等問題,音頻播放使用SDL庫,就幾個API調用就行,在這就不說了,可以查看相應OEIP里的代碼處理,音頻采集Winodws用的是WASAPI。

  時間基的概念:音視頻流都有一個時間基的概念,這個比較重要,flv的音視頻都是(1,1000),如果是mp4,視頻的時間基為(1,90000),音頻一般設為對應采樣率。時間基,你可以簡單理解為1秒內刻度,flv的流對應就是毫秒,而mp4視頻流的時間基對應的是1/90毫秒,什么意義了,比如你視頻對應的是25楨,在flv里,每楨相隔40個時間基,而在mp4里,相隔360個時間基,在編碼時,我們需要把frams上的pts/dts/duration以對應時間基為單位,注意轉換,在OEIP中,我們把所有轉出/轉入與用戶有關的時間全是毫秒,其中轉換我們內部自己處理。

  故到此 OEIP 中,可用的輸入輸出源新增媒體文件/協議,同樣,這些功能在Unity3D/UE4里很方便展示,比如把媒體文件/協議里的內容直接展示成對應Unity3D/UE4里的Texture2D顯示,或是把Unity3D/UE4里的Texture2D/RTT里的數據保存視頻或是推送出去。

  參考:
  https://www.cnblogs.com/leisure_chn/category/1351812.html 葉余 FFmpeg開發


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM