1、FFmpeg像素格式转换
FFmpeg像素转换一般使用libswscale来进行
接口说明
1、 获取上下文SwsContext
一般我们使用下面两个函数来获取,sws_getCachedContext和sws_getContext略有不同的是,如果输入和输出的宽/高/格式不变,则会返回之前创建的context。
参数说明:
前三个参数分变为原始宽、高、格式(如RGBA8888,YUV420等);后三个参数为转换后的宽、高、格式;参数flags可以指定转换时使用的算法;最后三个参数指定为NULL即可。
struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
struct SwsContext *sws_getCachedContext(struct SwsContext *context,
int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param);
2、 转换函数sws_scale
参数说明:
参数struct SwsContext *c:上下文。
参数const uint8_t *const srcSlice[]:输入数据,为一个指针数据,指向每个通道的数据。一般可以从解码出来的frame->data作为该参数。
参数const int srcStride[]:每个通道行字节数。一般可以将解码出来的frame->linesize作为该参数。
stride定义下一行的起始位置。stride和width不一定相同,这是因为:
1)由于数据帧存储的对齐,有可能会向每行后面增加一些填充字节这样 stride = width + N;
2)packet色彩空间下,每个像素几个通道数据混合在一起,例如RGB24,每个像素3字节连续存放,因此下一行的位置需要跳过3*width字节。
srcSlice和srcStride的维数相同,由srcFormat值来。
csp 维数 宽width 跨度stride 高 YUV420 3 w,
w/2, w/2 s, s/2, s/2 h, h/2, h/2 YUYV 1 w, w/2, w/2
2s, 0, 0 h, h, h NV12 2 w, w/2, w/2 s, s, 0
h, h/2 RGB24 1 w, w, w 3s, 0, 0 h, 0, 0
参数int srcSliceY, int srcSliceH:定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。
这种设置是为了多线程并行,例如可以创建两个线程,第一个线程处理 [0, h/2-1]行,第二个线程处理 [h/2, h-1]行。并行处理加快速度。
参数uint8_t *const dst[], const int dstStride[]定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)
这两个参数可以使用av_image_alloc进行获取,也可以根据输出的像素格式自行定义。
int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],
const int srcStride[], int srcSliceY, int srcSliceH,
uint8_t *const dst[], const int dstStride[]);
3、释放函数
void sws_freeContext(struct SwsContext *swsContext);
示例
int FFDecoder::videoConvert(AVFrame* pFrame, uint8_t* out){
if(pFrame == NULL || out == NULL)
return 0;
//AVPicture pict;
//av_image_alloc(pict.data, pict.linesize, pVCodecCxt->width,pVCodecCxt->height, AV_PIX_FMT_RGBA, 16);
uint8_t* dst_data[4] = {0};
char *swsData = new char[720*576*4];
dst_data[0] =(uint8_t *)swsData;
int dst_linesize[4];
dst_linesize[0] = 720*4;
swsContext = sws_getCachedContext(swsContext,pFrame->width, pFrame->height,(AVPixelFormat)pFrame->format,
720, 576, AV_PIX_FMT_RGBA, SWS_FAST_BILINEAR, 0, 0, 0);
if(swsContext){
sws_scale(swsContext, (const uint8_t**)pFrame->data, pFrame->linesize, 0, pFrame->height, (uint8_t* const*)dst_data, dst_linesize);
size_t size = dst_linesize[0] * 576;
memcpy(out,dst_data[0],size);
//av_free(&pict.data[0]);
return size;
}
//av_free(&pict.data[0]);
return 0;
}
2、使用SurfaceView显示
转换后的视频数据使用SurfaceView显示,可以在JAVA层处理,也可以在C++层,我们使用的是C++层,实际上使用的是Android的ANativeWindow来进行处理。
所以我们需要在JAVA层创建SurfaceView,获取surface后传入到JNI层,在JNI层通过surface获取ANativeWindow来显示。
JAVA层创建SurfaceView,并传入surface:
为确保surface已经创建,在surfaceChanged或者surfaceCreated的回调函数中,才将surface设置下去。
SurfaceView的创建不用多说,在布局文件添加相关控件即可。
如下:
private void initView() {
mSurfaceView = (SurfaceView)findViewById(R.id.surfaceView);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(new Callback() {
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
if(mFFdec != null){
mFFdec.initSurface(mSurfaceHolder.getSurface());
}
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {}
});
}
mFFdec.initSurface(mSurfaceHolder.getSurface());是native接口,将surface设置到JNI层,具体实现如下:
public native void initSurface(Surface surface);
......仅列出关键代码
static ANativeWindow* g_nwin = NULL;
static void jni_initSurface(JNIEnv* env, jobject obj, jobject surface){
if(surface){
g_nwin = ANativeWindow_fromSurface(env, surface);
if(g_nwin){
ANativeWindow_setBuffersGeometry(g_nwin, 720, 576, WINDOW_FORMAT_RGBA_8888);
LOGE("jni_initSurface g_nwin[%p],g_nwin->perform[%p]\n",g_nwin,g_nwin->perform);
}
}
return;
}
static JNINativeMethod gMethods[] = {
{"decodeInit", "()V", (void*)jni_decodeInit},
{"decodeDeInit", "()V", (void*)jni_decodeDeInit},
{"decodeFrame", "([B)I", (void*)jni_decodeFrame},
{"openInput", "(Ljava/lang/String;)I", (void*)jni_openInput},
{"getMediaSampleRate", "()I", (void*)jni_getMediaSampleRate},
{"getMediaType", "()I", (void*)jni_getMediaType},
{"initSurface", "(Landroid/view/Surface;)V", (void*)jni_initSurface},
};
...省略
ANativeWindow使用流程
1、初始化:
调用ANativeWindow_fromSurface从JAVA层传入的surface获取ANativeWindow;
调用ANativeWindow_setBuffersGeometry设置ANativeWindow相关参数,包括宽/高/像素格式(注意要和我们传入的数据匹配)。
2、锁定:
ANativeWindow是双缓冲机制,先调用ANativeWindow_lock锁定后台的缓冲部分,并获取surface缓冲区的地址。
3、绘制:
往缓冲区填充数据后,调用ANativeWindow_unlockAndPost,即会解锁缓冲区并绘制。
示例
static int renderVframe(uint8_t* rgb,int size){
if(g_nwin == NULL)
return -1;
LOGE("renderVframe g_nwin[%p],g_nwin->perform[%p]\n",g_nwin,g_nwin->perform);
ANativeWindow_Buffer outBuffer;
ANativeWindow_lock(g_nwin, &outBuffer,0);//获取surface缓冲区的地址
uint8_t* dst = (uint8_t*)outBuffer.bits;
memcpy(dst,rgb,size);//往surface缓冲区填充显示的RGB内容
ANativeWindow_unlockAndPost(g_nwin);
return 0;
}