引入頭文件
//核心庫
#include "avcodec.h"
//封裝格式處理庫
#include "avformat.h"
//工具庫
#include "imgutils.h"
功能實現
+(void)ffmpegVideoEncode:(NSString *)filePath outFilePath:(NSString *)outFilePath{
//第一步:註冊組件-編碼器、解碼器等
av_register_all();
//第二步:初始化封裝格式上下文->視頻編碼->處理爲視頻壓縮數據格式
AVFormatContext *avformat_context = avformat_alloc_context();
//注意事項:FFmpeg程序推測出文件類型->視頻壓縮數據格式類型
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.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(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 / 1000 /24 = 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;
}
//第七步:寫入文件頭信息
if (avformat_write_header(avformat_context, NULL) <0) {
NSLog(@"寫入文件頭信息失敗");
return;
}
//第八步:循環編碼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 = [filePath 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();
av_frame->width = avcodec_context->width;
av_frame->height = avcodec_context->height;
av_frame->format = AV_PIX_FMT_YUV420P;
//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;
NSLog(@"widhthh::%d",av_frame->width);
//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.1 發送一幀視頻像素數據
avcodec_send_frame(avcodec_context, av_frame);
//9.2 接受一幀視頻像素數據-編碼爲-視頻壓縮數據格式
result = avcodec_receive_packet(avcodec_context, av_packet);
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;
}
}
}
//第十一步:寫入剩餘幀數據-可能沒有
flush_encoder(avformat_context, 0);
//第十二步:寫入文件尾部信息
av_write_trailer(avformat_context);
//第十三步
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);
}
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;
}