虽然 ffmpeg的缩放效率比较低,但随着电脑或者硬件设备发展,即使使用低效率的,也可以达到普通播放器的要求了。但在使用的过程中的坑还是很多的。
在android手机中使用SwsContext 和 sws_scale缩放后,视频总视出现乱码或者 干脆一个黑屏。
void FFMVideoShow::show(AVFrame *avFrame)
{
if(dataManager->swsContext == nullptr){
dataManager -> outWidth = avFrame->width;
dataManager -> outHeight = avFrame->height;
dataManager->rgb = new char[dataManager -> outWidth * dataManager -> outHeight * 4];
ANativeWindow_setBuffersGeometry(dataManager->aNativeWindow, dataManager->outWidth,
dataManager->outHeight, WINDOW_FORMAT_RGBA_8888);
// 这个开销也比较大,且经常失败,,这里不为空的时候,再去获取,发现视频解码速度快了很多。
dataManager->swsContext = sws_getCachedContext(dataManager->swsContext,
avFrame->width,
avFrame->height,
(AVPixelFormat)avFrame->format,
dataManager->outWidth,
dataManager->outHeight,
AV_PIX_FMT_RGBA,
SWS_FAST_BILINEAR,
0,0,0 );
}
if(!dataManager->swsContext){
LOGE("swsContext failed %d!", dataManager->swsContext);
}
else {
LOGE("show show 3" );
// LOGE("swsContext success %d!", dataManager->swsContext);
// 指针数组AV_NUM_DATA_POINTERS=8 不一定全用
uint8_t *data[AV_NUM_DATA_POINTERS]={0};
if(dataManager->rgb != 0)
data[0]= (uint8_t *)dataManager->rgb;
int lines[AV_NUM_DATA_POINTERS]={0};
lines[0]=dataManager->outWidth*4;
int h = sws_scale(dataManager->swsContext,
(const uint8_t **)avFrame->data,
avFrame->linesize,0,
avFrame->height,
data,
lines
);
LOGE("show show 4 height =%d , width = %d" , dataManager->outHeight, dataManager->outWidth);
LOGE("show show 4 h =%d" , h );
av_frame_free(&avFrame);
if(h >0)
{
LOGE("show show 5" );
ANativeWindow_lock(dataManager->aNativeWindow, &(dataManager->wbuf), 0);
uint8_t *dst = (uint8_t*)dataManager->wbuf.bits;
memcpy(dst, dataManager->rgb,dataManager->outHeight*dataManager->outWidth*4);
ANativeWindow_unlockAndPost(dataManager->aNativeWindow);
}
}
}
问题:设置了natvieWindow的缓存区大小。而且 outwidth是 和 解码出来的frame的大小设置是一样的,像素格式rgba。但就是这么不缩放,也出现了问题,我的视频是抖音下随便下载的一个视频。宽高是368x640, 然而播放出来确实乱码。换了个标准的1920x1080的然而成了黑屏,sws_scale后h返回值为0。
在网上查资料,没找到类似的问题,作为一名从事图像的程序员,猜到可能是图像格式,特别是宽度在不够的时候,系统或者一些算法自己在后面补充一些空的像素,让图像宽度成为32、64的倍数。这样导致后面scale后,算法自己补充了一些像素,而native窗体已经设置了大小,从而不匹配,导致copy到buf的时候,一些像素丢失或者错位了。
于是,就做了以下实验:将窗体宽度从64开始,每次加2,发现,为64的倍数的时候都不会出现乱码。高度可以 随便scale都不会出现视频混乱清空。
从而得出一个结论:为了播放器的健壮性,窗体大小最好设置为64的倍数,为了保证视频的清晰度,可以尽可能靠近frame的宽度。
那么剩下一个问题,1920 是为64的倍数,为何我的视频scale 后 h为0,于是打印了log,查看了手机屏幕 和 surfaceview的大小,大小为:2012x1080 而我们的窗体大小为1920x1080
于是做了下面实验:
1、将高1080 修改为1079,就 是减去1个像素,视频就正常播放了。
2、将宽1920 - 64 后,1080不变也可以正常播放。
解决方案:
1、可以将宽度设置为linesize[0],这样就不会乱码了,但存在意外,视频1920*1080的时候太大,也无法显示。
2、缩放是根据宽度,android平台最好是64倍数,高度也是有相应的限制。为了播放器的 兼容性,最好使用opengl来渲染、或者利用 高效率libyuv 库来进行格式转换显示,libyuv高效转化git地址https://github.com/hurtnotbad/FFmpegDemo。
具体的 解决方法请参考博客:FFMpeg opengl显示解码avframe