android下播放器视频输出方法总结

转自: http://m.myexception.cn/android/1790584.html

题记:

   bitmap,native_windows,opengles api, android_app,native player

      在Android下输出视频画面,有很多种方法,每个都有自己的特点,比如将视频数据送回到java层然后用lockCanvas画出来这种方法的特点就是慢.

    上面这个完全不值得提倡,视频数据从native层传到jni层很耗时间.

    开发基于ffmpeg的播放器时,可以使用ffmpeg的各种软解码器,也可以使用android带的OMXCodec解码器,OMXCodec解码器是对OMX的一个封装.其中ffmpeg解码器主要输出YUV420P的帧,而OMXCodec解码器输出的格式就多样化,因具体的平台不同而不同.

    使用OMXCodec解码器解出来的视频,可以让它自己输出,只要在打开解码器的时候给它传个ANativeWindow:

<span style="font-size:14px;"> OMXCodec::Create(
        const sp<IOMX> &omx,
        const sp<MetaData> &meta, bool createEncoder,
        const sp<MediaSource> &source,
        const char *matchComponentName,
        uint32_t flags,
        const sp<ANativeWindow> &nativeWindow</span>
<span style="font-size:14px;">   )</span>
<span style="font-size:14px;">
</span>
    解码函数read出来的是MediaBuffer,其实是GraphicBuffer,不需要关心它的格式,只需要queueBuffer它,然后设置好render完成,并release它:


<span style="font-size:14px;">g_ANativeWindow->queueBuffer (g_ANativeWindow.get(),mbuf->graphicBuffer()->getNativeBuffer());
...
mbuf->meta_data()->setInt32(kKeyRendered,1);
...
mbuf->release();//这个是告诉OMXCodec</span></span>

    值得说明的是,在创建OMXCodec的时候,也可以传入kKeyColorFormat来指定成想要的格式,但是不幸的是,有时候并不会支持你想要的,比如有的手机就不支持HAL_PIXEL_FORMAT_YV12,而是使用了一种内部格式,纠结吧,所以还是让它自己输出比较好.

    在采用ffmpeg解码器时,目前我所知道的,输出YUV420P有两种方法,一种是通过Lock ANativeWindow写入YUV数据,一种是通过opengles在片断着色器中将YUV数据通过公式转换成RGB输出.

    参考AwesomePlayer.cpp,会发现有个SoftwareRenderer的东东,这个类可以输出HAL_PIXEL_FORMAT_YV12, YUV420P->YV12的转换其实很简单,就是UV分量的存储位置不一样,拷贝时变换下就可以了.这个是Lock ANativeWindow的方法.里面还有一个AwesomeNativeWindowRenderer,其实就是上面提到的让OMXCodec自己去输出视频的方法.

    我在实践中发现,有些手机的SoftwareRenderer没有处理好,特别是刷第三方ROM的时候,会花屏,所以需要自己手动从SoftwareRenderer.cpp中提取代码,其实就是操作ANativeWindow的一些函数:


<span style="font-size:14px;">native_window_api_connect(gDirectRendererContext.anativeWindow.get(),NATIVE_WINDOW_API_CPU);
native_window_set_usage(gDirectRendererContext.anativeWindow.get(),GraphicBuffer::USAGE_SW_WRITE_OFTEN|GraphicBuffer::USAGE_HW_TEXTURE);
native_window_set_buffers_geometry(gDirectRendererContext.anativeWindow.get(),w,h,android_fmt);
native_window_set_crop(gDirectRendererContext.anativeWindow.get(),&android_crop);
native_window_set_scaling_mode(gDirectRendererContext.anativeWindow.get(),NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
ANativeWindow_lock(gDirectRendererContext.anativeWindow.get(), &buffer, NULL);
//写数据
ANativeWindow_unlockAndPost(gDirectRendererContext.anativeWindow.get());</span>

    

   上面的代码省略了很多,只是一个基本的流程.android的源码中还有一个gl2_yuvtex.cpp,里面有输出YUV的方法,是采用了初始化egl和opengles,然后创建GraphicBuffer写入数据,跟据GraphicBuffer创建EGLImageKHR,最后调用opengles的扩展    GL_OES_EGL_image_external 输出YUV,这种方法我也把它归属于LockAnativeWindow方法,因为这个方法就是上面的底层实现.

    Lock AnativeWindow方法有一个很大的缺点,所以我在最终的播放器代码中没有采用.在lockBuffer之后,我们可以拿到y分量的行字节数,即stride,但是没有办法获取到uv分量的stride,跟据一般情况下应该是y_stride/2然后按16字节对齐,但是有些厂商不按常理出牌,这个值不一定是这样的,导致结果就是花屏或者内存Abort,所以得适配手机,麻烦!

    再来说说使用opengles将YUV转换成RGB输出的方法,这种方法很早有就人使用了,最早的时候就使用软件将YUV转换成RGB输出,但是效率太低了,于是大神们将眼光投向了opengles的可编程片段着色器,将YUV的转换放到着色器去做,硬件处理是非常快的,这样就大大提高了效率,具体的做法是:初始化EGL(参考gl2_yuvtex.cpp)->创建着色器程序并编译连接->创建并上传纹理->画三角形.下面是顶点和纹理坐标,着色器代码:


<span style="font-size:14px;">static float squareV[]={
	-1.0f,-1.0f,
	1.0f,-1.0f,
	1.0f,1.0f,
	-1.0f,1.0f
};
static float coordV[]={
	0.0f,1.0f,
	1.0f,1.0f,
	1.0f,0.0f,
	0.0f,0.0f
};
static char vertexShaderCode[]=
			"attribute vec4 squareV;"
			"attribute vec2 coordV;"
			"varying vec2 tc;"
			"void main(){"
			"	gl_Position=squareV;"
			"	tc=coordV;"
			"}";

static char fragmentShaderCode_yuv420[]=
			"precision mediump float;"
			"uniform sampler2D tex_y;"
			"uniform sampler2D tex_u;"
			"uniform sampler2D tex_v;"
			"varying vec2 tc;"
			"void main(){"
			" mediump vec3 yuv;"
			" mediump vec3 rgb;"
			" yuv.x=texture2D(tex_y,tc).r;"
			" yuv.y=texture2D(tex_u,tc).r-0.5;"
			" yuv.z=texture2D(tex_v,tc).r-0.5;"
			" rgb=mat3("
			"          1,1,1,"
			"          0,-0.39456,2.03211,"
			"          1.13983,-0.58060,0)*yuv;"
			" gl_FragColor=vec4(rgb,1);"
			"}";</span></span>

    

    要注意的是,在NativeWindow改变大小里,需要重新创建EGLSurface和EGLContex,另外EGLContex和线程相关,是opengles的状态机.

    都是根据前人经验总结,如有不对,欢迎指正!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值