YUV数据 编程h264数据后,h264视频画面模糊大片绿色紫色

来源:11-11 实战H264编码2

darklgd

2022-06-13

yuv 数据正常 但是编码后编程的h264 用ffplay打开后 画面很糊 而且有大片的 绿色和紫色 是啥原因 怎么解决啊?

在这里输
/// <summary>
/// 打开视频编码器
/// </summary>
/// <param name="width">视频宽度</param>
/// <param name="height">视频高度</param>
/// <param name="enc_ctx">编码器上下文</param>
static void open_encoder(int width, int height, AVCodecContext** enc_ctx) {

	AVCodec* codec = NULL;
	//查找使用libx264编码器 h264就是用的这个
	codec = avcodec_find_encoder_by_name("libx264");
	if (!codec)
	{
		printf("Codec libx264 not found \n");
		//编码器没有找到可以直接退出了
		exit(1);
	}
	//创建一个对应编码器的上下文
	*enc_ctx = avcodec_alloc_context3(codec);
	if (!enc_ctx)
	{
		printf("Could  not  a allocate video codec  Context! \n");
		//编码器上下文没有找到可以直接退出了
		exit(1);
	}

	//SPS/PPS
	//我们选择最高的  这种支持的压缩特性是最多的 11-1里面的发展历史的H264 profile  或者10-6最后面
	(*enc_ctx)->profile = FF_PROFILE_H264_HIGH_444;
	//这个等价level 5.0 
	(*enc_ctx)->level = 50;
	//设置编码后分辨率
	(*enc_ctx)->width = 640;
	(*enc_ctx)->height = 480;

	//设置GOP 强相关的一组帧,设置很小 I帧会很多,码流就会很大 反之 I帧就会很小,但是如果网络不好I帧丢了,这个就可能要等很长时间 根据业务进行自己处理。 
	(*enc_ctx)->gop_size = 250;
	//如果我们上面GOP设置的比较大, 在过程中有很多图像其实有很大的变化的时候,达到一定数值,可以设置这个值来自动插入一个I帧
	//所以这个就是表示 最少多少帧就有一个I帧 这个主要根据码流大小和 网络延迟能多快恢复
	(*enc_ctx)->keyint_min = 25;		//option

	//设置B帧数量
	(*enc_ctx)->max_b_frames = 3;		//一般B帧不超过3帧  option 不设置 编码器也会根据码流大小自己顶一个
	(*enc_ctx)->has_b_frames = 1;		//是否包含B帧的信息 1是包含    option


	//设置参考帧的数量  让解码器知道 解码器数组要存多少帧  这个值越大 处理的就越慢,还原性就越好,如果越小,处理就越快,还原就差很多,
	(*enc_ctx)->refs = 3;				//option
	//设置输入YUV格式 libx264要求的输入格式
	(*enc_ctx)->pix_fmt = AV_PIX_FMT_YUV420P;



	//设置码率  
	(*enc_ctx)->bit_rate = 600000;		//600kbps   参考了9-3  640x480x3x25x0.5 x8
	//(*enc_ctx)->bit_rate = 1000000;		//1000kbps  11-11 编码太模糊  加大了码率,不过其实模糊是因为没有加pts


	//设置帧率
	(*enc_ctx)->time_base = AVRational{1, 25};	//帧与帧之间的间隔 就是time_base 
	(*enc_ctx)->framerate= AVRational{ 25, 1 };	//帧率  time_base与帧率是一个倒数关系

	//打开视频编码器  
	//参数1 编码器上下文
	//参数2 编码器
	//参数3 设置参数 为NULL
	int ret=   avcodec_open2(*enc_ctx, codec, NULL);
	if (ret<0)
	{

		printf("Could  not open codec: %s! \n",av_err2str(ret));
		//编码器上下文没有找到可以直接退出了
		exit(1);
	}
}




/// <summary>
/// 视频编码
/// </summary>
/// <param name="enc_ctx">视频编码器上下文</param>
/// <param name="frame">视频数据转成yuv420p格式的数据实体类</param>
/// <param name="newpkt">接收编码后视频数据实体类</param>
/// <param name="outfile">输出的视频文件 </param>
static void encode_video(AVCodecContext* enc_ctx, AVFrame* frame, AVPacket* newpkt, FILE*  outfile ) 
{
	int  ret = 0;
//外部函数需要判断 是否为空 
	//if (!enc_ctx)
	//{
	//}
	if (frame)
	printf("send frame to encoder,pts=%lld", frame->pts);

	//将yuv420p格式的视频数据 交给编码器 
	 ret= avcodec_send_frame(enc_ctx, frame);
	 if (ret<0) 
	 {//传输的数据 和编码有问题
		 printf("Error,Failed to  send a frame for encoding!\n");
	     exit(1);  //整个数据都乱了 程序可以退出
	 }

	 //从编码器获取编码好的视频数据,开始编码 
	 while (ret >= 0)	
	 {//获取到数据了 开始编码
		 //参数1 上下文   参数2 接收编码后视频数据实体类  
		ret= avcodec_receive_packet(enc_ctx, newpkt);
		if (ret==AVERROR(EAGAIN)||ret== AVERROR_EOF)
		{//要么数据不足->EAGAIN   或者到了数据尾时会返回 ->AVERROR_EOF   让其重新传数据过来
			return;
		}else if (ret < 0)
		{//编码器出错了 没法编码
			printf("Error,Failed to encode!\n");
		}

		//编码好的数据 每次一个字节  写入newpkt->size大小 的数据到outfile
		fwrite(newpkt->data, 1, newpkt->size, outfile);
		//引用计数减少一个 因为每次调用都会增加newpkt的引用计数 我们这里减少一次 让其使用处于对等状态
		//或者说就是释放掉这个实体使用的内存
		av_packet_unref(newpkt);




	 }
}



/// <summary>
/// 实时yuyv(yuv422) 转换成yuv420p  这个函数暂时只能处理 分辨率不变的转换
/// 参考连接https://blog.csdn.net/chillcirno/article/details/100597972?ops_request_misc=&request_id=&biz_id=102&utm_term=%E6%91%84%E5%83%8F%E5%A4%B4YUYV%E8%BD%ACYUV420&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-100597972.142^v13^pc_search_result_control_group,157^v14^new_3&spm=1018.2226.3001.4187
/// </summary>
/// <param name="in">摄像头采集到的yuyv数据</param>
/// <param name="out">输出的yuv420的数据</param>
/// <param name="width">转换前宽度</param>
/// <param name="height">转换前的高度</param>
void yuyv_to_yuv420P(uint8_t* in, uint8_t* out, int width, int height)
{
	uint8_t* y, * u, * v;
	int i, j, offset = 0, yoffset = 0;

	y = out;                                    //yuv420的y放在前面  
	u = out + (width * height);                 //yuv420的u放在y后  
	v = out + (width * height * 5 / 4);         //yuv420的v放在u后  
	   //总共size = width * height * 3 / 2  

	for (j = 0; j < height; j++)
	{
		yoffset = 2 * width * j;
		for (i = 0; i < width * 2; i = i + 4)
		{
			offset = yoffset + i;
			*(y++) = *(in + offset);
			*(y++) = *(in + offset + 2);
			if (j % 2 == 1)                      //抛弃奇数行的UV分量  
			{
				*(u++) = *(in + offset + 1);
				*(v++) = *(in + offset + 3);
			}
		}
	}
}







/// <summary>
/// 录制视频
/// </summary>
void  rec_video11_10() {
	char* errors = new char[1024];
	//ctx
	//AVDictionary* options = NULL;		//设备参数实体
	AVFormatContext* fmt_ctx = NULL;		//媒体或者设备上下文
	//packet
	AVPacket pkt;			//存放读取数据
	av_log_set_level(AV_LOG_DEBUG); //设置日志级别
	//av_register_all();
	avdevice_register_all(); 			//注册所有的设备
	int	 rec_status = 1;
	int ret = 0;
	int base = 0;						//
	AVCodecContext* enc_ctx = NULL;		//视频编码器的上下文
	AVFrame* Frame = NULL;				//接收视频原始数据的实体类
	AVPacket* netpkt = NULL;			//接收编码后视频数据实体类





	//成功打开音频设备 后开始读数据
	//1创建文件  参数 文件名和 模式 w表示写入 b表示写入二进制 +表示 不存在就创建
	//编码后的数据 叫aac  pcm是原始数据
	char* outsource = (char*)"C:\\Users\\admin\\source\\repos\\ffmpegtest01\\video.h264";
	char* yuvoutsource = (char*)"C:\\Users\\admin\\source\\repos\\ffmpegtest01\\yuvvideo.yuv";

	FILE* outfile = fopen(outsource, "wb+");
	FILE* yuvfile = fopen(yuvoutsource, "wb+");
	if (!outfile)
	{
		printf("Error,Failed to open outfile \n");
		goto __ERROR;
	}
	

	if (!yuvfile)
	{
		printf("Error,Failed to open yuvfile \n");
		goto __ERROR;
	}


	//2打开设备
	fmt_ctx = open_dev();
	if (!fmt_ctx)
	{
		printf("Error,Failed to open Device \n");
		goto __ERROR;
	}
	//打开编码器
	open_encoder(V_WIDTH, V_HEIGHT, &enc_ctx);

	//创建 存储转码后的视频的AVFrame
	Frame = create_frame_video(V_WIDTH, V_HEIGHT);

	//分配视频编码后缓存空间 packet
	netpkt = av_packet_alloc();
	if (!netpkt)
	{
		printf("Error,Failed to alloc avpakcet \n");
		goto __ERROR;
	}
	//ffmpeg 修改编码



	//从设备获取 视频数据 把yvyv 也就是yuv422转成YUV420P
	while ((ret = av_read_frame(fmt_ctx, &pkt)) == 0 && rec_status < 50)
	{
		rec_status++;
		av_log(NULL, AV_LOG_INFO, "Packet size is %d(%p)\n", pkt.size, pkt.data);
		//每次都分配
		uint8_t* out = new uint8_t[V_WIDTH * V_HEIGHT * 3 / 2];
		memset(out, 0, V_WIDTH * V_HEIGHT * 3 / 2);
		yuyv_to_yuv420P(pkt.data, out, V_WIDTH, V_HEIGHT);
		//因为yuyv_to_yuv420P函数里面已经把yuv排列好了 所以直接把当前一帧的排列好的yuv写入yuvfile即可
		fwrite(out, 1, 460800, yuvfile);
		//把yuv420p数据从char* 写入到 Frame里面 后期在修改yuyv_to_yuv420P函数 在里面进行处理然返回出来的是Frame
		//y数据
		memcpy(Frame->data[0], out, 307200);
		//u数据
		memcpy(Frame->data[1], out, 307200/4);
		//v数据
		memcpy(Frame->data[2], out, 307200/4);

		//11-11 当前帧的值或者说时间戳  进行编码的时候需要因为后一帧需要参考前一帧 不然画面就会部分模糊不清晰
		Frame->pts==base++;
		//将视频数据进行编码
		encode_video(enc_ctx, Frame, netpkt, outfile);

		av_packet_unref(&pkt);	//使用完包(这个包是AVPacket) 必须释放掉 不然会内存泄漏
	}


	////从设备获取 视频数据 把NV12转成YUV420P
	//while ((ret = av_read_frame(fmt_ctx, &pkt)) == 0 && rec_status < 50)
	//{
	//	rec_status++;
	//	av_log(NULL, AV_LOG_INFO, "Packet size is %d(%p)\n", pkt.size, pkt.data);

	//
	//
	//	//YYYYYYYYUVUV NV12的数据  Mac采集设备的格式 教程讲的是mac的nv12转420的
	//	//YYYYYYYYUUVV YUV420的数据 
	//	//把Y数据拷贝到 data[0]  拷贝值 是640x420=307200  就是这个分辨率下 y数据有多少
	//	memcpy(Frame->data[0], pkt.data, 307200);
	//	////UV  307200/4 是u和v的分辨率  uv数据加起来是y的四分之一(4个y对应一个u和v 参考9-7文档) 
	//	////下面就是简单的存UV数据的逻辑,实际有更好的算法 比如libyuv的接口性能更高,不过学习肯定怎么通俗易懂怎么来。
	//	for (size_t i = 0; i < 307200/4; i++)
	//	{
	//		//Frame->data[1] 里面存的就是u数据 data[2]存放的就是v数据
	//		//把data[1]里面的数据也就是u数据拷贝出来  从y数据局之后的第一个数据开始也就是i开始
	//		//因为采集的是nv12  uv是循环存的  
	//		Frame->data[1][i] = pkt.data[307200 + i*2];//0 2 4
	//		Frame->data[2][i] = pkt.data[307201 + i*2]; //1 3  5
	//	}

	//	//YUYVYUYV  Y->0 2 4 6   UV->1 3 5 7 9 11 13 15  U->1 5 9 13
	//	//设备读出来的y数据  写到了 yuvfile
	//	fwrite(Frame->data[0], 1, 307200, yuvfile);
	//	//u数据
	//	fwrite(Frame->data[1], 1, 307200/4, yuvfile);
	//	//v数据
	//	fwrite(Frame->data[2], 1, 307200/4, yuvfile);



	//11-11 当前帧的值或者说时间戳  进行编码的时候需要因为后一帧需要参考前一帧 不然画面就会部分模糊不清晰
	Frame->pts == base++;
	//	//将视频数据进行编码
	//	encode_video(enc_ctx, Frame, netpkt, outfile);
	//
	//	av_packet_unref(&pkt);	//使用完包(这个包是AVPacket) 必须释放掉 不然会内存泄漏
	//}//~While((ret = av_read_frame(fmt_ctx, &pkt)) == 0 && rec_status < 50)

	//视频数据来源没了 编码器会将自己缓冲区剩余的所有数据都输出出来,防止丢帧
	//没有这一步 就可能出现丢帧的情况。
	encode_video(enc_ctx, NULL, netpkt, outfile);
__ERROR:
	//反初始化多媒体上下文 不使用了 就释放掉 
	if (fmt_ctx) {
		avformat_close_input(&fmt_ctx);
	}
	// 关闭文件
	if (outfile) {
		fclose(outfile);
	}
	av_log(NULL, AV_LOG_DEBUG, "finish\n");
	return;
}
入代码
写回答

1回答

李超

2022-06-13

转格式之后是对的吗?从YUV422转成YUV4:2:0

0
2
李超
回复
darklgd
你转之前是yuv4:2:2?你有没有用这个参数播放过?你最好到课程的QQ群里把你的步骤详细说一下,我现在严重怀疑你中间执行的步骤没搞清楚
2022-06-15
共2条回复

音视频小白系统入门课 音视频基础+ffmpeg原理

掌握音视频采集、编解码、RTMP传输协议等核心基础

2318 学习 · 813 问题

查看课程