h264源碼
Ⅰ 如何讓WebRTC支持H264
編譯選項調整
WebRTC能支持H264,但在Linux下編譯時默認未啟用。關鍵在於rtc_use_h264開關,控制著是否使用H264。通過在webrtc/webrtc.gni文件中調整proprietary_codecs選項,即可開啟H264支持。
調整proprietary_codecs為true後,打開rtc_use_h264選項,使能OpenH264編碼支持。WebRTC內部會使用ffmpeg來解碼H264,需要確保rtc_initialize_ffmpeg選項為true以使ffmpeg初始化。
調整配置後,運行gn gen命令生成構建文件,驗證選項是否生效。使用命令檢查Current Value為true時,說明已成功啟用H264支持。
要完全啟用H264,還需調整C++代碼中FFMPEG_H264_DECODER宏,確保avcodec_register_all()方法注冊H264解碼器。
此外,注意Linux編譯WebRTC時,生成的構建文件可能缺少ffmpeg的H264解碼器源代碼。因此,在third_party/ffmpeg/ffmpeg_generated.gni文件中打開相關條件,確保H264解碼器可用。
在C++音視頻開發學習中,需要調整代碼來改變默認的編解碼順序,將H264置於優先位置,以適應不同的應用需求。
使用特定模塊編譯並重新構建native app後,H264支持即可在WebRTC中生效。
關於WebRTC使用H264會黑屏的問題,WebRTC以出色的QoS而著稱,支持VP8和VP9視頻,但在使用H264時,質量可能不如VP8/VP9,存在卡頓、時延增加和塊狀效應等問題。
深入分析WebRTC的QoS策略後發現,H264的FEC(前向糾錯)被關閉,這與VP8/VP9不同。此外,H264的FEC存在BUG,可能導致解碼失敗,引起視頻卡頓。H264的FEC機制與VP8/VP9不兼容,以及RTP組包協議的差異,導致H264無法啟用時間分級。
綜上所述,WebRTC使用H264時,需調整編譯選項、代碼配置以及理解其QoS策略與編碼器特性,以確保穩定性和性能。
Ⅱ FFmpeg視頻編碼 YUV420P編碼H264
//第一步:注冊組件->編碼器、解碼器等等…
av_register_all();
//第二步:初始化封裝格式上下文->視頻編碼->處理為視頻壓縮數據格式
AVFormatContext *avformat_context = avformat_alloc_context();
//注意事項:FFmepg程序推測輸出文件類型->視頻壓縮數據格式類型
const char *coutFilePath = [outFilePath UTF8String];
//得到視頻壓縮數據格式類型(h264、h265、mpeg2等等...)
AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);
//指定類型
avformat_context->oformat = avoutput_format;
//第三步:打開輸出文件
//參數一:輸出流
//參數二:輸出文件
//參數三:許可權->輸出到文件中
if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0) {
NSLog(@"打開輸出文件失敗");
return;
}
//第四步:創建輸出碼流->創建了一塊內存空間->並不知道他是什麼類型流->希望他是視頻流
AVStream *av_video_stream = avformat_new_stream(avformat_context, NULL);
//第五步:查找視頻編碼器
//1、獲取編碼器上下文
AVCodecContext *avcodec_context = av_video_stream->codec;
//2、設置編解碼器上下文參數->必需設置->不可少
//目標:設置為是一個視頻編碼器上下文->指定的是視頻編碼器
//上下文種類:視頻解碼器、視頻編碼器、音頻解碼器、音頻編碼器
//2.1 設置視頻編碼器ID
avcodec_context->codec_id = avoutput_format->video_codec;
//2.2 設置編碼器類型->視頻編碼器
//視頻編碼器->AVMEDIA_TYPE_VIDEO
//音頻編碼器->AVMEDIA_TYPE_AUDIO
avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
//2.3 設置讀取像素數據格式->編碼的是像素數據格式->視頻像素數據格式->YUV420P(YUV422P、YUV444P等等...)
//注意:這個類型是根據你解碼的時候指定的解碼的視頻像素數據格式類型
avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;
//2.4 設置視頻寬高->視頻尺寸
avcodec_context->width = 640;
avcodec_context->height = 352;
//2.5 設置幀率->表示每秒25幀
//視頻信息->幀率 : 25.000 fps
//f表示:幀數
//ps表示:時間(單位:每秒)
avcodec_context->time_base.num = 1;
avcodec_context->time_base.den = 25;
//2.6 設置碼率
//2.6.1 什麼是碼率?
//含義:每秒傳送的比特(bit)數單位為 bps(Bit Per Second),比特率越高,傳送數據速度越快。
//單位:bps,"b"表示數據量,"ps"表示每秒
//目的:視頻處理->視頻碼率
//2.6.2 什麼是視頻碼率?
//含義:視頻碼率就是數據傳輸時單位時間傳送的數據位數,一般我們用的單位是kbps即千位每秒
//視頻碼率計算如下?
//基本的演算法是:【碼率】(kbps)=【視頻大小 - 音頻大小】(bit位) /【時間】(秒)
//例如:Test.mov時間 = 24,文件大小(視頻+音頻) = 1.73MB
//視頻大小 = 1.34MB(文件佔比:77%) = 1.34MB * 1024 * 1024 * 8 = 位元組大小 = 468365位元組 = 468Kbps
//音頻大小 = 376KB(文件佔比:21%)
//計算出來值->碼率 : 468Kbps->表示1000,b表示位(bit->位)
//總結:碼率越大,視頻越大
avcodec_context->bit_rate = 468000;
//2.7 設置GOP->影響到視頻質量問題->畫面組->一組連續畫面
//MPEG格式畫面類型:3種類型->分為->I幀、P幀、B幀
//I幀->內部編碼幀->原始幀(原始視頻數據)
// 完整畫面->關鍵幀(必需的有,如果沒有I,那麼你無法進行編碼,解碼)
// 視頻第1幀->視頻序列中的第一個幀始終都是I幀,因為它是關鍵幀
//P幀->向前預測幀->預測前面的一幀類型,處理數據(前面->I幀、B幀)
// P幀數據->根據前面的一幀數據->進行處理->得到了P幀
//B幀->前後預測幀(雙向預測幀)->前面一幀和後面一幀
// B幀壓縮率高,但是對解碼性能要求較高。
//總結:I只需要考慮自己 = 1幀,P幀考慮自己+前面一幀 = 2幀,B幀考慮自己+前後幀 = 3幀
// 說白了->P幀和B幀是對I幀壓縮
//每250幀,插入1個I幀,I幀越少,視頻越小->默認值->視頻不一樣
avcodec_context->gop_size = 250;
//2.8 設置量化參數->數學演算法(高級演算法)->不講解了
//總結:量化系數越小,視頻越是清晰
//一般情況下都是默認值,最小量化系數默認值是10,最大量化系數默認值是51
avcodec_context->qmin = 10;
avcodec_context->qmax = 51;
//2.9 設置b幀最大值->設置不需要B幀
avcodec_context->max_b_frames = 0;
//第二點:查找編碼器->h264
//找不到編碼器->h264
//重要原因是因為:編譯庫沒有依賴x264庫(默認情況下FFmpeg沒有編譯進行h264庫)
//第一步:編譯h264庫
AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);
if (avcodec == NULL) {
NSLog(@"找不到編碼器");
return;
}
NSLog(@"編碼器名稱為:%s", avcodec->name);
//第六步:打開h264編碼器
//缺少優化步驟?
//編碼延時問題
//編碼選項->編碼設置
AVDictionary *param = 0;
if (avcodec_context->codec_id == AV_CODEC_ID_H264) {
//需要查看x264源碼->x264.c文件
//第一個值:預備參數
//key: preset
//value: slow->慢
//value: superfast->超快
av_dict_set(¶m, "preset", "slow", 0);
//第二個值:調優
//key: tune->調優
//value: zerolatency->零延遲
av_dict_set(¶m, "tune", "zerolatency", 0);
}
if (avcodec_open2(avcodec_context, avcodec, ¶m) < 0) {
NSLog(@"打開編碼器失敗");
return;
}
//第七步:寫入文件頭信息
avformat_write_header(avformat_context, NULL);
//第8步:循環編碼yuv文件->視頻像素數據(yuv格式)->編碼->視頻壓縮數據(h264格式)
//8.1 定義一個緩沖區
//作用:緩存一幀視頻像素數據
//8.1.1 獲取緩沖區大小
int buffer_size = av_image_get_buffer_size(avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
1);
//8.1.2 創建一個緩沖區
int y_size = avcodec_context->width * avcodec_context->height;
uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size);
//8.1.3 打開輸入文件
const char *cinFilePath = [inFilePath UTF8String];
FILE *in_file = fopen(cinFilePath, "rb");
if (in_file == NULL) {
NSLog(@"文件不存在");
return;
}
//8.2.1 開辟一塊內存空間->av_frame_alloc
//開辟了一塊內存空間
AVFrame *av_frame = av_frame_alloc();
//8.2.2 設置緩沖區和AVFrame類型保持一直->填充數據
av_image_fill_arrays(av_frame->data,
av_frame->linesize,
out_buffer,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
1);
int i = 0;
//9.2 接收一幀視頻像素數據->編碼為->視頻壓縮數據格式
AVPacket *av_packet = (AVPacket *) av_malloc(buffer_size);
int result = 0;
int current_frame_index = 1;
while (true) {
//8.1 從yuv文件裡面讀取緩沖區
//讀取大小:y_size * 3 / 2
if (fread(out_buffer, 1, y_size * 3 / 2, in_file) <= 0) {
NSLog(@"讀取完畢...");
break;
}else if (feof(in_file)) {
break;
}
//8.2 將緩沖區數據->轉成AVFrame類型
//給AVFrame填充數據
//8.2.3 void * restrict->->轉成->AVFrame->ffmpeg數據類型
//Y值
av_frame->data[0] = out_buffer;
//U值
av_frame->data[1] = out_buffer + y_size;
//V值
av_frame->data[2] = out_buffer + y_size * 5 / 4;
av_frame->pts = i;
//注意時間戳
i++;
//總結:這樣一來我們的AVFrame就有數據了
//第9步:視頻編碼處理
//9.1 發送一幀視頻像素數據
avcodec_send_frame(avcodec_context, av_frame);
//9.2 接收一幀視頻像素數據->編碼為->視頻壓縮數據格式
result =avcodec_receive_packet(avcodec_context, av_packet);
//9.3 判定是否編碼成功
if (result == 0) {
//編碼成功
//第10步:將視頻壓縮數據->寫入到輸出文件中->outFilePath
av_packet->stream_index = av_video_stream->index;
result =av_write_frame(avformat_context, av_packet);
NSLog(@"當前是第%d幀", current_frame_index);
current_frame_index++;
//是否輸出成功
if (result < 0) {
NSLog(@"輸出一幀數據失敗");
return;
}
}
}
//第11步:寫入剩餘幀數據->可能沒有
flush_encoder(avformat_context, 0);
//第12步:寫入文件尾部信息
av_write_trailer(avformat_context);
//第13步:釋放內存
avcodec_close(avcodec_context);
av_free(av_frame);
av_free(out_buffer);
av_packet_free(&av_packet);
avio_close(avformat_context->pb);
avformat_free_context(avformat_context);
fclose(in_file);