一、H264壓縮編碼
1.1 H264 中的 I 幀、P幀和 B幀
H264 使用幀內壓縮和幀間壓縮的方式提高編碼壓縮率;H264 采用了獨特的 I 幀、P 幀和 B 幀策略來實現,連續幀之間的壓縮;
1.2 其他概念
GOP(圖像組):一個IDR幀到下一個IDR幀之間間隔了多少個幀
IDR:一個序列的第一個圖像叫做IDR圖像(立即刷新圖像),IDR圖像都是I幀圖像。I幀不用參考任何幀,但之后的P幀和B幀是有可能參考這個I幀之前的幀。IDR幀不允許后面PB參考前面的幀。IDR的核心作用是為了序列出現重大錯誤后重新同步,不用參考IDR之前的圖像的數據來解碼
1.3 NLU
視頻流至少發從一幀SPS和一幀PPS來告訴客戶端視頻流信息
H.264原始碼流(裸流)是由?個接?個NALU組成。它的功能分為兩層,VCL (視頻編碼層) 和
NAL (?絡提取層)。VCL:負責壓縮視頻;NAL:把VCL壓縮的視頻封裝成NLU幀
1、NLU幀包括三個部分
[StartCode] [NALU Header] [NALU Payload]
NALU Header是NALU的頭部
RBSP是I、P、B幀的數據。
NLU幀的結束也是根據00 00 00 01或00 00 01來判斷的
2、NLU Header第一個字節
3、NLU的類型
nal_unit_type | NAL單元和RBSP語法結構的內容 |
---|---|
0 | 未指定 |
1 | 一個非IDR圖像的編碼條帶slice_layer_without_partitioning_rbsp( ) |
2 | 編碼條帶數據分割塊A slice_data_partition_a_layer_rbsp( ) |
3 | 編碼條帶數據分割塊B slice_data_partition_b_layer_rbsp( ) |
4 | 編碼條帶數據分割塊C slice_data_partition_c_layer_rbsp( ) |
5 | IDR圖像的編碼條帶(?) slice_layer_without_partitioning_rbsp ( ) |
6 | 輔助增強信息 (SEI)?sei_rbsp( ) |
7 | 序列參數集 seq_parameter_set_rbsp( ) |
8 | 圖像參數集 pic_parameter_set_rbsp( ) |
9 | 訪問單元分隔符 access_unit_delimiter_rbsp( ) |
10 | 序列結尾 end_of_seq_rbsp( ) |
11 | 流結尾? end_of_stream_rbsp( ) |
12 | 填充數據 filler_data_rbsp( ) |
13 | 序列參數集擴展9 seq_parameter_set_extension_rbsp( ) |
14...18 | 保留 |
19 | 未分割的輔助編碼圖像的編碼條帶 slice_layer_without_partitioning_rbsp( ) |
20...23 | 保留 |
24...31 | 未指定 |
1.4?H264 annexb模式
H264有兩種封裝
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
AVBSFContext *bsf_ctx = NULL;
// 2 初始化過濾器上下?
av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
// 3 添加解碼器屬性
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
av_bsf_init(bsf_ctx);
1. Annex B內部數據,對應的了前面講解的內容
[4字節起始碼] + [SPS NALU數據]
[4字節起始碼] + [PPS NALU數據]
[3字節起始碼] + [I幀NALU數據]
[3字節起始碼] + [P幀NALU數據]
[3字節起始碼] + [B幀NALU數據]
...(后續NALU以此類推)
輸出的文件是H.264視頻文件
2. MP4直接的碼流對應下圖右邊,開頭記錄的是NALU幀的長度,都是4字節
二、MP4->H.264代碼
#include <iostream>extern "C"{#include <libavutil/log.h>#include <libavformat/avio.h>#include <libavformat/avformat.h>#include <libavcodec/bsf.h>
}using namespace std;static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{av_strerror(errnum, err_buf, 128);return err_buf;
}int main(int argc, char **argv) {string inputFile = "believe.mp4";string outputFile = "believe.h264"; FILE * outfp = fopen(outputFile.c_str(),"wb");printf("in:%s out:%s\n", inputFile.c_str(), outputFile.c_str());// 創建AVFormatContext上下文AVFormatContext * ifmt_ctx = avformat_alloc_context();if (!ifmt_ctx) {printf("[error] Could not allocate context.\n");return -1;}int ret = avformat_open_input(&ifmt_ctx, inputFile.c_str(), NULL, NULL);if (ret != 0) {printf("[error] avformat_open_input: %s\n", av_get_err(ret));return -1;}ret = avformat_find_stream_info(ifmt_ctx, NULL);if (ret < 0) {printf("[error] avformat_find_stream_info: %s\n", av_get_err(ret));avformat_close_input(&ifmt_ctx);return -1;}// 查找出哪個碼流是video/audio/subtitlesint videoindex = -1;videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);if(videoindex == -1) {printf("[error] Didn't find a video stream.\n");avformat_close_input(&ifmt_ctx);return -1;}// 創建AVPacket包AVPacket * pkt = av_packet_alloc();av_init_packet(pkt);// 1 獲取一個名為"h264_mp4toannexb"的比特流過濾器// 用于將MP4容器中的H.264視頻流轉換為Annex B格式的H.264流const AVBitStreamFilter * bsfilter = av_bsf_get_by_name("h264_mp4toannexb");AVBSFContext * bsf_ctx = NULL;// 2 初始化過濾器上下文av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;// 3 復制視頻流的編解碼參數到過濾器上下文avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);av_bsf_init(bsf_ctx);int file_end = 0;while (0 == file_end) {if((ret = av_read_frame(ifmt_ctx, pkt)) < 0) {// 沒有更多包可讀file_end = 1;printf("read file end: ret:%d\n", ret);}if(ret == 0 && pkt->stream_index == videoindex) {int input_size = pkt->size;int out_pkt_count = 0;if (av_bsf_send_packet(bsf_ctx, pkt) != 0) // bitstreamfilter內部去維護內存空間{av_packet_unref(pkt); // 你不用了就把資源釋放掉continue; // 繼續送}av_packet_unref(pkt); // 釋放資源while(av_bsf_receive_packet(bsf_ctx, pkt) == 0) {out_pkt_count++;// printf("fwrite size:%d\n", pkt->size);size_t size = fwrite(pkt->data, 1, pkt->size, outfp);if(size != pkt->size){printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);}av_packet_unref(pkt);}if(out_pkt_count >= 2){printf("cur pkt(size:%d) only get 1 out pkt, it get %d pkts\n", input_size, out_pkt_count);}// 注釋掉前面代碼,可以直接保存mp4流// size_t size = fwrite(pkt->data, 1, pkt->size, outfp);// if(size != pkt->size)// {// printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);// }// av_packet_unref(pkt);}else {if(ret == 0) av_packet_unref(pkt); // 釋放內存}}if (outfp) fclose(outfp);if (bsf_ctx) av_bsf_free(&bsf_ctx);if (pkt) av_packet_free(&pkt);if (ifmt_ctx) avformat_close_input(&ifmt_ctx);printf("finish\n");return 0;
}