【FFMPEG应用篇】基于FFmpeg的PCM和RGB数据统一封装

项目采用工程模式进行构造。

技术解决难点:

1.编码后的PTS时间一定要赋值。2.音视频封装同步问题,其中涉及到PTS同步问题,例如视频25PTS/s  音频43PTS/s(44100采样率/1024每帧),音频要慢于视频所以这种情况下要进行PTS同步计算处理.

主要代码如下

XVideoWriter.h

#pragma once
#include<string>
class AVPacket;
enum XSAMPLEFMT
{
	X_S16 = 1,
	X_FLATP = 8
};


class XVideoWriter
{
public:
	virtual bool Init(const char* file)=0;
	virtual void Close() = 0;
	virtual bool AddVideoStream()=0;
	virtual bool AddAudeoStream() = 0;
	virtual AVPacket* EncodeVideo(const unsigned char *rgb)=0;
	virtual AVPacket* EncodeAudeo(const unsigned char *pcm) = 0;
	virtual bool WriteHead() = 0;

	//会释放PKT空间
	virtual bool WriteFrame(AVPacket *pkt) = 0;
	virtual bool WriteEnd() = 0;
	virtual bool IsVideoBefore() = 0;


	static XVideoWriter *Get(unsigned short index=0);
	~XVideoWriter();
protected:
	XVideoWriter();
public:
	std::string filename;
	//视频输入参数
	int inWidth = 1920;
	int inHeight = 1080;
	int inPixFmt = 30;
	//音频输入参数
	int inSampleRate = 44100;
	int inChannels = 2;
	XSAMPLEFMT inSampleFmt = X_S16;
	//视频输出参数
	int vBitrate = 400000000;
	int outwidth = 1920;
	int outheight = 1080;
	int outfps = 25;
    //音频输出参数
	int aBitrate = 64000;
	int sample_rate = 44100;
	int channels = 2;
	int outSampleType = X_FLATP;
	int nb_Asample = 1024;//每帧输出样本数量
};

rgb_pcm_to_mp4.cpp

#include "XVideoWriter.h"
#include<iostream>
extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
}

using namespace std;

class CXVideoWriter :public XVideoWriter
{
public:
	AVFormatContext *ic = NULL;//封装MP4输出上下文
	AVCodecContext  *vc = NULL;//视频编码器
	AVCodecContext  *ac = NULL;//音频编码器
	AVStream *vs = NULL;//视频流
	AVStream *as = NULL;//音频流
	SwsContext *ctx = NULL;	//像素转换上下文
	SwrContext *actx = NULL;//音频重采样上下文
	AVFrame *yuv = NULL;//输出YUV空间
	AVFrame *pcm = NULL;//输出YUV空间
	int vpts = 0;//视频的pts
	int apts = 0;//音频的pts
	void Close()
	{
		if (ic)avformat_close_input(&ic);
		if (vc)
		{
			avcodec_close(vc);
			avcodec_free_context(&vc);
		}
		if (ac)
		{
			avcodec_close(ac);
			avcodec_free_context(&ac);
		}
		if (ctx)
		{
			sws_freeContext(ctx);
			ctx = nullptr;
		}
		if (yuv)
		{
			av_frame_free(&yuv);
		}
		if (pcm)
		{
			av_frame_free(&pcm);
		}

		if (actx)
		{
			swr_free(&actx);
		}

		
	}
	bool Init(const char* file)
	{
		Close();
		//封装文件输出上下文
		avformat_alloc_output_context2(&ic, 0, 0, file);
		if (!ic)
		{
			 cout<< "avformat_alloc_output_context2 NO" << endl;
			return false;
		}

		filename = file;
		return true;
	}

	bool AddVideoStream()
	{
		if (!ic)return false;
		//1 创建视频编码器
		AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
		if (!codec)
		{
			cout << "avcodec_find_encoder No" << endl;
			return false;
		}
		vc = avcodec_alloc_context3(codec);
		if (!vc)
		{
			cout << "avcodec_alloc_context3 No" << endl;
			return false;
		}

		vc->bit_rate = vBitrate;
		vc->width = outwidth;
		vc->height = outheight;
		vc->time_base = { 1,outfps };
		vc->framerate = { outfps,1 };

		vc->gop_size = 50;
		vc->max_b_frames = 0;
		vc->pix_fmt = AV_PIX_FMT_YUV420P;
		vc->codec_id = AV_CODEC_ID_H264;
		vc->thread_count = 8;
		//全局编码
		vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;


		//打开编码器
		int ret = avcodec_open2(vc, codec, 0);
		if (ret < 0)
		{
			cout << "avcodec_open2 No" << endl;
			return false;
		}

		cout << "avcodec_open2 OK" << endl;

		//添加视频流到输出上下文
		vs = avformat_new_stream(ic, NULL);
		vs->id = 0;
		vs->codecpar->codec_tag = 0;
		avcodec_parameters_from_context(vs->codecpar, vc);

		cout << "========================" << endl;
		av_dump_format(ic, 0, filename.c_str(), 1);
		cout << "========================" << endl;


		ctx = sws_getCachedContext(ctx,
			outwidth, outheight, AV_PIX_FMT_BGRA,
			outwidth, outheight, AV_PIX_FMT_YUV420P,
			SWS_BICUBIC, NULL, NULL, NULL);
		if (!ctx)
		{
			cout << "sws_getCachedContext No" << endl;
			return false;
		}
		//输入的空间
		unsigned char *rgb = new unsigned char[outwidth * outheight * 4];

		//输出的空间
		if (!yuv)
		{
			yuv = av_frame_alloc();
			yuv->format = AV_PIX_FMT_YUV420P;
			yuv->width = outwidth;
			yuv->height = outheight;
			yuv->pts = 0;

			//分配frame空间
			ret = av_frame_get_buffer(yuv, 32);
			if (ret < 0)
			{
				cout << "av_frame_get_buffer No" << endl;
				return false;
			}
			return true;
		}
	}
	bool AddAudeoStream()
	{
		if (!ic)return false;
		//1 找到音频编码器
		AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
		if (!codec)
		{
			cout << "audio avcodec_find_encoder NO" << endl;
			return false;
		}
		//2 创建并打开音频编码器
		ac = avcodec_alloc_context3(codec);
		if (!codec)
		{
			cout << "audio avcodec_alloc_context3 NO" << endl;
			return false;
		}

		ac->bit_rate = aBitrate;
		ac->sample_rate = sample_rate;
		ac->sample_fmt = AVSampleFormat(outSampleType);
		ac->channels = channels;
		ac->channel_layout = av_get_default_channel_layout(channels);
		ac->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;


		int ret = avcodec_open2(ac, codec, NULL);
		if (ret < 0)
		{
			cout << "avcodec_open2 NO" << endl;
			avcodec_free_context(&ac);
			return false;
		}
		cout << "audio avcodec_open2 OK" << endl;


		//3 新增音频流
		as = avformat_new_stream(ic, NULL);
		if (!as)
		{
			cout << "avformat_new_stream NO" << endl;
			return false;
		}
		as->codecpar->codec_tag = 0;
		avcodec_parameters_from_context(as->codecpar, ac);

		av_dump_format(ic, 0, filename.c_str(), 1);


		//4 音频重采样
		actx = swr_alloc();
		actx = swr_alloc_set_opts(actx,
			ac->channel_layout,	//输出格式
			ac->sample_fmt,					//输出样本格式
			ac->sample_rate,					//输出采样率
			av_get_default_channel_layout(inChannels),//输入格式
			(AVSampleFormat)inSampleFmt,
			inSampleRate,
			0, 0
		);
		ret = swr_init(actx);
		if (ret != 0)
		{
			char buf[1024] = { 0 };
			av_strerror(ret, buf, sizeof(buf) - 1);
			cout << "swr_init  failed! :" << buf << endl;
			return false;
		}
		//5 音频输出AVFrame
		if (!pcm)
		{
			pcm = av_frame_alloc();
			pcm->format = ac->sample_fmt;
			pcm->channels = ac->channels;
			pcm->channel_layout = ac->channel_layout;
			pcm->nb_samples = nb_Asample; //一帧音频存放的样本数量
			ret = av_frame_get_buffer(pcm, 0);
			if (ret < 0)
			{
				cout << "av_frame_get_buffer No :" << endl;
				return false;
			}
			cout << "audio AVFrame Create OK" << endl;
		}



		return true;
	}
	AVPacket* EncodeVideo(const unsigned char *rgb)
	{
		AVPacket *p = NULL;
		//rgb to yuv
		uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
		indata[0] = (uint8_t*)rgb;
		int inlinesize[AV_NUM_DATA_POINTERS] = { 0 };
		inlinesize[0] = outwidth * 4;//*4字节数

		int h = sws_scale(ctx, indata, inlinesize, 0, outheight,
				yuv->data,
				yuv->linesize);
		if (h <= 0)
				return p;
		//cout << h << "|";

		yuv->pts = vpts;
		vpts++;
		//encoder
		int ret = avcodec_send_frame(vc, yuv);
		if (ret != 0)
		{
			return NULL;
		}
		p=av_packet_alloc();
	
		//一次发送可能多次接收
		ret = avcodec_receive_packet(vc,p);
		if (ret != 0 || p->size<=0)
		{
			av_packet_free(&p);
			return NULL;
		}

		//自动计算pts
		av_packet_rescale_ts(p, vc->time_base, vs->time_base);
		p->stream_index = vs->index;
		return p;
	}
	AVPacket* EncodeAudeo(const unsigned char *d)
	{

		//1 音频重采样
		const uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 };
		data[0] = (uint8_t*)d;
		int len = swr_convert(actx,
			pcm->data, pcm->nb_samples,		//输出
			data, pcm->nb_samples);	//输入

		cout << len << "*";


		//2 音频编码
		int ret = avcodec_send_frame(ac, pcm);
		if (ret != 0)return nullptr;

		AVPacket* pkt = av_packet_alloc();
		av_init_packet(pkt);
		ret = avcodec_receive_packet(ac, pkt);
		if (ret != 0)
		{
			av_packet_free(&pkt);
			return nullptr;
		}
		cout << pkt->size << "|";
		pkt->stream_index = as->index;

		pkt->pts = apts;
		pkt->dts = pkt->pts;
		apts += av_rescale_q(pcm->nb_samples, { 1,ac->sample_rate },ac->time_base);//可以根据音频样本大小推算PTS

		return pkt;
	}
	bool WriteHead()
	{
		if (!ic)return false;
		//打开IO
		int ret = avio_open(&ic->pb, filename.c_str(), AVIO_FLAG_WRITE);//打开输出文件IO
		if (ret!=0)
		{
			cout << "avio_open failed" << endl;
			return false;
		}


		//写入封装头
		ret = avformat_write_header(ic, NULL);
		if (ret!=0)
		{
			cout << "avformat_write_header failed" << endl;
			return false;
		}
		cout << "write"<<filename<<" OK "<< endl;
		return true;
	}
	bool WriteFrame(AVPacket *pkt)
	{
		if (!ic || !pkt || pkt->size <= 0)return false;
		if (av_interleaved_write_frame(ic, pkt) != 0)return false;
		return true;
	}

	bool WriteEnd()
	{
		if (!ic || !ic->pb)return false;
		//写入视频索引尾部信息
		if (av_write_trailer(ic) != 0)
		{
			cout << "av_write_trailer No" << endl;
			return false;
		}
		if (avio_close(ic->pb) != 0)
		{
			cout << "avio_close No" << endl;
			return false;
		}
		cout << "Write End OK" << endl;

	}


	/*如果视频在前,先写入视频信息,反之先写入音频数据*/
	bool IsVideoBefore()
	{
		if (!ic || !as || !vs)return false;

		int re = av_compare_ts(vpts,vc->time_base,apts,ac->time_base);
		if (re <= 0)
		{
			return true;
		}
		return false;
	}

};

bool XVideoWriter::Init(const char * file)
{

	return true;
}

XVideoWriter * XVideoWriter::Get(unsigned short index)
{
	static bool isfirst = true;
	if (isfirst)
	{
		//初始化封装库
		av_register_all();

		//初始化网络库 (可以打开rtsp rtmp http 协议的流媒体视频)
		avformat_network_init();

		//注册解码器
		avcodec_register_all();

		isfirst = false;
	}
	static CXVideoWriter wrs[65535];
	return &wrs[index];
}

XVideoWriter::~XVideoWriter()
{
}

XVideoWriter::XVideoWriter()
{
}

rgb_pcm_to_mp4.cpp

// rgb_pcm_to_mp4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include"XVideoWriter.h"
#include <iostream>

int main()
{
	char outfile[] = "rgb_pcm_to_mp4.mp4";
	char rgbfile[] = "test.rgb";
	char pcmfile[] = "test.pcm";
	XVideoWriter *xw = XVideoWriter::Get(0);
	xw->Init(outfile);
	xw->AddVideoStream();
	xw->AddAudeoStream();
	FILE *fp = fopen(rgbfile, "rb");
	if (!fp)
	{
		std::cout << "fopen rgbfile NO" << std::endl;
		return -1;
	}
	int size = xw->outwidth * xw->outheight * 4;
	unsigned char *rgb = new unsigned char[size];

	int asize = xw->nb_Asample * xw->inChannels * 2;
	unsigned char *pcm = new unsigned char[asize];

	FILE *fa = fopen(pcmfile, "rb");
	if (!fa)
	{
		std::cout << "fopen pcmfile NO" << std::endl;
		return -1;
	}

	xw->WriteHead();
	AVPacket *pkt = NULL;
	int len = 0;
	for (;;)
	{
		if (xw->IsVideoBefore())//视频在前
		{
			int len = fread(rgb, 1, size, fp);
			if (len <= 0)break;

			pkt = xw->EncodeVideo(rgb);
			if (pkt)std::cout << ".";
			else
			{
				std::cout << "-";
				continue;
			}
			if (xw->WriteFrame(pkt))
			{
				std::cout << "+";
			}
		}
		else
		{
			len = fread(pcm, 1, asize, fa);
			if (len <= 0)break;
			pkt = xw->EncodeAudeo(pcm);
			xw->WriteFrame(pkt);
		}
	
	}
	xw->WriteEnd();
	delete rgb;
	rgb = nullptr;
	std::cout << "*******************end***************" << std::endl;

	return 0;
}

项目链接

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

༄yi笑奈何

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值