视频编码
1.读取RGB文件转换为yuv
2.压缩为h264
3.封装为MP4
YUV
1.“Y"表示明亮度,也就是灰度值
2.而"U” 和 “V” 则表示色度
音视频类的封装
1.对外使用接口类,对象由内部创建
2.隐藏ffmpeg,调用者不需要引用ffmpeg头文件
通过ffmpeg工具抽取数据
1.ffmpeg -i test.mp4 -f s16le test.pcm
2.ffmpeg -i test.mp4 -pix_fmt bgra test.rgb
CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
project(ffmpeg_pro VERSION 0.1.0 LANGUAGES CXX C)
##配置 预编译库 如ffmpeg库 路径
# find_library(AVCODEC_LIBRARY avcodec)
# find_library(AVFORMAT_LIBRARY avformat)
# find_library(AVUTIL_LIBRARY avutil)
# find_library(AVDEVICE_LIBRARY avdevice)
##加入预编译库的头文件 如ffmpeg库 的头文件
#include_directories(include)
include_directories(./)
add_executable(ffmpeg_pro rgb_pcm_to_mp4.cpp XVideoWriter.cpp)
message(STATUS ${AVCODEC_LIBRARY})
target_link_libraries(
ffmpeg_pro
pthread
swresample
m
swscale
avformat
avcodec
avutil
avfilter
avdevice
postproc
z
lzma
rt)
XVideoWriter.h
#pragma once
#include <string>
class AVPacket;
enum XSAMPLEFMT
{
X_S16 = 1,
X_FLATP = 8
};
class XVideoWriter
{
public:
//视频输入参数
int inWidth = 848;
int inHeight = 480;
int inPixFmt = 28;//30 //AV_PIX_FMT_BGRA
//音频的输入参数
int inSampleRate = 44100;
int inChannels = 2;
XSAMPLEFMT inSampleFmt = X_S16;
//视频输出参数
int vBitrate = 4000000;
int outWidth = 848;
int outHeight = 480;
int outFPS = 25;
//音频输出参数
int aBitrate = 64000;
int outChannels = 2;
int outSampleRate = 44100;
XSAMPLEFMT outSampleFmt = X_FLATP;
int nb_sample = 1024; //输入输出的每帧数据每通道样本数量
virtual bool Init(const char* file) = 0;
virtual void Close() = 0;
virtual bool AddVideoStream() = 0;
virtual bool AddAudioStream() = 0;
//把转yuv和编码放在一起做
virtual AVPacket *EncodeVideo(const unsigned char *rgb) = 0;
virtual AVPacket *EncodeAudio(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();
std::string filename;
protected:
XVideoWriter();
};
XVideoWriter.cpp
#include "XVideoWriter.h"
extern "C"
{
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
}
#include <iostream>
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 *vsc = NULL; //像素转换的上下文
SwrContext *asc = NULL; //音频重采样上下文
AVFrame *yuv = NULL; //输出yuv
AVFrame *pcm = NULL; //重采样后输出的pcm
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(vsc)
{
sws_freeContext(vsc);
vsc = NULL;
}
if(yuv){ av_frame_free(&yuv);}
if(pcm){ av_frame_free(&pcm);}
if(asc)
{
swr_free(&asc);
}
}
bool Init(const char* file)
{
Close();
//封装文件的上限文
avformat_alloc_output_context2(&ic,NULL,NULL,file);
if(!ic)
{
cerr<<"avformat_alloc_output_context2 failed!"<<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)
{
cerr<<"avcodec_find_encoder AV_CODEC_ID_H264 failed!"<<endl;
return false;
}
vc = avcodec_alloc_context3(codec);
if(!vc)
{
cerr<<"avcodec_alloc_context3 failed!"<<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;
//两个非b帧之间的b帧数量
vc->max_b_frames = 0;
//像素格式
vc->pix_fmt = AV_PIX_FMT_YUV420P;
//编码id
vc->codec_id = AV_CODEC_ID_H264;
//选项的设置
av_opt_set(vc->priv_data,"preset","superfast",0);
//统一的封装头
vc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
//打开编码器
int ret = avcodec_open2(vc,codec,NULL);
if(ret != 0)
{
cerr<<"avcodec_open2 failed!"<<endl;
return false;
}
cout<<"avcodec_open2 success!"<<endl;
//添加视频流到上下文
vs = avformat_new_stream(ic,NULL);
//不包含编码信息
vs->codecpar->codec_tag = 0;
//把编码器信息拷贝到封装流里面
avcodec_parameters_from_context(vs->codecpar,vc);
av_dump_format(ic,0,filename.c_str(),1);
//像素(尺寸)转换的上下文 rgb to yuv
vsc = sws_getCachedContext(vsc,
inWidth,inHeight,(AVPixelFormat)inPixFmt,//输入参数
outWidth,outHeight,AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL,NULL,NULL
);
if(!vsc)
{
cerr<<"sws_getCachedContext failed!"<<endl;
return false;
}
if(!yuv)
{
//对对象成员初始化
yuv = av_frame_alloc();
yuv->format = AV_PIX_FMT_YUV420P;
yuv->width = outWidth;
yuv->height = outHeight;
//需要指定pts 每一帧加1
yuv->pts = 0;
//创建yuv 空间
int ret = av_frame_get_buffer(yuv,32);
if(ret != 0)
{
cerr<<"sws_getCachedContext failed!"<<endl;
return false;
}
}
return true;
}
AVPacket *EncodeVideo(const unsigned char *rgb)
{
AVPacket *p = NULL;
uint8_t *inData[AV_NUM_DATA_POINTERS] = { 0 };
inData[0] = (uint8_t *)rgb;
int insize[AV_NUM_DATA_POINTERS] = {0};
insize[0] = inWidth * 4;
//rgb to yuv
int h = sws_scale(vsc, inData,insize,0,inHeight,
yuv->data,yuv->linesize);
if(h<0)
return NULL;
//cout<<h<<"|";
yuv->pts = vpts;
vpts++;
//encode
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;
}
av_packet_rescale_ts(p,vc->time_base,vs->time_base);
p->stream_index = vs->index;
return p;
}
bool WriteHead()
{
if(!ic) return false;
//打开io
int ret = avio_open(&ic->pb,filename.c_str(),AVIO_FLAG_WRITE);
if(ret != 0)
{
cerr<<"avio_open failed!"<<endl;
return false;
}
cout<<"write "<<filename<<" head success"<<endl;
//写入封装头
ret = avformat_write_header(ic,NULL);
if(ret != 0)
{
cerr<<"avformat_write_header failed!"<<endl;
return false;
}
cout<<"write "<<filename<<" head success"<<endl;
return true;
}
bool WriteFrame(AVPacket *pkt)
{
if(!ic || !pkt || pkt->size <= 0) return false;
//av_write_frame
if(av_interleaved_write_frame(ic,pkt) != 0) return false;
return true;
}
virtual bool WriteEnd()
{
if(!ic || !ic->pb) return false;
//写入尾部信息和索引
if(av_write_trailer(ic) != 0)
{
cerr<<"av_write_trailer failed!"<<endl;
return false;
}
//关闭输入io
if(avio_close(ic->pb) != 0)
{
cerr<<"avio_close failed!"<<endl;
return false;
}
cout<<"WriteEnd success!"<<endl;
return true;
}
bool AddAudioStream()
{
//1.找到音频编码
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if(!codec)
{
cerr<<"avcodec_find_encoder AV_CODEC_ID_AAC failed!"<<endl;
return false;
}
//2.创建并打开音频编码器
ac = avcodec_alloc_context3(codec);
if(!ac)
{
cerr<<"avcodec_alloc_context3 failed!"<<endl;
}
ac->bit_rate = aBitrate;
ac->sample_rate = outSampleRate;
ac->sample_fmt = (AVSampleFormat)outSampleFmt;
ac->channels = outChannels;
ac->channel_layout = av_get_default_channel_layout(outChannels);
ac->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
int ret = avcodec_open2(ac,codec,NULL);
if(ret != 0)
{
avcodec_free_context(&ac);
cerr<<"avcodec_open2 failed!"<<endl;
return false;
}
cout<<"avcodec_open2 AV_CODEC_ID_AAC success"<<endl;
//新曾音频流
as = avformat_new_stream(ic,NULL);
if(!as)
{
cerr<<"avformat_new_stream failed!"<<endl;
return false;
}
//参数设置
as->codecpar->codec_tag = 0;
avcodec_parameters_from_context(as->codecpar,ac);
av_dump_format(ic,0,filename.c_str(),1);
//音频重采样上下文
asc = swr_alloc_set_opts(asc,
ac->channel_layout,ac->sample_fmt,ac->sample_rate,//输入格式
av_get_default_channel_layout(inChannels),(AVSampleFormat)inSampleFmt,inSampleRate,
0,0);
ret = swr_init(asc);
if(ret != 0)
{
cerr<<"swr_init failed!"<<endl;
return false;
}
//音频重采样后输出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_sample;//一帧音频的采样数量
ret = av_frame_get_buffer(pcm,0);
if(ret < 0)
{
cerr<<"av_frame_get_buffer failed "<<endl;
return false;
}
cout<<"audio av_frame_get_buffer success"<<endl;
}
return true;
}
AVPacket* EncodeAudio(const unsigned char *d)
{
const uint8_t *data[AV_NUM_DATA_POINTERS] = {0};
data[0] = (uint8_t *)d;
int len = swr_convert(asc,pcm->data,pcm->nb_samples,
data,pcm->nb_samples);
cout<<len<<"*";
//2音频编码
int ret = avcodec_send_frame(ac,pcm);
if(ret != 0)
{
return NULL;
}
//需要对pkt进行释放
AVPacket *pkt = av_packet_alloc();
av_init_packet(pkt);
ret = avcodec_receive_packet(ac,pkt);
if(ret != 0)
{
av_packet_free(&pkt);
return NULL;
}
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);
return pkt;
}
bool IsVideoBefore()
{
if(!ic || !as || !vs)
{
std::cout<<"!ic || !as || !vs = false"<<std::endl;
return false;
}
int ret = av_compare_ts(vpts,vc->time_base,apts,ac->time_base);
if(ret <= 0)
{
std::cout<<"IsVideoBefore = true"<<std::endl;
return true;
}
std::cout<<"IsVideoBefore = false"<<std::endl;
return false;
}
};
XVideoWriter* XVideoWriter::Get(unsigned short index)
{
static bool isfirst = true;
if(isfirst)
{
av_register_all();
avcodec_register_all();
isfirst = false;
}
static CXVideoWriter wrs[65535];
return &wrs[index];
}
XVideoWriter::XVideoWriter()
{
}
XVideoWriter::~XVideoWriter()
{
}
bool XVideoWriter::AddVideoStream()
{
return true;
}
rgb_pcm_to_mp4.cpp
#include "XVideoWriter.h"
#include <iostream>
using namespace std;
int main()
{
char outfile[] = "rgbpcm.mp4";
char rgbfile[] = "test.rgb";
char pcmfile[] = "test.pcm";
XVideoWriter *xw = XVideoWriter::Get(0);
cout<<xw->Init(outfile);
cout<<xw->AddVideoStream();
xw->AddAudioStream();
FILE *fp = fopen(rgbfile,"rb");
if(!fp)
{
cout<<"fopen "<<rgbfile<<" failed!"<<endl;
return -1;
}
FILE *fa = fopen(pcmfile,"rb");
if(!fa)
{
cout<<"open "<<pcmfile<<" failed!"<<endl;
return -1;
}
int size = xw->inWidth*xw->inHeight*4;
unsigned char *rgb = new unsigned char[size];
int asize = xw->nb_sample * xw->inChannels * 2;
unsigned char *pcm = new unsigned char[asize];
xw->WriteHead();
AVPacket *pkt = NULL;
int len = 0;
for(;;)
{
if(xw->IsVideoBefore())
{
len = fread(rgb,1,size,fp);
if(len<=0)
break;
pkt = xw->EncodeVideo(rgb);
if(pkt)
cout<<".";
else
{
cout<<"-";
continue;
}
if(xw->WriteFrame(pkt))
{
cout<<"+";
}
}
else
{
len = fread(pcm,1,asize,fa);
if(len <= 0) break;
pkt = xw->EncodeAudio(pcm);
xw->WriteFrame(pkt);
}
}
xw->WriteEnd();
delete rgb;
rgb = NULL;
cout<<"\n===========================end===========================\n";
//rgb 转 yuv
//编码视频
return 0;
}