FFmpeg小白學(xué)習(xí)記錄(三)視頻流編碼流程

視頻編碼流程

之前了解了如何解碼多媒體文件中的視頻流,并將解碼后的圖像進(jìn)行顯示,接下來(lái)我們學(xué)習(xí)如何對(duì)視頻流進(jìn)行編碼,從 圖片 → h.264 和 圖片 → MP4 兩個(gè)案例中具體了解視頻的編碼流程

首先我們了解視頻編碼的流程,編碼流程與解碼流程類(lèi)似,將解碼器替換為了編碼器,在細(xì)節(jié)上有點(diǎn)差異

FFmpeg視頻編碼流程

圖像 → h.264

本案例中我們將多張圖像轉(zhuǎn)成h.264文件,每張圖像顯示1s,最終實(shí)現(xiàn)代碼如下:

為了方便,選擇的圖像均為 600x900 的 jpg 圖像,同時(shí)可以使用之前編寫(xiě)的視頻解碼驗(yàn)證h264文件正確性

之后案例中導(dǎo)入的頭文件都一致,后續(xù)的代碼中就不多次寫(xiě)出了

extern"C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <iostream>
using namespace std;

#include <opencv2/opencv.hpp>
using namespace cv;

//刷新解碼緩沖區(qū),包數(shù)據(jù)的處理方式一致
int flush_encoder(AVFormatContext* fmtCtx, AVCodecContext* codecCtx, int vStreamIndex) {
    int ret;
    AVPacket* pkt = av_packet_alloc();
    pkt->data = NULL;
    pkt->size = 0;

    if (!(codecCtx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
        av_packet_free(&pkt);
        return 0;
    }

    cout << "Flushing stream " << vStreamIndex << " encoder" << endl;

    if ((ret = avcodec_send_frame(codecCtx, 0)) >= 0) {
        while (avcodec_receive_packet(codecCtx, pkt) >= 0) {
            cout << "encoder success:" << pkt->size << endl;

            pkt->stream_index = vStreamIndex;
            av_packet_rescale_ts(pkt, codecCtx->time_base,
                fmtCtx->streams[vStreamIndex]->time_base);
            ret = av_interleaved_write_frame(fmtCtx, pkt);
            if (ret < 0) {
                break;
            }
        }
    }

    av_packet_free(&pkt);
    return ret;
}

void rgb2h264() {
    int ret = -1;

    //聲明所需的變量名
    AVFormatContext* fmtCtx = NULL;
    AVCodecContext* codecCtx = NULL;
    AVStream* vStream = NULL;
    AVCodec* codec = NULL;

    AVPacket* pkt = av_packet_alloc();

    AVFrame* rgbFrame = NULL;
    AVFrame* yuvFrame = NULL;

    //需要編碼的視頻寬高、每幅圖像所占幀數(shù)
    int w = 600, h = 900, perFrameCnt = 25;

    do {
        //輸出文件名
        const char* outFile = "result.h264";

        //----------------- 打開(kāi)輸出文件 -------------------   
        //創(chuàng)建輸出結(jié)構(gòu)上下文 AVFormatContext,會(huì)根據(jù)文件后綴創(chuàng)建相應(yīng)的初始化參數(shù)
        if (avformat_alloc_output_context2(&fmtCtx, NULL, NULL, outFile) < 0) {
            cout << "Cannot alloc output file context" << endl;
            break;
        }

        //打開(kāi)文件
        if (avio_open(&fmtCtx->pb, outFile, AVIO_FLAG_READ_WRITE) < 0) {
            cout << "output file open failed" << endl;
            break;
        }

        //----------------- 查找編碼器 -------------------   
        //查找codec有三種方法
        /*1.AVFormatContext的oformat中存放了對(duì)應(yīng)的編碼器類(lèi)型
        AVOutputFormat* outFmt = fmtCtx->oformat;
        codec = avcodec_find_encoder(outFmt->video_codec);
        */
        /*2.根據(jù)編碼器名稱(chēng)去查找
        codec = avcodec_find_encoder_by_name("libx264");
        */
        //3.根據(jù)編碼器ID查找
        codec = avcodec_find_encoder(AV_CODEC_ID_H264);

        if (codec == NULL) {
            cout << "Cannot find any endcoder" << endl;
            break;
        }

        //----------------- 申請(qǐng)編碼器上下文結(jié)構(gòu)體 -------------------  
        codecCtx = avcodec_alloc_context3(codec);
        if (codecCtx == NULL) {
            cout << "Cannot alloc context" << endl;
            break;
        }
        
        //----------------- 創(chuàng)建視頻流,并設(shè)置參數(shù) -------------------  
        vStream = avformat_new_stream(fmtCtx, codec);
        if (vStream == NULL) {
            cout << "failed create new video stream" << endl;
            break;
        }
        
        //設(shè)置時(shí)間基,25為分母,1為分子,表示以1/25秒時(shí)間間隔播放一幀圖像
        vStream->time_base = AVRational{ 1,25 };
        /*兩種設(shè)置方法等價(jià)
        vStream->time_base.den = 25;
        vStream->time_base.num = 1;
        */

        //設(shè)置編碼所需的參數(shù)
        AVCodecParameters* param = fmtCtx->streams[vStream->index]->codecpar;
        param->codec_type = AVMEDIA_TYPE_VIDEO;
        param->width = w;
        param->height = h;

        //----------------- 將參數(shù)傳給解碼器上下文 -------------------  
        avcodec_parameters_to_context(codecCtx, param);

        //視頻幀類(lèi)型,使用YUV420P格式
        codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
        codecCtx->time_base = AVRational{ 1,25 };
        codecCtx->bit_rate = 400000;
        //gop表示多少個(gè)幀中存在一個(gè)關(guān)鍵幀
        codecCtx->gop_size = 12;

        //H264-設(shè)置量化步長(zhǎng)范圍
        if (codecCtx->codec_id == AV_CODEC_ID_H264) {
            codecCtx->qmin = 10;
            codecCtx->qmax = 51;
            //(0~1.0),0=>CBR 1->恒定QP
            codecCtx->qcompress = (float)0.6;
        }

        if (codecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
            codecCtx->max_b_frames = 2;
        if (codecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
            codecCtx->mb_decision = 2;

        //----------------- 打開(kāi)解碼器 -------------------  
        if (avcodec_open2(codecCtx, codec, NULL) < 0) {
            cout << "Open encoder failed" << endl;
            break;
        }

        av_dump_format(fmtCtx, 0, outFile, 1);

        //設(shè)置視頻幀參數(shù)
        rgbFrame = av_frame_alloc();
        yuvFrame = av_frame_alloc();
        rgbFrame->width = codecCtx->width;
        yuvFrame->width = codecCtx->width;
        rgbFrame->height = codecCtx->height;
        yuvFrame->height = codecCtx->height;
        rgbFrame->format = AV_PIX_FMT_BGR24;
        yuvFrame->format = codecCtx->pix_fmt;

        int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, codecCtx->width, codecCtx->height, 1);
        int yuvSize = av_image_get_buffer_size(codecCtx->pix_fmt, codecCtx->width, codecCtx->height, 1);

        uint8_t* pictureBuf = (uint8_t*)av_malloc(size);
        uint8_t* yuvBuf = (uint8_t*)av_malloc(yuvSize);

        av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize,
            pictureBuf, AV_PIX_FMT_BGR24,
            codecCtx->width, codecCtx->height, 1);

        av_image_fill_arrays(yuvFrame->data, yuvFrame->linesize,
            yuvBuf, codecCtx->pix_fmt,
            codecCtx->width, codecCtx->height, 1);

        //設(shè)置BGR數(shù)據(jù)轉(zhuǎn)換為YUV的SwsContext
        struct SwsContext* imgCtx = sws_getContext(
            codecCtx->width, codecCtx->height, AV_PIX_FMT_BGR24,
            codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
            SWS_BILINEAR, NULL, NULL, NULL);

        //寫(xiě)入文件頭信息
        ret = avformat_write_header(fmtCtx, NULL);
        if (ret != AVSTREAM_INIT_IN_WRITE_HEADER) {
            cout << "Write file header fail" << endl;
            break;
        }

        av_new_packet(pkt, size);

        //這里使用OpenCV讀取圖像的數(shù)據(jù),當(dāng)然FFmpeg也可以讀取圖像數(shù)據(jù),會(huì)略微麻煩一些之后會(huì)舉例
        Mat img;
        char imgPath[] = "img/p0.jpg";
        for (int i = 0; i < 6; i++) {
            imgPath[5] = '0' + i;
            img = imread(imgPath);
            //imshow("img", img);
            //waitKey(0);
            //----------------- BGR數(shù)據(jù)填充至圖像幀 -------------------  
            memcpy(pictureBuf, img.data, size);

            //進(jìn)行圖像格式轉(zhuǎn)換
            sws_scale(imgCtx,
                rgbFrame->data,
                rgbFrame->linesize,
                0,
                codecCtx->height,
                yuvFrame->data,
                yuvFrame->linesize);

            for (int j = 0; j < perFrameCnt; j++) {
                //設(shè)置 pts 值,用于度量解碼后視頻幀位置
                yuvFrame->pts = i * perFrameCnt + j;
                //解碼時(shí)為 avcodec_send_packet ,編碼時(shí)為 avcodec_send_frame
                if (avcodec_send_frame(codecCtx, yuvFrame) >= 0) {
                    //解碼時(shí)為 avcodec_receive_frame ,編碼時(shí)為 avcodec_receive_packet
                    while (avcodec_receive_packet(codecCtx, pkt) >= 0) {
                        cout << "encoder success:" << pkt->size << endl;
                        pkt->stream_index = vStream->index;
                        //將解碼上下文中的時(shí)間基同等轉(zhuǎn)換為流中的時(shí)間基
                        av_packet_rescale_ts(pkt, codecCtx->time_base, vStream->time_base);
                        //pos為-1表示未知,編碼器編碼時(shí)進(jìn)行設(shè)置
                        pkt->pos = -1;
                        //將包數(shù)據(jù)寫(xiě)入文件中
                        ret = av_interleaved_write_frame(fmtCtx, pkt);
                        if (ret < 0) {
                            cout << "error is:" << ret << endl;
                        }
                    }
                }
            }
        }

        //刷新解碼緩沖區(qū)
        ret = flush_encoder(fmtCtx, codecCtx, vStream->index);
        if (ret < 0) {
            printf("flushing encoder failed!\n");
            break;
        }

        //向文件中寫(xiě)入文件尾部標(biāo)識(shí),并釋放該文件
        av_write_trailer(fmtCtx);

        av_free(pictureBuf);
        av_free(yuvBuf);
        sws_freeContext(imgCtx);
    } while (0);

    //釋放資源
    av_packet_free(&pkt);
    avcodec_close(codecCtx);

    if (rgbFrame)
        av_frame_free(&rgbFrame);
    if (yuvFrame)
        av_frame_free(&yuvFrame);

    if (fmtCtx) {
        avio_close(fmtCtx->pb);
        avformat_free_context(fmtCtx);
    }
}

這樣我們就完成了視頻的編碼,將圖像轉(zhuǎn)換為了h.264文件,我們可以通過(guò)之前編寫(xiě)的視頻解碼+OpenCV播放該文件

產(chǎn)生的h264文件播放效果:

result.h264
代碼解析

avformat_alloc_output_context2

avformat_alloc_output_context2avformat_alloc_context類(lèi)似都是創(chuàng)建AVFormatContext結(jié)構(gòu)體,只不過(guò)avformat_alloc_output_context2是專(zhuān)門(mén)創(chuàng)建用于輸出的AVFormatContext結(jié)構(gòu)體

使用完后,釋放資源需使用avformat_free_context函數(shù)

int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
                                   const char *format_name, const char *filename);

參數(shù):

  • AVFormatContext **ctx:AVFormatContext結(jié)構(gòu)體指針的指針,用于賦值。如果失敗則設(shè)置為NULL
  • AVOutputFormat *oformat:輸出格式,若為NULL,則會(huì)通過(guò) format_name 去尋找對(duì)應(yīng)格式
  • const char *format_name:輸出格式名稱(chēng),若為NULL,則會(huì)通過(guò) filename 文件后綴去尋找對(duì)應(yīng)格式
  • const char *filename:輸出文件名

return:

返回?cái)?shù)值 ≥ 0時(shí)代表成功,失敗時(shí)會(huì)返回一個(gè)負(fù)值


avio_open

avio_open函數(shù)會(huì)創(chuàng)建并初始化AVIOContext,用于訪(fǎng)問(wèn) url 指定的資源

使用完后,釋放資源需使用avio_close函數(shù)

int avio_open(AVIOContext **s, const char *url, int flags);

參數(shù):

  • AVIOContext **s:AVIOContext結(jié)構(gòu)體指針的指針,用于賦值。如果失敗則設(shè)置為NULL
  • const char *url:訪(fǎng)問(wèn)文件 url 路徑
  • int flags:對(duì)文件的訪(fǎng)問(wèn)標(biāo)志,如:只讀、只寫(xiě)、讀寫(xiě)等

return:

返回?cái)?shù)值 ≥ 0時(shí)代表成功,失敗時(shí)會(huì)返回一個(gè)負(fù)值


avformat_new_stream

avformat_new_stream函數(shù)會(huì)向媒體文件添加新的流,需要在avformat_write_header之前調(diào)用

AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

參數(shù):

  • AVFormatContext *s:媒體文件對(duì)應(yīng)的結(jié)構(gòu)上下文
  • const AVCodec *c:如果不為NULL,則之后對(duì)應(yīng)AVStreamAVCodecContext默認(rèn)初始化時(shí)使用該解碼器

return:

成功返回AVStream指針,失敗返回NULL


avformat_write_header

avformat_write_header函數(shù)向文件中輸入對(duì)應(yīng)多媒體格式的文件頭信息,比如:流相關(guān)信息

av_warn_unused_result
int avformat_write_header(AVFormatContext *s, AVDictionary **options);

參數(shù):

  • AVFormatContext *s:媒體文件對(duì)應(yīng)的結(jié)構(gòu)上下文
  • AVDictionary **options:指定各種參數(shù),一般填NULL即可

return:

AVSTREAM_INIT_IN_WRITE_HEADER (0)表示成功,如果編碼器還沒(méi)有在 avformat_init中完全初始化,則AVSTREAM_INIT_IN_INIT_OUTPUT (1)表示成功,AVERROR為負(fù)值表示失敗


avcodec_send_frame與avcodec_receive_packet

這兩個(gè)函數(shù)與之前在解碼流程中的avcodec_send_packetavcodec_receive_frame功能一致,只是發(fā)送和接收的類(lèi)型發(fā)生了變化,這里就不多加闡述

int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);

av_packet_rescale_ts

av_packet_rescale_ts函數(shù)將解碼上下文中的時(shí)間基同等轉(zhuǎn)換為流中的時(shí)間基,會(huì)根據(jù)參數(shù)對(duì)AVPacket的pts、dts、duration等屬性進(jìn)行賦值

void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb){
    if (pkt->pts != AV_NOPTS_VALUE)
        pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
    if (pkt->dts != AV_NOPTS_VALUE)
        pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
    if (pkt->duration > 0)
        pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);
#if FF_API_CONVERGENCE_DURATION
FF_DISABLE_DEPRECATION_WARNINGS
    if (pkt->convergence_duration > 0)
        pkt->convergence_duration = av_rescale_q(pkt->convergence_duration, src_tb, dst_tb);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
}

參數(shù):

  • AVPacket *pkt:將在其上執(zhí)行轉(zhuǎn)換的包
  • AVRational tb_src:源時(shí)間基
  • AVRational tb_dst:目標(biāo)時(shí)間基

av_interleaved_write_frame

av_interleaved_write_frame函數(shù)將數(shù)據(jù)包寫(xiě)入輸出媒體文件,確保交錯(cuò)正確。確保包按照dts遞增的順序正確交錯(cuò)

該函數(shù)無(wú)論成功還是失敗,其內(nèi)部都會(huì)調(diào)用av_packet_unref解引用

int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);

參數(shù):

  • AVFormatContext *s:媒體文件對(duì)應(yīng)的結(jié)構(gòu)上下文
  • AVPacket *pkt:包含要寫(xiě)入數(shù)據(jù)的數(shù)據(jù)包

return:

返回 0 表示成功,失敗則會(huì)返回一個(gè)負(fù)數(shù)


av_write_trailer

av_write_trailer函數(shù)寫(xiě)入文件結(jié)束符,并關(guān)閉文件

int av_write_trailer(AVFormatContext *s);

參數(shù):

  • AVFormatContext *s:媒體文件對(duì)應(yīng)的結(jié)構(gòu)上下文

return:

返回 0 表示成功,失敗則會(huì)返回一個(gè)負(fù)數(shù)

FFmpeg讀取圖像數(shù)據(jù)

FFmpeg中將 jpg圖像文件視為只有一幀數(shù)據(jù)的視頻流,其編碼方式為AV_CODEC_ID_MJPEG,我們同樣可以使用解碼的流程對(duì)其進(jìn)行操作

FFmpeg對(duì)JPG文件的封裝支持模式匹配,即如果想要將多張圖片寫(xiě)入到多張jpg中只需要文件名包含百分號(hào)即可,例如 p%d.jpg,那么在每一次調(diào)用av_read_frame函數(shù)就會(huì)讀取一個(gè)jpg文件數(shù)據(jù)

實(shí)際上代碼和解碼流程幾乎一摸一樣,甚至可以將視頻解碼流程中的filePath修改為圖片路徑就可以做到,只是考慮到 jpg圖像格式差異,將SwsContext的創(chuàng)建放在了avcodec_receive_frame

為了簡(jiǎn)化代碼,下面代碼省略錯(cuò)誤處理。完整版請(qǐng)參考視頻解碼流程中的具體代碼

void readJpg() {
    //聲明所需的變量名
    AVFormatContext* fmtCtx = NULL;
    AVCodecContext* codecCtx = NULL;
    AVCodec* codec = NULL;

    AVPacket* pkt = av_packet_alloc();
    AVFrame* jpgFrame = NULL;
    AVFrame* rgbFrame = NULL;

    const char* filePath = "img/p%d.jpg";
    do {
        fmtCtx = avformat_alloc_context();
        avformat_open_input(&fmtCtx, filePath, NULL, NULL);
        avformat_find_stream_info(fmtCtx, NULL);

        int videoIndex = -1;
        for (int i = 0; i < fmtCtx->nb_streams; i++) {
            if (fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                videoIndex = i;
                break;
            }
        }

        AVCodecParameters* param = fmtCtx->streams[videoIndex]->codecpar;
        codec = avcodec_find_decoder(param->codec_id);
        codecCtx = avcodec_alloc_context3(codec);
        avcodec_parameters_to_context(codecCtx, param);
        avcodec_open2(codecCtx, codec, NULL);
            
        //記錄第一張圖片的寬高,將其余圖片縮放至第一張圖片的大小
        int w = codecCtx->width, h = codecCtx->height;

        jpgFrame = av_frame_alloc();
        rgbFrame = av_frame_alloc();
        
        //如果想要根據(jù)圖片的寬高更改Frame大小,請(qǐng)將下列3行代碼移至while (avcodec_receive_frame(codecCtx, jpgFrame) >= 0)中,記得釋放buffer
        int size = av_image_get_buffer_size(AV_PIX_FMT_BGR24, w, h, 1);
        unsigned char* buffer = (unsigned char*)av_malloc(size * sizeof(unsigned char));
        av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_BGR24, w, h, 1);

        SwsContext* imgCtx = NULL;

        //老樣子,OpenCV進(jìn)行顯示
        Mat img = Mat(Size(w, h), CV_8UC3);
        while (av_read_frame(fmtCtx, pkt) >= 0) {
            if (pkt->stream_index == videoIndex) {
                if (avcodec_send_packet(codecCtx, pkt) >= 0) {
                    while (avcodec_receive_frame(codecCtx, jpgFrame) >= 0) {
                        //打印結(jié)果 12 -> AV_PIX_FMT_YUVJ420P 或 14 -> AV_PIX_FMT_YUV444P
                        cout << codecCtx->pix_fmt << endl;
                        
                        imgCtx = sws_getCachedContext(imgCtx,
                            codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
                            codecCtx->width, codecCtx->height, AV_PIX_FMT_BGR24,
                            SWS_BICUBIC, NULL, NULL, NULL);
                        
                        sws_scale(imgCtx,
                            jpgFrame->data,
                            jpgFrame->linesize,
                            0,
                            codecCtx->height,
                            rgbFrame->data,
                            rgbFrame->linesize);

                        //此時(shí)buffer就是BGR數(shù)據(jù)
                        img.data = buffer;
                        imshow("img", img);
                        waitKey(0);
                    }
                }
            }
            av_packet_unref(pkt);
        }
        
        sws_freeContext(imgCtx);
    } while (0);

    avformat_close_input(&fmtCtx);
    avcodec_free_context(&codecCtx);
    av_packet_free(&pkt);
    if (jpgFrame) 
        av_frame_free(&jpgFrame);
    if (rgbFrame) 
        av_frame_free(&rgbFrame);
}

參考資料

http://www.itdecent.cn/p/fe84413a140f

https://blog.csdn.net/qq_42139383/article/details/118334630

圖像 → mp4

因?yàn)?FFmpeg 內(nèi)部封裝了各種格式的編解碼方式,使得我們可以很方便使用,不需要管底層代碼邏輯。也正是這種封裝性,使得我們的代碼可以有很強(qiáng)的復(fù)用性,我們只需要給函數(shù)添加一個(gè)const char* filePath參數(shù)就可以完成 圖像→ mp4/h264 的轉(zhuǎn)換

如果你熟悉 圖像→ h.264 的流程,你會(huì)發(fā)現(xiàn)幾乎沒(méi)有差異。為了代碼看起來(lái)比較簡(jiǎn)潔,只會(huì)在和 圖像→ h.264 有差異的地方標(biāo)記注釋,如果想看代碼具體的功能可以參考 圖像→ h.264 處的代碼,那里有完整的注釋

int rgb2mp4Encode(AVCodecContext* codecCtx, AVFrame* yuvFrame, AVPacket* pkt, AVStream* vStream, AVFormatContext* fmtCtx){
    int ret = 0;
    if (avcodec_send_frame(codecCtx, yuvFrame) >= 0) {
        while (avcodec_receive_packet(codecCtx, pkt) >= 0) {
            pkt->stream_index = vStream->index;
            pkt->pos = -1;

            av_packet_rescale_ts(pkt, codecCtx->time_base, vStream->time_base);
            cout << "encoder success:" << pkt->size << endl;

            ret = av_interleaved_write_frame(fmtCtx, pkt);
            if (ret < 0) {
                char errStr[256];
                av_strerror(ret, errStr, 256);
                cout << "error is:" << errStr << endl;
            }
        }
    }
    return ret;
}

void rgb2mp4(){
    int ret;
    AVFormatContext* fmtCtx = NULL;
    AVCodecContext* codecCtx = NULL;
    AVCodec* codec = NULL;
    AVStream* vStream = NULL;
    AVPacket* pkt = av_packet_alloc();
    
    AVFrame* rgbFrame = NULL, * yuvFrame = NULL;
    
    int w = 600, h = 900, perFrameCnt = 25;

    do {
        //輸出文件名 從.h264后綴改為mp4
        const char* filePath = "result2.mp4";
        ret = avformat_alloc_output_context2(&fmtCtx, NULL, NULL, filePath);
        if (ret < 0) {
            cout << "Cannot alloc output file context" << endl;
            break;
        }

        ret = avio_open(&fmtCtx->pb, filePath, AVIO_FLAG_READ_WRITE);
        if (ret < 0) {
            cout << "output file open failed" << endl;
            break;
        }

        codec = avcodec_find_encoder(AV_CODEC_ID_H264);

        if (codec == NULL) {
            cout << "Cannot find any endcoder" << endl;
            break;
        }

        codecCtx = avcodec_alloc_context3(codec);
        if (codecCtx == NULL) {
            cout << "Cannot alloc AVCodecContext" << endl;
            break;
        }
        
        vStream = avformat_new_stream(fmtCtx, codec);
        if (vStream == NULL) {
            cout << "failed create new video stream" << endl;
            break;
        }
        
        vStream->time_base = AVRational{ 1,25 };

        AVCodecParameters* param = vStream->codecpar;
        param->width = w;
        param->height = h;
        param->codec_type = AVMEDIA_TYPE_VIDEO;

        ret = avcodec_parameters_to_context(codecCtx, param);
        if (ret < 0) {
            cout << "Cannot copy codec para" << endl;
            break;
        }

        codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
        codecCtx->time_base = AVRational{ 1,25 };
        codecCtx->bit_rate = 400000;
        codecCtx->gop_size = 12;

        // 某些封裝格式必須要設(shè)置該標(biāo)志,否則會(huì)造成封裝后文件中信息的缺失,如:mp4
        if (fmtCtx->oformat->flags & AVFMT_GLOBALHEADER) {
            codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        }

        if (codec->id == AV_CODEC_ID_H264) {
            codecCtx->qmin = 10;
            codecCtx->qmax = 51;
            codecCtx->qcompress = (float)0.6;
        }

        ret = avcodec_open2(codecCtx, codec, NULL);
        if (ret < 0) {
            cout << "Open encoder failed" << endl;
            break;
        }

        //再將codecCtx設(shè)置的參數(shù)傳給param,用于寫(xiě)入頭文件信息
        avcodec_parameters_from_context(param, codecCtx);

        rgbFrame = av_frame_alloc();
        yuvFrame = av_frame_alloc();

        yuvFrame->width = w;
        yuvFrame->height = h;
        yuvFrame->format = codecCtx->pix_fmt;

        rgbFrame->width = w;
        rgbFrame->height = h;
        rgbFrame->format = AV_PIX_FMT_BGR24;

        int size = av_image_get_buffer_size((AVPixelFormat)rgbFrame->format, w, h, 1);
        uint8_t* buffer = (uint8_t*)av_malloc(size);
        ret = av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, (AVPixelFormat)rgbFrame->format, w, h, 1);
        if (ret < 0) {
            cout << "Cannot filled rgbFrame" << endl;
            break;
        }

        int yuvSize = av_image_get_buffer_size((AVPixelFormat)yuvFrame->format, w, h, 1);
        uint8_t* yuvBuffer = (uint8_t*)av_malloc(size);
        ret = av_image_fill_arrays(yuvFrame->data, yuvFrame->linesize, yuvBuffer, (AVPixelFormat)yuvFrame->format, w, h, 1);
        if (ret < 0) {
            cout << "Cannot filled yuvFrame" << endl;
            break;
        }

        SwsContext* imgCtx = sws_getContext(w, h, AV_PIX_FMT_BGR24, w, h, codecCtx->pix_fmt, 0, NULL, NULL, NULL);

        ret = avformat_write_header(fmtCtx, NULL);
        if (ret != AVSTREAM_INIT_IN_WRITE_HEADER) {
            cout << "Write file header fail" << endl;
            break;
        }

        av_new_packet(pkt, size);

        Mat img;
        char imgPath[] = "img/p0.jpg";
        int index = 0;

        for (int i = 0; i < 7; i++) {
            imgPath[5] = '0' + i;
            img = imread(imgPath);
            imshow("img", img);
            waitKey(0);
            memcpy(buffer, img.data, size);

            sws_scale(imgCtx,
                rgbFrame->data,
                rgbFrame->linesize,
                0,
                codecCtx->height,
                yuvFrame->data,
                yuvFrame->linesize);

            for (int j = 0; j < perFrameCnt; j++) {
                yuvFrame->pts = i * perFrameCnt + j;
                //將解碼的流程抽離成一個(gè)方法
                rgb2mp4Encode(codecCtx, yuvFrame, pkt, vStream, fmtCtx);
            }
        }

        rgb2mp4Encode(codecCtx, NULL, pkt, vStream, fmtCtx);

        av_write_trailer(fmtCtx);
        
        av_free(buffer);
        av_free(yuvBuffer);
        sws_freeContext(imgCtx);
    } while (0);

    av_packet_free(&pkt);

    if (fmtCtx)
        avformat_free_context(fmtCtx);

    if (codecCtx)
        avcodec_free_context(&codecCtx);

    if (rgbFrame)
        av_frame_free(&rgbFrame);
    if (yuvFrame)
        av_frame_free(&yuvFrame);
}

可以看到,rgb2mp4方法與rgb2h264之間嚴(yán)格來(lái)說(shuō)只有三處地方是不同的,而且如果在rgb2mp4方法中將filePath修改為"result2.h264"也是可以進(jìn)行轉(zhuǎn)換的

經(jīng)過(guò)測(cè)試可以在只修改filePath的情況下生成.avi、.flv格式文件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容