最简单的视频编码器(二)---ffmpeg

概述

本文记录一个最简单的基于ffmpeg的H.264视频编码器;直接调用ffmpeg完成编码。因此项目的体积非常小巧。该编码器可以将输入的YUV数据编码为H.264码流文件。

流程中主要的函数如下所示:

av_register_all():注册FFmpeg所有编解码器;

avcodec_find_encoder():查找编码器;

avcodec_alloc_context3():初始化输出码流的AVFormatContext;

avcodec_open2():打开编码器;

avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据);

avcodec_close():关闭编码器;

源代码介绍

基于ffmpeg的H.264视频编码器封装成simplest_encoder_FFmpeg类;里面包含对外暴露接口如下:

    1、long Init();

         这个是用于申请ffmpeg 的编码资源; 返回:0代表成功。


    2、long SetEncoderParam(ffmpeg_video_encoder_param* param);

    这个函数用于编码处理的;参数解释如下:

          param:设置编码参数,具体参考:ffmpeg_video_encoder_param

   函数返回值:0代表成功;否则失败。


    3、long StartEncoder();

    这个函数表示开始编码,在这个函数内部会进行ffmpeg 参数初始化;

    函数返回值:0代表成功;否则失败。


    4、long Encoder(ffmpeg_video_frame* in_frame, ffmpeg_video_frame* out_frame);

    这个函数表示编码功能,在这个函数内部会进行ffmpeg 的编码接口,具体参数如下:

    in_frame:表示输入的YUV数据,结构体内容参考ffmpeg_video_frame;

   out_frame:表示编码输出的编码数据,结构体内容参考ffmpeg_video_frame;

    函数返回值:0代表成功;否则失败。

    5、long AdjustBitrate(int bitrate);   

   这个函数用于编码处理的;参数解释如下:

    bitrate:更换的比特率,单位应该对应比特率*1000;

    函数返回值:0代表成功;否则失败。


    6、 long MandatoryKeyFrame();

   函数解释:这个代表强制输出关键帧,即IDR帧;

   函数返回值:0代表成功;否则失败。

    7、long Flush(ffmpeg_video_frame* out_frame);

    这个函数表示刷新编码器内部缓存帧,参数如下:

   out_frame:表示编码输出的编码数据,结构体内容参考ffmpeg_video_frame;

    函数返回值:0代表成功;否则失败。

    8、long StopEncoder();

    这个函数表示停止编码,在这个函数内部会进行ffmpeg 释放相关编码函数;

    函数返回值:0代表成功;否则失败。

    9、long UnInit();

    函数解释:代表释放ffmpeg 相关资源;

   函数返回值:0代表成功;否则失败。
 

对应源码头文件:simplest_encoder_FFmpeg.h

/**
 * 简单的ffmpeg视频编码器
 *Simple ffmpeg video encoder
 *
 * 梁启东 qidong.liang
 * 18088708700@163.com
 * https://blog.csdn.net/u011645307
 *
 *
 * 本程序实现了简单的ffmpeg视频编码功能;
 * 支持强制输出关键帧
 * 支持动态修改码率
 * 目前只开放了I420数据格式编码
 *This program realizes a simple ffmpeg video coding function;
 *Support forced output of key frames
 *Support dynamic modification of bit rate
 *At present, only i420 data format coding is open
 *
 */
#ifndef SIMPLEST_ENCODER_FFMPEG
#define SIMPLEST_ENCODER_FFMPEG

#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif

#include<map>

enum  ffmpeg_video_frame_type
{
	FFmpegVideoFrame_UNKNOWN = 0,
	FFmpegVideoFrame_H264,
	FFmpegVideoFrame_H265,
	FFmpegVideoFrame_I420,
	FFmpegVideoFrame_NV12,
	FFmpegVideoFrame_GRAY8,
	FFmpegVideoFrame_YUV422P,
	FFmpegVideoFrame_YUV444P,
	FFmpegVideoFrame_YUYV422,
	FFmpegVideoFrame_RGB24,
	FFmpegVideoFrame_BGR24,
	FFmpegVideoFrame_BGRA,
	FFmpegVideoFrame_PNG,
	FFmpegVideoFrame_JPG,
};

struct ffmpeg_video_frame
{
	ffmpeg_video_frame_type pix_fmt = FFmpegVideoFrame_H264;
	char* payload = nullptr;
	unsigned int payloadLen = 0;
	unsigned int width = 0;					
	unsigned int height = 0;						
	unsigned long long pts = 0;						
	unsigned long long dts = 0;					
};

struct ffmpeg_video_encoder_param
{
	unsigned int bitRate = 0; 
	unsigned int width = 0; 
	unsigned int height = 0;
	unsigned int gopSize = 0; 
	unsigned int frameRate = 0;   
	bool         openBFrame = 0; 
	unsigned int sliceCount = 0;
	unsigned int threadCount = 1;
	ffmpeg_video_frame_type pix_fmt = FFmpegVideoFrame_I420;
};

class simplest_encoder_FFmpeg
{

public:
	simplest_encoder_FFmpeg();

	~simplest_encoder_FFmpeg();

	long Init();

	long SetEncoderParam(ffmpeg_video_encoder_param* param);

	long StartEncoder();

	long Encoder(ffmpeg_video_frame* in_frame, ffmpeg_video_frame* out_frame);

	long AdjustBitrate(int bitrate);

	long MandatoryKeyFrame();

	long Flush(ffmpeg_video_frame* out_frame);

	long StopEncoder();

	long UnInit();

private:
	std::map<ffmpeg_video_frame_type, AVPixelFormat>		m_PixelFormatMap;
	AVCodecContext*								m_pCodecCtx;
	AVCodec*									m_pCodec;
	AVFrame*									m_pFrame;
	AVCodecID									m_codecId;
	AVPacket									pkt;
	unsigned int								m_bitRate;
	float										m_videoBuffMultiple;
	bool                                        m_mandatoryKeyFrame;
};

#endif//SIMPLEST_ENCODER_FFMPEG

对应源码头文件:simplest_encoder_FFmpeg.cpp


#include "simplest_encoder_FFmpeg.h"

simplest_encoder_FFmpeg::simplest_encoder_FFmpeg()
	: m_codecId(AV_CODEC_ID_H264)
{
	avcodec_register_all();
}

simplest_encoder_FFmpeg::~simplest_encoder_FFmpeg()
{

}

long simplest_encoder_FFmpeg::Init()
{
	m_pCodecCtx = nullptr;
	m_pCodec = nullptr;
	m_mandatoryKeyFrame = false;
	m_PixelFormatMap.insert(std::pair<ffmpeg_video_frame_type, AVPixelFormat>(FFmpegVideoFrame_I420, AV_PIX_FMT_YUV420P));

	m_pCodec = avcodec_find_encoder(m_codecId);
	if (!m_pCodec) {
		return -1;
	}

	m_pCodecCtx = avcodec_alloc_context3(m_pCodec);
	if (!m_pCodecCtx) {
		return -2;
	}

	m_pFrame = av_frame_alloc();
	if (!m_pFrame) {
		return -3;
	}

	return 0;
}

long simplest_encoder_FFmpeg::SetEncoderParam(ffmpeg_video_encoder_param* param)
{

	//m_pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;//表示代表头
	m_pCodecCtx->bit_rate = param->bitRate;
	m_pCodecCtx->width = param->width;
	m_pCodecCtx->height = param->height;
	m_pCodecCtx->gop_size = param->frameRate;// param->gopSize * 4;
	m_pCodecCtx->max_b_frames = param->openBFrame;
	m_pCodecCtx->time_base.den = param->frameRate;
	m_pCodecCtx->time_base.num = 1;
	m_pCodecCtx->thread_count = param->threadCount; // more than one threads seem to increase delay

	/* 根据分辨率设定多线程编码 */
	if (param->threadCount == 0)
	{
		if (m_pCodecCtx->width > 1080)
		{
			m_pCodecCtx->thread_count = 8; // auto or 8
		}
		else if (m_pCodecCtx->width > 720 && m_pCodecCtx->width <= 1080)
		{
			m_pCodecCtx->thread_count = 4;
		}
		else if (m_pCodecCtx->width > 360 && m_pCodecCtx->width <= 720)
		{
			m_pCodecCtx->thread_count = 2;
		}
		else
		{
			m_pCodecCtx->thread_count = 1;
		}
	}

	av_opt_set(m_pCodecCtx->priv_data, "preset", "superfast", 0);
	av_opt_set(m_pCodecCtx->priv_data, "tune", "zerolatency", 0);
	av_opt_set(m_pCodecCtx->priv_data, "profile", "baseline", 0);
	av_opt_set(m_pCodecCtx->priv_data, "crf", "29", 0);
	if (param->sliceCount > 0)
	{
		m_pCodecCtx->slices = param->sliceCount;
	}
	else
	{
		av_opt_set(m_pCodecCtx->priv_data, "slice-max-size", "1260", 0);
	}

	m_pCodecCtx->refs = 3;
	m_pCodecCtx->qmin = 1;
	m_pCodecCtx->qmax = 50;
	m_pCodecCtx->me_range = 16;  //32
	m_pCodecCtx->me_subpel_quality = 3; // 5
	m_pCodecCtx->qcompress = 0.6;

	// 码率控制
	m_pCodecCtx->rc_min_rate = m_pCodecCtx->bit_rate;
	m_pCodecCtx->rc_max_rate = m_pCodecCtx->bit_rate;
	m_pCodecCtx->bit_rate_tolerance = m_pCodecCtx->bit_rate;
	m_pCodecCtx->rc_buffer_size = m_pCodecCtx->bit_rate * 1.2;// *2 / 3;
	m_pCodecCtx->rc_initial_buffer_occupancy = m_pCodecCtx->rc_buffer_size * 3 / 4;
	m_pCodecCtx->scenechange_threshold = 0;
	m_pCodecCtx->flags &= AV_CODEC_FLAG_CLOSED_GOP; // intra-refresh

	m_bitRate = m_pCodecCtx->bit_rate;
	auto itor = m_PixelFormatMap.find(param->pix_fmt);
	if (m_PixelFormatMap.end() == itor)
	{
		return -1;
	}
	m_pCodecCtx->pix_fmt = itor->second;
	m_pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
	if (AV_PIX_FMT_YUV420P == m_pCodecCtx->pix_fmt)
	{
		m_videoBuffMultiple = 1.5f;
	}

	m_pCodecCtx->pix_fmt = itor->second;

	return 0;
}

long simplest_encoder_FFmpeg::StartEncoder()
{
	int ret = avcodec_open2(m_pCodecCtx, m_pCodec, NULL);
	if (ret < 0) {
		return -2;
	}

	m_pFrame->format = m_pCodecCtx->pix_fmt;
	m_pFrame->width = m_pCodecCtx->width;
	m_pFrame->height = m_pCodecCtx->height;

	int picture_size = avpicture_get_size(m_pCodecCtx->pix_fmt, m_pCodecCtx->width, m_pCodecCtx->height);
	uint8_t* picture_buf = (uint8_t*)av_malloc(picture_size);
	avpicture_fill((AVPicture*)m_pFrame, picture_buf, m_pCodecCtx->pix_fmt, m_pCodecCtx->width, m_pCodecCtx->height);

	av_new_packet(&pkt, picture_size);

	return 0;
}

long simplest_encoder_FFmpeg::Encoder(ffmpeg_video_frame* in_frame, ffmpeg_video_frame* out_frame)
{
	int gotOutput = 0;
	long long i = 0;
	int ret = 0;

	//I420
	int y_size = in_frame->height * in_frame->width;
	memcpy(m_pFrame->data[0], in_frame->payload, y_size);
	memcpy(m_pFrame->data[1], in_frame->payload + y_size, y_size / 4);
	memcpy(m_pFrame->data[2], in_frame->payload + y_size + y_size / 4, y_size / 4);
	m_pFrame->pts = in_frame->pts;
		
	if (m_bitRate != m_pCodecCtx->bit_rate)
	{
		avcodec_close(m_pCodecCtx);
		m_pCodecCtx->bit_rate = m_bitRate;

		ret = avcodec_open2(m_pCodecCtx, m_pCodec, NULL);
		if (ret < 0) {
			return -1;
		}
	}
	av_init_packet(&pkt);
	pkt.data = NULL;    // packet data will be allocated by the encoder
	pkt.size = 0;
	if (m_mandatoryKeyFrame)
	{
		m_pFrame->pict_type = AV_PICTURE_TYPE_I;
		m_pFrame->key_frame = 1;
		m_mandatoryKeyFrame = false;
	}
	else
	{
		m_pFrame->pict_type = AV_PICTURE_TYPE_NONE;
	}
	/* encode the image */
	ret = avcodec_encode_video2(m_pCodecCtx, &pkt, m_pFrame, &gotOutput);
	if (ret < 0) {
		return -2;
	}
	if (!gotOutput) {
		return -3;
	}
	bool frameType_I = (pkt.flags & AV_PKT_FLAG_KEY) ? true : false;
	char* buff = (char*)pkt.data;
	unsigned int buffLen = pkt.size;

	if (nullptr != out_frame)
	{
		out_frame->width = m_pFrame->width;
		out_frame->height = m_pFrame->height;
		out_frame->pts = pkt.pts;
		out_frame->dts = pkt.dts;
		out_frame->payload = (char*)buff;
		out_frame->payloadLen = buffLen;
		out_frame->pix_fmt = FFmpegVideoFrame_H264;
	}
	av_free_packet(&pkt);

	return 0;
}

long simplest_encoder_FFmpeg::AdjustBitrate(int bitrate)
{
	m_bitRate = bitrate;
	return 0;
}

long simplest_encoder_FFmpeg::MandatoryKeyFrame()
{
	m_mandatoryKeyFrame = true;
	return 0;
}

long simplest_encoder_FFmpeg::Flush(ffmpeg_video_frame* out_frame)
{
	//Flush Encoder 立刻编码输出
	int gotOutput = 1;
	av_init_packet(&pkt);
	int ret = avcodec_encode_video2(m_pCodecCtx, &pkt, NULL, &gotOutput);
	if (ret <= 0) {
		return -1;
	}

	if (!gotOutput) {
		return -1;
	}
	bool frameType_I = (pkt.flags & AV_PKT_FLAG_KEY) ? true : false;
	char* buff = (char*)pkt.data;
	unsigned int buffLen = pkt.size;
	if (nullptr != out_frame)
	{
		out_frame->width = m_pFrame->width;
		out_frame->height = m_pFrame->height;
		out_frame->pts = pkt.pts;
		out_frame->dts = pkt.dts;
		out_frame->payload = (char*)buff;
		out_frame->payloadLen = buffLen;
		out_frame->pix_fmt = FFmpegVideoFrame_H264;
	}
	av_free_packet(&pkt);

	return 0;
}

long simplest_encoder_FFmpeg::StopEncoder()
{
	if (nullptr != m_pCodecCtx)
	{
		avcodec_close(m_pCodecCtx);
		av_free(m_pCodecCtx);

	}
	m_pCodecCtx = nullptr;

	if (nullptr != m_pFrame && nullptr != m_pFrame->data[0])
	{
		av_freep(&m_pFrame->data[0]);
		m_pFrame->data[0] = nullptr;

	}
	

	if (nullptr != m_pFrame)
	{
		av_frame_free(&m_pFrame);

	}
	m_pFrame = nullptr;
	return 0;
}

long simplest_encoder_FFmpeg::UnInit()
{
	StopEncoder();
	return 0;
}

对应的测试函数 main.cpp

/**
 * 简单的ffmpeg视频编码器测试程序
 *Simple ffmpeg video encoder
 *
 * 梁启东 qidong.liang
 * 18088708700@163.com
 * https://blog.csdn.net/u011645307
 *
 *
 * 本程序测试simplest_encoder_ffmpeg视频编码功能;
 * 测试simplest_encoder_ffmpeg强制输出关键帧的功能
 * 测试simplest_encoder_ffmpeg动态修改码率的功能
 * 测试simplest_encoder_ffmpeg I420数据格式编码的功能
 * This program tests simplest_encoder_ffmpeg video coding function;
 * Test simplest_encoder_ffmpeg forced output keyframe function
 * Test simplest_encoder_ffmpeg4 function of dynamically modifying code rate
 * Test simplest_encoder_ffmpeg i420 data format coding function
 *
 */

#include "simplest_encoder_ffmpeg.h"
#include <iostream>
int main()
{
    FILE* out_file = nullptr;
    FILE* in_file = nullptr;
    int w = 0;
    int h = 0;
    int buffLen = 0;

    fopen_s(&in_file, "../inData/i420_480x272.yuv", "rb+");
    fopen_s(&out_file, "../outData/i420_480x272.h264", "wb+");
    w = 480;
    h = 272;
    buffLen = w * h * 3 / 2;

    if (!in_file)
    {
        printf("in_file is nullptr\n");
        exit(-1);
    }

    ffmpeg_video_encoder_param cfg;
    simplest_encoder_FFmpeg* ffmpeg = new (std::nothrow)simplest_encoder_FFmpeg;
    if (!ffmpeg)
    {
        printf("ffmpeg is nullptr\n");
        delete ffmpeg;
        ffmpeg = nullptr;

        if (in_file)
        {
            fclose(in_file);
        }
        in_file = nullptr;

        if (out_file)
        {
            fclose(out_file);
        }
        out_file = nullptr;
        exit(-1);
    }

    cfg.bitRate = 200000000;
    cfg.pix_fmt = FFmpegVideoFrame_I420;
    cfg.height = h;
    cfg.width = w;
    cfg.openBFrame = 0;
    cfg.frameRate = 25;

    ffmpeg->Init();

    if (0 != ffmpeg->SetEncoderParam(&cfg))
    {
        printf("ffmpeg.SetEncoderParam error\n");
        exit(-1);
    }

    char* inbuff = new char[buffLen];
    char* outbuff = new char[buffLen];
    long long i = 0;
    long long d = 0;
    int outLen = buffLen;
    ffmpeg_video_frame* in_frame = new  (std::nothrow)ffmpeg_video_frame;
    ffmpeg_video_frame* out_frame = new  (std::nothrow)ffmpeg_video_frame;
    if (!in_frame || !out_frame)
    {
        printf("ffmpeg.in_frame :%p, out_frame:%p error\n", in_frame, out_frame);
        exit(-1);
    }
    out_frame->dts = 0;
    out_frame->height = cfg.height;
    out_frame->payload = outbuff;
    out_frame->payloadLen = outLen;
    out_frame->pix_fmt = FFmpegVideoFrame_H264;
    out_frame->pts = 0;
    out_frame->width = cfg.width;

    in_frame->payload = inbuff;
    in_frame->payloadLen = buffLen;
    in_frame->dts = 0;
    in_frame->height = cfg.height;
    in_frame->pix_fmt = FFmpegVideoFrame_I420;
    in_frame->pts = 0;
    in_frame->width = cfg.width;

    ffmpeg->StartEncoder();

    while (true)
    {
        i++;
        outLen = buffLen;
        if (fread(inbuff, 1, buffLen, in_file) != buffLen)
        {
            break;
        }

        if (100 == i)
        {
            ffmpeg->AdjustBitrate(150000000);
        }
        if (i == 10)
        {
            ffmpeg->MandatoryKeyFrame();
        }

        ffmpeg->Encoder(in_frame, out_frame);

        fwrite(out_frame->payload, 1, out_frame->payloadLen, out_file);
    }

    while (true)
    {
        if (0 != ffmpeg->Flush(out_frame))
        {
            break;
        }
        fwrite(out_frame->payload, 1, out_frame->payloadLen, out_file);
    }

    ffmpeg->StopEncoder();

    if (0 != ffmpeg->UnInit())
    {
        printf("ffmpeg.UnInit error\n");
    }
   
    delete ffmpeg;
    ffmpeg = nullptr;

    if (in_file)
    {
        fclose(in_file);
    }
    in_file = nullptr;

    if (out_file)
    {
        fclose(out_file);
    }
    out_file = nullptr;

    std::cout << "Hello World!\n";
}

代码路径

csdn:

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值