FFmpeg小白学习记录(三)视频流编码流程

视频编码流程

之前了解了如何解码多媒体文件中的视频流,并将解码后的图像进行显示,接下来我们学习如何对视频流进行编码,从 图片 → h.264 和 图片 → MP4 两个案例中具体了解视频的编码流程

首先我们了解视频编码的流程,编码流程与解码流程类似,将解码器替换为了编码器,在细节上有点差异

FFmpeg视频编码流程

图像 → h.264

本案例中我们将多张图像转成h.264文件,每张图像显示1s,最终实现代码如下:

为了方便,选择的图像均为 600x900 的 jpg 图像,同时可以使用之前编写的视频解码验证h264文件正确性

之后案例中导入的头文件都一致,后续的代码中就不多次写出了

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;

//刷新解码缓冲区,包数据的处理方式一致
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;

    //需要编码的视频宽高、每幅图像所占帧数
    int w = 600, h = 900, perFrameCnt = 25;

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

        //----------------- 打开输出文件 -------------------   
        //创建输出结构上下文 AVFormatContext,会根据文件后缀创建相应的初始化参数
        if (avformat_alloc_output_context2(&fmtCtx, NULL, NULL, outFile) < 0) {
            cout << "Cannot alloc output file context" << endl;
            break;
        }

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

        //----------------- 查找编码器 -------------------   
        //查找codec有三种方法
        /*1.AVFormatContext的oformat中存放了对应的编码器类型
        AVOutputFormat* outFmt = fmtCtx->oformat;
        codec = avcodec_find_encoder(outFmt->video_codec);
        */
        /*2.根据编码器名称去查找
        codec = avcodec_find_encoder_by_name("libx264");
        */
        //3.根据编码器ID查找
        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 context" << endl;
            break;
        }
        
        //----------------- 创建视频流,并设置参数 -------------------  
        vStream = avformat_new_stream(fmtCtx, codec);
        if (vStream == NULL) {
            cout << "failed create new video stream" << endl;
            break;
        }
        
        //设置时间基,25为分母,1为分子,表示以1/25秒时间间隔播放一帧图像
        vStream->time_base = AVRational{ 1,25 };
        /*两种设置方法等价
        vStream->time_base.den = 25;
        vStream->time_base.num = 1;
        */

        //设置编码所需的参数
        AVCodecParameters* param = fmtCtx->streams[vStream->index]->codecpar;
        param->codec_type = AVMEDIA_TYPE_VIDEO;
        param->width = w;
        param->height = h;

        //----------------- 将参数传给解码器上下文 -------------------  
        avcodec_parameters_to_context(codecCtx, param);

        //视频帧类型,使用YUV420P格式
        codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
        codecCtx->time_base = AVRational{ 1,25 };
        codecCtx->bit_rate = 400000;
        //gop表示多少个帧中存在一个关键帧
        codecCtx->gop_size = 12;

        //H264-设置量化步长范围
        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;

        //----------------- 打开解码器 -------------------  
        if (avcodec_open2(codecCtx, codec, NULL) < 0) {
            cout << "Open encoder failed" << endl;
            break;
        }

        av_dump_format(fmtCtx, 0, outFile, 1);

        //设置视频帧参数
        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);

        //设置BGR数据转换为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);

        //写入文件头信息
        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读取图像的数据,当然FFmpeg也可以读取图像数据,会略微麻烦一些之后会举例
        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数据填充至图像帧 -------------------  
            memcpy(pictureBuf, img.data, size);

            //进行图像格式转换
            sws_scale(imgCtx,
                rgbFrame->data,
                rgbFrame->linesize,
                0,
                codecCtx->height,
                yuvFrame->data,
                yuvFrame->linesize);

            for (int j = 0; j < perFrameCnt; j++) {
                //设置 pts 值,用于度量解码后视频帧位置
                yuvFrame->pts = i * perFrameCnt + j;
                //解码时为 avcodec_send_packet ,编码时为 avcodec_send_frame
                if (avcodec_send_frame(codecCtx, yuvFrame) >= 0) {
                    //解码时为 avcodec_receive_frame ,编码时为 avcodec_receive_packet
                    while (avcodec_receive_packet(codecCtx, pkt) >= 0) {
                        cout << "encoder success:" << pkt->size << endl;
                        pkt->stream_index = vStream->index;
                        //将解码上下文中的时间基同等转换为流中的时间基
                        av_packet_rescale_ts(pkt, codecCtx->time_base, vStream->time_base);
                        //pos为-1表示未知,编码器编码时进行设置
                        pkt->pos = -1;
                        //将包数据写入文件中
                        ret = av_interleaved_write_frame(fmtCtx, pkt);
                        if (ret < 0) {
                            cout << "error is:" << ret << endl;
                        }
                    }
                }
            }
        }

        //刷新解码缓冲区
        ret = flush_encoder(fmtCtx, codecCtx, vStream->index);
        if (ret < 0) {
            printf("flushing encoder failed!\n");
            break;
        }

        //向文件中写入文件尾部标识,并释放该文件
        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);
    }
}

这样我们就完成了视频的编码,将图像转换为了h.264文件,我们可以通过之前编写的视频解码+OpenCV播放该文件

产生的h264文件播放效果:

result.h264
代码解析

avformat_alloc_output_context2

avformat_alloc_output_context2avformat_alloc_context类似都是创建AVFormatContext结构体,只不过avformat_alloc_output_context2是专门创建用于输出的AVFormatContext结构体

使用完后,释放资源需使用avformat_free_context函数

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

参数:

  • AVFormatContext **ctx:AVFormatContext结构体指针的指针,用于赋值。如果失败则设置为NULL
  • AVOutputFormat *oformat:输出格式,若为NULL,则会通过 format_name 去寻找对应格式
  • const char *format_name:输出格式名称,若为NULL,则会通过 filename 文件后缀去寻找对应格式
  • const char *filename:输出文件名

return:

返回数值 ≥ 0时代表成功,失败时会返回一个负值


avio_open

avio_open函数会创建并初始化AVIOContext,用于访问 url 指定的资源

使用完后,释放资源需使用avio_close函数

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

参数:

  • AVIOContext **s:AVIOContext结构体指针的指针,用于赋值。如果失败则设置为NULL
  • const char *url:访问文件 url 路径
  • int flags:对文件的访问标志,如:只读、只写、读写等

return:

返回数值 ≥ 0时代表成功,失败时会返回一个负值


avformat_new_stream

avformat_new_stream函数会向媒体文件添加新的流,需要在avformat_write_header之前调用

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

参数:

  • AVFormatContext *s:媒体文件对应的结构上下文
  • const AVCodec *c:如果不为NULL,则之后对应AVStreamAVCodecContext默认初始化时使用该解码器

return:

成功返回AVStream指针,失败返回NULL


avformat_write_header

avformat_write_header函数向文件中输入对应多媒体格式的文件头信息,比如:流相关信息

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

参数:

  • AVFormatContext *s:媒体文件对应的结构上下文
  • AVDictionary **options:指定各种参数,一般填NULL即可

return:

AVSTREAM_INIT_IN_WRITE_HEADER (0)表示成功,如果编码器还没有在 avformat_init中完全初始化,则AVSTREAM_INIT_IN_INIT_OUTPUT (1)表示成功,AVERROR为负值表示失败


avcodec_send_frame与avcodec_receive_packet

这两个函数与之前在解码流程中的avcodec_send_packetavcodec_receive_frame功能一致,只是发送和接收的类型发生了变化,这里就不多加阐述

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函数将解码上下文中的时间基同等转换为流中的时间基,会根据参数对AVPacket的pts、dts、duration等属性进行赋值

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
}

参数:

  • AVPacket *pkt:将在其上执行转换的包
  • AVRational tb_src:源时间基
  • AVRational tb_dst:目标时间基

av_interleaved_write_frame

av_interleaved_write_frame函数将数据包写入输出媒体文件,确保交错正确。确保包按照dts递增的顺序正确交错

该函数无论成功还是失败,其内部都会调用av_packet_unref解引用

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

参数:

  • AVFormatContext *s:媒体文件对应的结构上下文
  • AVPacket *pkt:包含要写入数据的数据包

return:

返回 0 表示成功,失败则会返回一个负数


av_write_trailer

av_write_trailer函数写入文件结束符,并关闭文件

int av_write_trailer(AVFormatContext *s);

参数:

  • AVFormatContext *s:媒体文件对应的结构上下文

return:

返回 0 表示成功,失败则会返回一个负数

FFmpeg读取图像数据

FFmpeg中将 jpg图像文件视为只有一帧数据的视频流,其编码方式为AV_CODEC_ID_MJPEG,我们同样可以使用解码的流程对其进行操作

FFmpeg对JPG文件的封装支持模式匹配,即如果想要将多张图片写入到多张jpg中只需要文件名包含百分号即可,例如 p%d.jpg,那么在每一次调用av_read_frame函数就会读取一个jpg文件数据

实际上代码和解码流程几乎一摸一样,甚至可以将视频解码流程中的filePath修改为图片路径就可以做到,只是考虑到 jpg图像格式差异,将SwsContext的创建放在了avcodec_receive_frame

为了简化代码,下面代码省略错误处理。完整版请参考视频解码流程中的具体代码

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();
        
        //如果想要根据图片的宽高更改Frame大小,请将下列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进行显示
        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) {
                        //打印结果 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);

                        //此时buffer就是BGR数据
                        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);
}

参考资料

https://www.jianshu.com/p/fe84413a140f

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

图像 → mp4

因为 FFmpeg 内部封装了各种格式的编解码方式,使得我们可以很方便使用,不需要管底层代码逻辑。也正是这种封装性,使得我们的代码可以有很强的复用性,我们只需要给函数添加一个const char* filePath参数就可以完成 图像→ mp4/h264 的转换

如果你熟悉 图像→ h.264 的流程,你会发现几乎没有差异。为了代码看起来比较简洁,只会在和 图像→ h.264 有差异的地方标记注释,如果想看代码具体的功能可以参考 图像→ 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;

        // 某些封装格式必须要设置该标志,否则会造成封装后文件中信息的缺失,如: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设置的参数传给param,用于写入头文件信息
        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;
                //将解码的流程抽离成一个方法
                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之间严格来说只有三处地方是不同的,而且如果在rgb2mp4方法中将filePath修改为"result2.h264"也是可以进行转换的

经过测试可以在只修改filePath的情况下生成.avi.flv格式文件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容