FFmpeg 对一个rgb文件转换为yuv再封装到mp4里面

cpp 复制代码
extern "C"
{
	#include <libavformat/avformat.h>
	#include <libswscale/swscale.h>
}

#include <iostream>
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 << "<"<<pkt.size<<">";
	}
	
	//写入视频索引
	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.

相关推荐
MonkeyKing_sunyuhua7 小时前
FFmpeg将mp4的文件转化为m4a
ffmpeg
zanglengyu9 小时前
RK3568硬解码并与Qt界面融合显示深入探究
开发语言·qt·ffmpeg·rk3568硬解码rtsp
橘子味的茶二1 天前
ffmpeg内存模型
ffmpeg
TPCloud1 天前
windows 11编译安装ffmpeg(包含ffplay)
windows·ffmpeg·源码安装·mysys
runing_an_min2 天前
ffmpeg视频滤镜:缓入缓出-fade
ffmpeg·音视频·fade·缓出·缓入
ssslar2 天前
FFMPEG录屏(22)--- Linux 下基于X11枚举所有显示屏,并获取大小和截图等信息
linux·运维·ffmpeg
MonkeyKing_sunyuhua2 天前
FFmpeg 怎么裁剪m4a的音频,从一个时间点开始,裁剪15秒钟的视频
ffmpeg·音视频
DO_Community3 天前
教程:FFmpeg结合GPU实现720p至4K视频转换
ffmpeg·音视频
x66ccff3 天前
使用NVIDIA GPU加速FFmpeg视频压制:完全指南
ffmpeg·音视频
冷眼Σ(-᷅_-᷄๑)3 天前
如何使用ffmpeg命令行进行录屏
ffmpeg