经过几天的琢磨、写demo验证,终于解决问题了:
- 保存修改前和修改后的数据,使用雷神YUVPlayer逐帧看,发现新进来的frame->data是上一帧拆分后的数据,具体原因可能是每帧视频帧并不是完整的数据帧(关键帧-key_frame),而是每帧只保存运动(不同部分)的数据,这是视频编码压缩的一种方式;
- 通过这一现象作为突破口,发现每帧frame->data[0]的内存地址都是一样的,也就是说每帧的data部分都是保存在同一内存,即拆分了前一帧数据,下一帧过来是拆分后的数据,对拆分后的一帧数据再次拆分,必然造成上图的错乱的现象;
- 解决办法是,不修改原始frame->data的数据,重新构造一个AVFrame,再使用新构造的AVFrame显示,具体代码如下:
AVFrame* VideoForm::CropYUVFrame(AVFrame* srcFrame)
{
int width = srcFrame->width;//768
int height = srcFrame->height; //576
int lsz = srcFrame->linesize[0];//一行数据大小
int srcOffset = lsz >> 1;
int cpySize = lsz - srcOffset - 1;
int dstOffset = lsz * 3 / 10;
int YSize = width * height;
int UVSize = YSize >> 2;
// 构造dstframe ,注意由于不同数据帧的data都是共用一块内存的,所以不能直接修改原始帧的数据;
// 如果直接修改原始帧数据,下一帧数据过来的并不是原始帧数据,是上一次修改后的数据基础上再修改,将造成画面错乱,所以需要重新构造目标帧
AVFrame* dstFrame = av_frame_alloc();
dstFrame->width = width;
dstFrame->height = height;
dstFrame->linesize[0] = srcFrame->linesize[0];
dstFrame->linesize[1] = srcFrame->linesize[1];
dstFrame->linesize[2] = srcFrame->linesize[2];
dstFrame->format = srcFrame->format;
//设置好宽高,linesize后再填充data
av_frame_get_buffer(dstFrame, 1);//data内存申请
memcpy(dstFrame->data[0], srcFrame->data[0], YSize);
memcpy(dstFrame->data[1], srcFrame->data[1], UVSize);
memcpy(dstFrame->data[2], srcFrame->data[2], UVSize);
int dstWidth = srcOffset;
int srcWidth = lsz - dstOffset;
//缩放采用最近邻插值算法实现
double scale = srcWidth * 1.0 / dstWidth;
unsigned long* pdwSrcXTable = (unsigned long*)malloc(sizeof(unsigned long) * dstWidth);
for (int x = 0; x < dstWidth; x++)//生成表 pdwSrcXTable
{
pdwSrcXTable[x] = qRound(x * scale);//数据对齐(先除以pixelBytes取整再乘以pixelBytes)
}
//处理Y分量
for (int row = 0; row < (height >> 1); ++row)
{
//memset(dstFrame->data[0] + row * lsz + srcOffset, 0, lsz - srcOffset);//中间分割线
//memcpy(dstFrame->data[0] + row * lsz + srcOffset + 1, srcFrame->data[0] + row * lsz + dstOffset, cpySize);
for (int col = 0; col < lsz; ++col)
{
if (col == srcOffset)
{
dstFrame->data[0][row * lsz + col] = 0;
}
else if (col < srcOffset)
{
dstFrame->data[0][row * lsz + col] = srcFrame->data[0][row * lsz + pdwSrcXTable[col]];
}
else
{
dstFrame->data[0][row * lsz + col] = srcFrame->data[0][row * lsz + dstOffset + pdwSrcXTable[col - srcOffset]];
}
}
}
static int c = 0;
std::string strSrc = "./frame_src_" + std::to_string(width) + "x" + std::to_string(height) + ".yuv";
std::string strDst = "./frame_dst_" + std::to_string(width) + "x" + std::to_string(height) + ".yuv";
static FILE* fp1 = fopen(strSrc.c_str(), "wb+");
static FILE* fp2 = fopen(strDst.c_str(), "wb+");
if (c <= 99)
{
fwrite(srcFrame->data[0], 1, YSize, fp1);
fwrite(dstFrame->data[0], 1, YSize, fp2);
c++;
}
else
{
fclose(fp1);
fclose(fp2);
}
//处理UV分量
lsz = srcFrame->linesize[1];
for (int row = 0; row < (height >> 2); ++row)
{
//memcpy(dstFrame->data[1] + row * srcOffset + (srcOffset >> 1)
// , srcFrame->data[1] + row * srcOffset + (dstOffset >> 1), (srcOffset >> 1));
for (int col = 0; col < lsz; ++col)
{
if (col < (lsz >> 1))
{
dstFrame->data[1][row * lsz + col] = srcFrame->data[1][row * lsz + pdwSrcXTable[col]];
dstFrame->data[2][row * lsz + col] = srcFrame->data[2][row * lsz + pdwSrcXTable[col]];
}
else
{
dstFrame->data[1][row * lsz + col] = srcFrame->data[1][row * lsz + (dstOffset >> 1) + pdwSrcXTable[col - (lsz >> 1)]];
dstFrame->data[2][row * lsz + col] = srcFrame->data[2][row * lsz + (dstOffset >> 1) + pdwSrcXTable[col - (lsz >> 1)]];
}
}
}
free(pdwSrcXTable);
return dstFrame;
}