FFmpeg解码出来avframe,但显示很容易出现乱码,因为为了计算的方便,ffmpeg解码出来的数据,总视根据系统处理器或者系统来补齐为 64、32、16的倍数。这也是我们显示乱码的问题所在。
问题
比如我显示抖音上的视频的宽为368x640, 解码的帧率avFrame,下面是avframe的一些成员变量:
avFrame->format = 0 // 表示是 yuv420p的数据格式
avFrame->width = 364
avFrame->height= 640
然后取数据的时候,
y= avFrame->data[0]
u= avFrame->data[1]
v= avFrame->data[2]
将yuv给opengl渲染就会乱码。
解决方法
将宽度设置为linesize
直接获得data[0]的数据,在创建显示texture的时候以,avFrame-linesize[0]作为一帧的宽度:
// 直接按照填补的 宽度来显示,效率最高,若视频宽度不是64/32(根据系统位数)倍数,右边会存在边框。
int w = avFrame->linesize[0];
int h = avFrame->height;
unsigned char * y = avFrame->data[0];
unsigned char * u = avFrame->data[1] ;
unsigned char * v = avFrame->data[2];
glProgram->Draw(w,h,y,u ,v);
LEGL::Get()->Draw();
av_frame_free(&avFrame);
这样就可以正常显示视频了,取数据也是正常的,但会显示补齐的那部分数据,
缺点:视频右边会有边框。
拷贝有效数据
unsigned char * y = nullptr;
unsigned char * u = nullptr;
unsigned char * v = nullptr;
void getYUV420pData(AVFrame *avFrame)
{
int w = avFrame->width;
int h = avFrame->height;
if(y == nullptr){
y = new unsigned char[w*h];
}
if(u == nullptr){
u = new unsigned char[w*h/4];
}
if(v == nullptr){
v = new unsigned char[w*h/4];
}
int l1 = avFrame->linesize[0];
int l2 = avFrame->linesize[1];
int l3 = avFrame->linesize[2];
for(int i= 0 ; i < h ; i++)
{
memcpy(y + w*i,avFrame->data[0] + l1* i, sizeof( unsigned char)*w);
}
for(int i= 0 ; i < h/2 ; i++)
{
memcpy(u + w/2*i,avFrame->data[1] + l2 * i, sizeof( unsigned char)*w/2);
memcpy(v + w/2*i,avFrame->data[2] + l3 * i, sizeof( unsigned char)*w/2);
}
}
拷贝出有效数据,然后再利用opengl显示图片就可以了。
2022年1月6日新增
如果用代码切割每一帧太慢,可以考虑用opengl来进行裁剪,渲染方式还是如上面一样,但后续的渲染可以修改映射点:
//加入材质坐标数据
const float txts[8] = {
1.0f*0.95,0.0f , //右下
0.0f,0.0f,
1.0f*0.95,1.0f,
0.0,1.0
};
这样,在渲染的时候就自动裁掉了后面的边框。