ffmpeg針對視頻和音頻裸碼封裝一點感想


換個新工作,需求是將實時接收過來的音頻和視頻封裝成mpegts格式,自然想到的是用ffmpeg進行編碼,網上找了下這方面資料,無奈找了半天 沒找到相應的資料,關於ffmpeg編譯命令行的到是非常多,所以自己就研究總結下,參考ffmpeg中的例子,個人用ffmpeg時間不多,理解有限, 可能有錯誤之處,望指點一二。

輸入參數:

輸入視頻流:h264,YUV420,分辨率1280*720,幀率25,碼率4M

輸入音頻流:pcm,采樣率8K,8位,單聲道

輸出參數:

音頻:AAC,采樣率44100,16位,雙聲道

視頻:H264

mpegts

ffmpeg中對文件的輸入和輸出用一個結構體AVFormatContext來指定,其中AVInputFormat指定的是輸入,AVOutputFormat指定的是輸出,輸出格式用函數av_guess_format來查找指定格式

AVOutputFormat *fmt = av_guess_format("mpegts",NULL,NULL);

AVFormatContex *oc = avformat_alloc_context();

oc->oformat= fmt;

其 中無論是AVInputFormat還是AVOutputFormat,都包含一個結構體AVStream,這個結構體表示一個單一的流,音頻流,或者視 頻流,無論輸入文件是用何種容器格式包裝存儲,他都將視頻流和音頻流分開放到這個AVStream中,這樣我們可以根據這兩個流分別對音頻或者視頻進行處 理,可以對視頻進行解碼,或者編碼,或者什么都不做,原樣輸出,也就是ffmpeg命令行中的copy。

其中創建輸出流分兩總情況:

1.音、視頻編碼:

AVCodec *codec = avcodec_find_encodec(CODEC_ID_AAC);//查找AAC編碼器,視頻就查找其他格式的編碼器

AVStream *audio_st = avformat_new_stream(oc,codec);//其中就已經動態分配AVCodexContext,並將codec指定到AVCodexContext

2.音視頻不編碼

AVStream *video_st = avformat_new_stream(oc,NULL);//不指定編碼器,這樣實現就是ffmpeg命令行的copy,當然其他的參數賦值要根據AVInputFormat的AVStream的AVCodecContext來賦值。

完成上訴兩個過程

都要進行下一步就是對AVStream中的AVCodexContext進行手動初始化

其 中共有的codec_id,bit_rate,codec_type,id等都需要賦值,其中id和avformat_new_stream初始化順序有 關,如果先初始化的是視頻,那么視頻的id就是0,音頻就是1,其中的視頻AVStream中的AVCodecContext中的time_base這個 表示幀率

這樣到后面的AVPacket中的stream_index就要根據這個id來指定當前packet是視頻包還是音頻包

初始化完畢后需要判斷是否對輸出標志位進行設置

if(oc->oformat->flags & AVFMT_GLOBALHEADER)

{

    vodeo_st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;

}

如果進行編碼的話

就需要進行打開編碼器,否則就不需要

avcodec_open2

打開編碼器的話,就需要一個結構體AVFrame

AVFrame這個存放一個解碼后的原始數據,音頻就是PCM,視頻就是YUV

而AVPacket這個存放的是編碼數據,音頻可能是AAC或者其他格式,視頻就是H264等一楨數據,其中的flag表示是否是關鍵幀,這些在后續中都要手動設置的。

先說音頻編碼:

AVFrame有幾個值需要賦值

其中的nb_samples要賦值輸出流中AVStream的AVCodecContext的frame_size,表示一個AVFrame中包含多少個音頻幀,雖然音頻沒有幀的概念,暫且這么理解吧

format要賦值成輸出流AVStream的AVCodecContext的sample_fmt,指定AVFrame的音頻格式,可能是AV_SAMPLE_FMT_U8或者是AV_SAMPLE_S16,如果是U8還得需要音頻重采樣。

channel_layout這個不用說,聲道數目,音頻專用

這樣我們還需要一個緩沖區放入到AVFramleavede中,緩沖區的大小計算可以根據函數av_samples_get_buffer_size來計算

然后將此緩沖區賦值到AVFrame中,通過函數avcodec_fill_audio_frame

這樣前期初始化完畢,接下來就處理就收到的視頻幀和音頻了

寫文件第一幀時,一定要關鍵幀,而一些特殊容器如MP4或flv關鍵幀前還需要加pps和sps,如果想播放過程中拖放視頻,那每個關鍵幀都得有sps和pps

avio_open,avformat_write_header

對於視頻不需要編碼,那就直接封裝AVPacket

其 中的pts和dts如果沒有B幀,值相等即可,而duration就按照90000除以幀率來賦值就可以,其中stream_index要和視頻流 AVStream中的AVCodecContext的id要一致,如果是關鍵幀,就將flags設置AV_PKT_FLAG_KEY

然后直接av_interleaved_write_frame即可。

對 於音頻能復雜些,畢竟涉及到編碼,重采樣,新版ffmpeg中對音頻定義有兩種格式,一種是平面的,一中是非平面的,目前我這個需求中是對非平面進行處 理,處理音頻數據有一點要注意,音頻數據一定要填滿AVFrame的緩沖區,然后在進行編碼。否者出來的效果就不受控制了。

項目中接收到的 音頻數據是定長的,所以需要拼接到音頻緩沖區大小,在寫到音頻緩沖區之前,先進行重采樣處理,然后將重采樣的數據拼接到AVFrame的音頻緩沖區中,好 在ffmpeg中提供一個AVFifoBuffer,這個用起來很方便,這樣每次收到數據寫到av_fifo_generic_write中,另一個判斷 當前緩沖區是否達到AVFrame緩沖區大小即可,達到后進行編碼,有一點說明的是每次編碼前,AVFrame的pts要進行賦值,否則編碼出的pts是 負值,就沒有辦法進行時間基數的轉換了。一般來說先定義一個int64_t lastpts = 0;編碼前AVFrame->pts = lastpts;然后lastpts = lastpts + AVFrame->nb_samples;

進行avcodec_encode_audio2后,packet就是編碼的數據,然后進行時間基的轉換,用av_rescale_q函數時間。

最后寫到av_interleaved_write_frame即可,因項目音頻重采樣有點問題,先不上代碼了,后期再加上吧。


免責聲明!

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



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