視頻編碼的過程就是將YUV的像素格式編碼成H264的壓縮格式
YUV:視頻像素格式
H264:視頻壓縮數(shù)據(jù)格式
流程圖

步驟詳解
1、注冊組件
av_register_all();
2、初始化化封裝格式上下文
AVFormatContext* avformat_context = avformat_alloc_context();
獲取視頻壓縮格式類型(h254、h265、mpeg2等)
AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);
3、打開輸出文件
avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE)
參數(shù)一:輸出流
參數(shù)二:輸出文件
參數(shù)三:權(quán)限->輸出到文件中
4、創(chuàng)建輸出碼流
AVStream* av_video_stream = avformat_new_stream(avformat_context, NULL);
注意:這里只是開辟了一塊內(nèi)存空間,還不知道他是什么類型
5、查找視頻編碼器(重點)
5.1、獲取上下文
AVCodecContext *avcodec_context = av_video_stream->codec;
上下文種類:視頻解碼器、視頻編碼器、音頻解碼器、音頻編碼器
5.2、設(shè)置為視頻編碼器上下文
1、設(shè)置視頻編碼器ID
avcodec_context->codec_id = avoutput_format->video_codec;
2、設(shè)置編碼器類型
avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
3、設(shè)置讀取像素格式
//注意:這個類型是根據(jù)你解碼的時候指定的解碼的視頻像素數(shù)據(jù)格式類型
avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;
4、設(shè)置視頻寬高
avcodec_context->width = 640;
avcodec_context->height = 352;
這里的尺寸是通過一定工具查看的,不同的視頻不一樣。
5、設(shè)置幀率(重點)
avcodec_context->time_base.num = 1;
avcodec_context->time_base.den = 25;
這兩個參數(shù)表示幀率為25.000fps
幀率越大越流暢。視頻卡頓說明掉幀了。
6、設(shè)置碼率(重點)
碼率:也叫比特率,單位bps。也就是每秒傳送的比特數(shù),碼率越高傳送速度越快。
視頻碼率:單位為kbps,千位每秒
視頻碼率的計算方式:視頻文件大小/視頻時間
注意:一個視頻的總文件包括視頻文件和音頻文件,上面公式中,是指視頻文件的大小。
e.g 一個視頻,視頻文件的大小是1.34MB,時長是24s,那么他的視頻幀率為:1.34 * 1024 * 8 / 24 / 1000 = 468 Kbps
每個文件的碼率不一樣,都要經(jīng)過計算得到
avcodec_context->bit_rate = 468000;
從上面的分析可以看出:碼率越大,視頻越大
7、設(shè)置GOP(重點)
GOP:畫面組,一組連續(xù)畫面(一個完整的畫面)
MPEG格式的畫面類型有3種:
I幀:內(nèi)部編碼幀,原始幀,也叫關(guān)鍵幀。視頻的第一幀都是I幀,可獨立編碼。
P幀:向前預(yù)測幀。編碼需要依賴前一幀。
B幀:前后預(yù)測幀,也叫雙向預(yù)測幀。編碼需要依賴本幀與前一幀和后一幀的對比。B幀壓縮率高,但對性能要求高

avcodec_context->gop_size = 250;
這里設(shè)置250,表示每250幀插入一個I幀。I幀約少,視頻越小。但過分的少,會導(dǎo)致視頻編碼失敗,所以要適量。
8、設(shè)置量化參數(shù)(難點,我們一般設(shè)置默認值)
avcodec_context->qmin = 10;
avcodec_context->qmax = 51;
量化系數(shù)越小,視頻越是清晰。一般情況下都是默認值,最小量化系數(shù)默認值是10,最大量化系數(shù)默認值是51。
9、設(shè)置B幀最大值
avcodec_context->max_b_frames = 0;
我們設(shè)置為0,表示不需要B幀
5.3、查找編碼器h264
查找編碼器h264:找不到???深坑
原因:編譯庫沒有依賴x264庫(默認情況下FFmpeg沒有編譯進行h264庫)
如何編譯x264庫?
1、下載x264的庫
2、編譯x264的.a靜態(tài)庫,也可以便以動態(tài)庫,根據(jù)需要而定
3、重新編譯ffmpeg庫,使ffmpeg依賴2中生成的x264庫
4、替換代碼中之前生成的ffmpeg庫
6、打開視頻編碼器
對于h264解碼器,要多設(shè)置參數(shù)如下
AVDictionary *param = 0;
if (avcodec_context->codec_id == AV_CODEC_ID_H264) {
// 查看h264.c源碼
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
}
打開視頻編碼器
if (avcodec_open2(avcodec_context, avcodec, ¶m) < 0) {
NSLog(@"打開編碼器失敗");
return;
}
7、寫入頭文件信息
avformat_write_header(avformat_context, NULL);
8、循環(huán)編碼視頻像素數(shù)據(jù)為視頻壓縮數(shù)據(jù)(YUV-->h264)-- 視頻編碼處理
1、申請緩沖區(qū)
av_frame_alloc()
av_image_fill_arrays
(AVPacket *)av_malloc(buffer_size)
2、將緩沖區(qū)數(shù)據(jù)填充到AVFrame中
3、avcodec_send_frame
4、avcodec_receive_packet
9、將編碼后的視頻壓縮數(shù)據(jù)寫入文件中
av_packet->stream_index = av_video_stream->index;
result = av_write_frame(avformat_context, av_packet);
10、寫入剩余幀數(shù)據(jù)(可能沒有)
int flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index) {
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_video2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame) {
ret = 0;
break;
}
NSLog(@"Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n", enc_pkt.size);
/* mux encoded frame */
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
11、寫入文件尾部信息
av_write_trailer(avformat_context);
12、釋放內(nèi)存,關(guān)閉編碼器等等
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);
代碼
- (void)videoEncodeWithInputPath:(NSString *)inputPath outputPath:(NSString *)outputPath {
//第一步:注冊組件->編碼器、解碼器等等…
av_register_all();
//第二步:初始化封裝格式上下文
AVFormatContext* avformat_context = avformat_alloc_context();
const char* coutFilePath = [outputPath UTF8String];
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;
}
//第四步:創(chuàng)建輸出碼流
AVStream* av_video_stream = avformat_new_stream(avformat_context, NULL);
//第五步:查找視頻編碼器
//1、獲取編碼器上下文
AVCodecContext *avcodec_context = av_video_stream->codec;
//2、設(shè)置編解碼器上下文參數(shù)
avcodec_context->codec_id = avoutput_format->video_codec;
avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;
avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;
avcodec_context->width = 640;
avcodec_context->height = 352;
// 設(shè)置幀率25fps
avcodec_context->time_base.num = 1;
avcodec_context->time_base.den = 25;
// 設(shè)置碼率
avcodec_context->bit_rate = 468000;
// 設(shè)置GOP
avcodec_context->gop_size = 250;
// 設(shè)置量化參數(shù)
avcodec_context->qmin = 10;
avcodec_context->qmax = 51;
avcodec_context->max_b_frames = 0;
// 第六步:打開編碼器
// 1、查找編碼器
AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);
if (avcodec == NULL){
NSLog(@"找不到解碼器");
return;
}
NSLog(@"解碼器名稱為:%s", avcodec->name);
// 若是h264編碼器,要設(shè)置一些參數(shù)
AVDictionary *param = 0;
if (avcodec_context->codec_id == AV_CODEC_ID_H264) {
// 查看h264.c源碼
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
}
// 2、打開編碼器
if (avcodec_open2(avcodec_context, avcodec, ¶m) < 0) {
NSLog(@"打開編碼器失敗");
return;
}
// 第七步:寫入頭文件信息
int flag = avformat_write_header(avformat_context, NULL);
// 第八步:循環(huán)編碼YUV文件為H264
// 1、開辟緩沖區(qū)
int buffer_size = av_image_get_buffer_size(avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
1);
int y_size = avcodec_context->width * avcodec_context->height;
uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
const char *cinFilePath = [inputPath UTF8String];
FILE *in_file = fopen(cinFilePath, "rb");
if (in_file == NULL) {
NSLog(@"輸入文件不存在");
return;
}
// 2、內(nèi)存空間填充
AVFrame *av_frame = av_frame_alloc();
av_image_fill_arrays(av_frame->data,
av_frame->linesize,
out_buffer,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
1);
// 3、開辟packet
AVPacket *av_packet = (AVPacket *)av_malloc(buffer_size);
int i = 0;
int result = 0;
int current_frame_index = 0;
// 4、循環(huán)編碼
while (true) {
// 從yuv文件里面讀取緩沖區(qū)
//讀取大?。簓_size * 3 / 2
if (fread(out_buffer, 1, y_size * 3 / 2, in_file) <= 0) {
NSLog(@"讀取完畢...");
break;
} else if (feof(in_file)) {
break;
}
// 將緩沖區(qū)數(shù)據(jù)轉(zhuǎn)換成AVFrame類型
//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++;
// 第九步:視頻編碼處理
// 1、發(fā)送一幀視頻像素數(shù)據(jù)
avcodec_send_frame(avcodec_context, av_frame);
// 2、接收一幀視頻壓縮數(shù)據(jù)格式(像素數(shù)據(jù)編碼而來)
result = avcodec_receive_packet(avcodec_context, av_packet);
if (result == 0) {
// 編碼成功
// 第十步:將數(shù)據(jù)寫入到輸出文件
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(@"輸出一幀數(shù)據(jù)失敗");
return;
}
}
}
//第11步:寫入剩余幀數(shù)據(jù)->可能沒有
flush_encoder(avformat_context, 0);
//第12步:寫入文件尾部信息
av_write_trailer(avformat_context);
//第13步:釋放內(nèi)存
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);
}