換個新工作,需求是將實時接收過來的音頻和視頻封裝成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即可,因項目音頻重采樣有點問題,先不上代碼了,后期再加上吧。