extern "C"
{
#include
#include
}
#include
using namespace std;
#pragma comment(lib,"avformat.lib")
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avutil.lib")
#pragma comment(lib,"swscale.lib")
int main()
{
char infile[] = "out.rgb";
char outfile[] = "rgb.mp4";
//muxer,demuters
av_register_all();
avcodec_register_all();//注册编解码器
FILE *fp = fopen(infile, "rb");
if (!fp)
{
cout << infile << " open failed!" << endl;
getchar();
return -1;
}
int width = 848;
int height = 480;
int fps = 25;//fps表示平均帧率,总帧数除以总时长(以s为单位)。
width = 1920;
height = 1080;
//1 create codec
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec)
{
cout << " avcodec_find_encoder AV_CODEC_ID_H264 failed!" << endl;
getchar();
return -1;
}
AVCodecContext *c = avcodec_alloc_context3(codec);
if (!c)
{
cout << " avcodec_alloc_context3 failed!" << endl;
getchar();
return -1;
}
//压缩比特率
c->bit_rate = 400000000;//视频比特率是指每秒传送的比特 (bit)数。. 单位为bps (Bit Per Second),比特率越高,每秒传送 数据 就越多, 画质 就越清晰。
c->width = width;
c->height = height;
c->time_base = { 1, fps };//fps表示平均帧率,总帧数除以总时长(以s为单位)。
c->framerate = { fps, 1 };
//画面组大小,关键帧
c->gop_size = 50;
c->max_b_frames = 0;//指定不需要B帧
c->pix_fmt = AV_PIX_FMT_YUV420P;//指定像素格式
c->codec_id = AV_CODEC_ID_H264;
c->thread_count = 8;
//全局的编码信息
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
int ret = avcodec_open2(c, codec, NULL);
if (ret < 0)
{
cout << " avcodec_open2 failed!" << endl;
getchar();
return -1;
}
cout << "avcodec_open2 success!" << endl;
//2 create out context
AVFormatContext *oc = NULL;
avformat_alloc_output_context2(&oc, 0, 0, outfile);
//3 add video stream
AVStream *st = avformat_new_stream(oc, NULL);
//st->codec = c;
st->id = 0;
st->codecpar->codec_tag = 0;//?
avcodec_parameters_from_context(st->codecpar, c);
cout << "===============================================" << endl;
av_dump_format(oc, 0, outfile, 1);
cout << "===============================================" << endl;
//4 rgb to yuv
SwsContext *ctx = NULL;
ctx = sws_getCachedContext(ctx,
width, height, AV_PIX_FMT_BGRA,
width, height, AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL, NULL, NULL
);
//输入空间
unsigned char *rgb = new unsigned char[width*height * 4];
//输出的空间,未编码的原始数据
AVFrame *yuv = av_frame_alloc();//此时没有真正的被开辟buffer
yuv->format = AV_PIX_FMT_YUV420P;
yuv->width = width;
yuv->height = height;
ret = av_frame_get_buffer(yuv, 32);//此时才根据上面的信息开辟真正的buffer,32是对奇数
if (ret < 0)
{
cout << " av_frame_get_buffer failed!" << endl;
getchar();
return -1;
}
//5 wirte mp4 head
ret = avio_open(&oc->pb, outfile, AVIO_FLAG_WRITE);
if (ret < 0)
{
cout << " avio_open failed!" << endl;
getchar();
return -1;
}
ret = avformat_write_header(oc, NULL);//写入输出的头文件
if (ret < 0)
{
cout << " avformat_write_header failed!" << endl;
getchar();
return -1;
}
int p = 0;
for (;;)
{
/*
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
参数
ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
size -- 这是要读取的每个元素的大小,以字节为单位。
nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
*/
int len = fread(rgb, 1, width*height * 4, fp);//width*height为一屏有多少个像素点,*4是因为一个像素需要4个字节
if (len <= 0)
{
break;
}
uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
indata[0] = rgb;
int inlinesize[AV_NUM_DATA_POINTERS] = { 0 };
inlinesize[0] = width * 4;
/*
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);
1.参数 SwsContext *c, 转换格式的上下文。也就是 sws_getContext 函数返回的结果。
2.参数 const uint8_t *const srcSlice[], 输入图像的每个颜色通道的数据指针。其实就是解码后的AVFrame中的data[]数组。因为不同像素的存储格式不同,所以srcSlice[]维数
也有可能不同。
以YUV420P为例,它是planar格式,它的内存中的排布如下:
YYYYYYYY UUUU VVVV
使用FFmpeg解码后存储在AVFrame的data[]数组中时:
data[0]——-Y分量, Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8……
data[1]——-U分量, U1, U2, U3, U4……
data[2]——-V分量, V1, V2, V3, V4……
linesize[]数组中保存的是对应通道的数据宽度 ,
linesize[0]——-Y分量的宽度
linesize[1]——-U分量的宽度
linesize[2]——-V分量的宽度
而RGB24,它是packed格式,它在data[]数组中则只有一维,它在存储方式如下:
data[0]: R1, G1, B1, R2, G2, B2, R3, G3, B3, R4, G4, B4……
这里要特别注意,linesize[0]的值并不一定等于图片的宽度,有时候为了对齐各解码器的CPU,实际尺寸会大于图片的宽度,这点在我们编程时(比如OpengGL硬件转换/渲染)要特别注意,否则解码出来的图像会异常。
3.参数const int srcStride[],输入图像的每个颜色通道的跨度。.也就是每个通道的行字节数,对应的是解码后的AVFrame中的linesize[]数组。根据它可以确立下一行的起始位置,不过stride和width不一定相同,这是因为:
a.由于数据帧存储的对齐,有可能会向每行后面增加一些填充字节这样 stride = width + N;
b.packet色彩空间下,每个像素几个通道数据混合在一起,例如RGB24,每个像素3字节连续存放,因此下一行的位置需要跳过3*width字节。
4.参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。这种设置是为了多线程并行,例如可以创建两个线程,第一个线程处理 [0, h/2-1]行,第二个线程处理 [h/2, h-1]行。并行处理加快速度。
5.参数uint8_t *const dst[], const int dstStride[]定义输出图像信息(输出的每个颜色通道数据指针,每个颜色通道行字节数)
此处是看另一位博主的解释,我觉得解释的非常非常好,清晰明了
*/
int h = sws_scale(ctx, indata, inlinesize, 0, height,
yuv->data, yuv->linesize
);
if (h <= 0)
break;
//6 encode frame
yuv->pts = p;//注意PTS的计算,https://zhuanlan.zhihu.com/p/101480401(PTS)PTS:Presentation Time Stamp
//yuv->pict_type = AV_PICTURE_TYPE_I;
p = p + 3600;
ret = avcodec_send_frame(c, yuv);//注意frame中的是原始的数据,pkt 中是压缩后的数据
if (ret != 0)
{
continue;
}
AVPacket pkt;
av_init_packet(&pkt);
ret = avcodec_receive_packet(c, &pkt);//注意一个avcodec_receive_packet(c, &pkt)不一定只对应一个av_interleaved_write_frame(oc, &pkt);,有可能会对应多次,需要写循环接受,直到没有。
if (ret != 0)
continue;
//av_write_frame(oc, &pkt);
//av_packet_unref(&pkt);
av_interleaved_write_frame(oc, &pkt);//会按照dps的次序写入,并且不需要去释放pkt里面的空间,推荐使用这种方式
cout << "<"<";
}
//写入视频索引
av_write_trailer(oc);
//关闭视频输出io
avio_close(oc->pb);
//清理封装输出上下文
avformat_free_context(oc);
//关闭编码器
avcodec_close(c);
//清理编码器上下文
avcodec_free_context(&c);
//清理视频重采样上下文
sws_freeContext(ctx);
cout << "======================end=========================" << endl;
delete rgb;
getchar();
return 0;
}
上面的代码中包含每一个函数的解释,欢迎大家进行交流
不大清楚YUV的推荐看这一篇博客,如何理解 YUV ? - 知乎 (zhihu.com)
我觉得大佬写的非常非常nice.