FFMPEG编码实现:将YUV文件编码为H264

一般编码流程:
1、创建编码器上下文并设置编码器参数(编码格式、时间基、编码器类型、最大最小质量、宽高等等)
2、寻找编码器
3、打开编码器
3、获取原始YUV或RGB数据
4、编码:avcodec_send_frame()、avcodec_receive_packet()
5、输出:保存为文件或封装为其他格式(mp4、flv、avi等)

本程序(将YUV文件编码为H264)流程:
1、创建输出码流的上下文AVFormatContext,并初始化
2、打开输出文件:avio_open2()
3、创建新流:avformat_new_stream()   //用于保存视频流信息,一个完整的视频文件包含多个流信息:视频流、音频流、字幕流等
4、创建编码器上下文并设置编码器参数
5、查找编码器并打开编码器
6、写入文件头信息:avformat_write_header()
7、打开输入文件
8、循环读取输入文件的yuv值,并进行编码;编码成功写入文件:av_write_frame()
9、对编码器中剩余数据编码
10、写入文件尾信息:av_write_trailer()
11、释放资源

demo

/*
* 编码
* 将yuv视频文件按h264重新编码
*/
#include <iostream>
using namespace std;
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
};
const char* outfile = "1.h264";
const char* infile = "output.yuv";

int main(int argc, char** argv)
{
    AVFormatContext* afc = avformat_alloc_context();
    AVOutputFormat* outformat;
    outformat = av_guess_format(NULL, outfile, NULL);
    afc->oformat = outformat;
    if (avio_open2(&afc->pb, outfile, AVIO_FLAG_READ_WRITE, NULL, NULL) < 0)
    {
        cout << "open outfile error" << endl;
        return 0;
    }
    AVStream* ast = avformat_new_stream(afc, NULL); //创建新流
    cout << "ast.codecid = " << ast->codec->codec_id<< endl;
    if (ast == NULL)
    {
        cout << "create stream error" << endl;
        return 0;
    }
    AVCodecContext* acc  = ast->codec;
    acc->codec_id = afc->oformat->video_codec;
    acc->codec_type = AVMEDIA_TYPE_VIDEO;
    acc->pix_fmt = AV_PIX_FMT_YUV420P;
    acc->width = 720;
    acc->height = 480;
    acc->bit_rate = 480000;
    acc->gop_size = 25;
    acc->time_base.num = 1;
    acc->time_base.den = 25;
    acc->qmin = 10;
    acc->qmax = 30;
    acc->max_b_frames = 3;
    AVDictionary* para = 0;
    if (acc->codec_id == AV_CODEC_ID_H264)
    {
        av_dict_set(&para, "preset", "slow", 0);
        av_dict_set(&para, "tune", "zerolatency", 0);
    }
    //av_dump_format(afc, 0, outfile, 1);//输出文件信息
    avformat_write_header(afc, NULL);
    AVCodec* codec = avcodec_find_encoder(acc->codec_id);
    if (!codec)
    {
        cout << "find codec error ,codec_id = " << acc->codec_id << endl;
        return 0;
    }
    if (avcodec_open2(acc, codec, &para) < 0)
    {
        cout << "open codec error" << endl;
        return 0;
    }
    AVFrame* frame = av_frame_alloc();
    int picsize = av_image_get_buffer_size(acc->pix_fmt, acc->width, acc->height, 1);
    uint8_t* picbuf;
    picbuf = (uint8_t*)av_malloc(picsize);
    av_image_fill_arrays(frame->data, frame->linesize, picbuf, AV_PIX_FMT_YUV420P, acc->width, acc->height, 1); //填充像素数据缓冲区,没有这步会导致sws_scale()失败
    AVPacket pkt;
    av_new_packet(&pkt, picsize); //初始化pkt,并给data字段分配空间
    int ysize = acc->width * acc->height;
    FILE* inf = fopen(infile, "rb");
    //FILE* f = fopen(outfile, "wb");
    int count = 0;
    int curr = 1;
    while (1)
    {
		if (fread(picbuf, 1, ysize * 3 / 2, inf) <= 0)
        {
            cout << "read finsh" << endl;
            break;
        }
        else if (feof(inf))
			break;
        frame->data[0] = picbuf;
        frame->data[1] = picbuf + ysize;
        frame->data[2] = picbuf + ysize * 5 / 4;
        frame->pts = count* (ast->time_base.den) / ((ast->time_base.num) * 25); //pts:显示时间戳
        count++;
        int ret = avcodec_send_frame(acc, frame);
        while (ret >= 0)
        {
            ret = avcodec_receive_packet(acc, &pkt);
            if (ret == 0) //编码成功
            {
				pkt.stream_index = ast->index;
                int er = av_write_frame(afc, &pkt);//将编码后的数据包写入文件
                //int er = fwrite(pkt.data, 1, pkt.size, f);//也可以使用fwrite()实现 将编码后的数据包写入文件
                cout << "write frame is " << curr << endl; 
                curr++;
                if (er < 0)
                {
                    cout << "write curr frame error" << endl;
                    return -1;
                }
            }
            else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) //接收到的数据无效 需要重新读入
            {
				cout << "receive video frame  error, need again" << endl;
                break;
            }
			else
				return -1;
        }
    }
	int codecnum = 0;
    //对编码器中的剩余数据编码,注意这里和前面不一样的地方avcodec_send_frame()传入的fram为NULL
    ret = avcodec_send_frame(acc, NULL);
	while (ret >= 0)
    {
        ret = avcodec_receive_packet(acc, &pkt);
        if (ret == 0) //编码成功
        {
            pkt.stream_index = ast->index;
            int er = av_write_frame(afc, &pkt);
            cout << "write codecContext frame is " << codecnum << endl;
            codecnum++;
            if (er < 0)
            {
                cout << "write codecnum frame error" << endl;
                return -1;
            }
        }
        else if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)//接收到的数据无效 需要重新读入
        {
            cout << "receive video frame  error, need again" << endl;
            break;
        }
		else
			return -1;
    }
    cout << "encodec frame is " << curr - 1 << ", encodec codecnum is " << codecnum - 1 <<endl;           
    av_write_trailer(afc); //写文件尾
    //释放资源
    if (ast)
    {
        avcodec_close(ast->codec);
        av_free(frame);
        av_free(picbuf);
    }
    avio_close(afc->pb);
	av_packet_unref(&pkt);
	av_frame_free(&frame);
	avcodec_free_context(&acc);
	avformat_free_context(afc);
    fclose(inf);
    return 0;
}

重要步骤解析:

outformat = av_guess_format(NULL, outfile, NULL);
afc->oformat = outformat;

av_guess_format()获取和参数最匹配的封装格式,这里的outfile为输出文件1.h264,所以最后的封装格式匹配的编码格式为H264

编码器的参数设置:

AVCodecContext* acc  = ast->codec;
//编码器ID
acc->codec_id = afc->oformat->video_codec; 
//编码器类型,这是是视频编码器
acc->codec_type = AVMEDIA_TYPE_VIDEO;

acc->pix_fmt = AV_PIX_FMT_YUV420P;
acc->width = 720;
acc->height = 480;
//平均码率,越大则文件越大
acc->bit_rate = 480000;
//一组图片中的图片数量,相当于两个I帧之间的间隔
acc->gop_size = 15;
//编码帧率,每秒多少帧。这里设置表示1秒25帧
acc->time_base.num = 1;
acc->time_base.den = 25;
//最小最大量化器
acc->qmin = 10;
acc->qmax = 30;
//最大B帧数
acc->max_b_frames = 0;

AVDictionary* para = 0;
if (acc->codec_id == AV_CODEC_ID_H264)
{
    av_dict_set(&para, "preset", "slow", 0);
    av_dict_set(&para, "tune", "zerolatency", 0);
}

av_dict_set()的详细使用可查看 AVDictionary各接口

preset   的参数主要调节编码速度和质量的平衡,有ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo这10个选项,从快到慢。

tune  的参数主要配合视频类型和视觉优化的参数。tune的值有:
film: 电影、真人类型
animation: 动画
grain: 需要保留大量的grain时用
stillimage: 静态图像编码时使用
psnr: 为提高psnr做了优化的参数
ssim: 为提高ssim做了优化的参数
fastdecode: 可以快速解码的参数
zerolatency:零延迟,用在需要非常低的延迟的情况下 

avformat_write_header(afc, NULL);
av_write_trailer(afc);

写入文件头尾信息,对于某些没有文件头的封装格式,不需要这一操作。如MPEG2TS 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值