FFmpeg視頻編碼

引入頭文件

//核心庫
#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(&param, "preset", "slow", 0);
        //第二個值:調優
        //key:tune->調優
        //value:zerolatency->零延遲
        av_dict_set(&param, "tune", "zerolatency", 0);
    }
    if (avcodec_open2(avcodec_context, avcodec, &param) < 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章