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
022022-06-15
相似问题