项目采用工程模式进行构造。
技术解决难点:
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;
}