android:
id="@+id/live_sv_live"
android:
layout_width="match_parent"
android:
layout_height="match_parent"/>
因为不是拍照,所以Camera的处理就会显得比较的简单了,
packagecom.xiaoxiao.live;
importandroid.graphics.ImageFormat;
importandroid.hardware.Camera;
importandroid.os.Bundle;
importandroid.support.v7.app.AppCompatActivity;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.SurfaceHolder;
importandroid.view.SurfaceView;
/**
*CreatedbyAdministratoron2017/2/20.
*/
publicclassLiveActivityextendsAppCompatActivityimplementsSurfaceHolder.Callback,Camera.PreviewCallback{
privateCameramCamera;
privateSurfaceViewmSurfaceView;
privateSurfaceHoldermSurfaceHolder;
privateintmCameraId=0;
privateintwidth=720;
privateintheight=480;
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live);
mSurfaceView=(SurfaceView)findViewById(R.id.live_sv_live);
mSurfaceHolder=mSurfaceView.getHolder();
mSurfaceHolder.setFixedSize(width,height);
mSurfaceHolder.addCallback(this);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_live,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
if(item.getItemId()==R.id.checkable_menu){
booleanisChecked=item.isChecked();
Log.e("LiveActivity","checked:
"+isChecked);
item.setChecked(!
isChecked);
mCameraId=1-mCameraId;
destroyCamera();
initCamera();
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
@Override
publicvoidonPreviewFrame(byte[]data,Cameracamera){
}
@Override
publicvoidsurfaceCreated(SurfaceHolderholder){
initCamera();
}
@Override
publicvoidsurfaceChanged(SurfaceHolderholder,intformat,intwidth,intheight){
}
@Override
publicvoidsurfaceDestroyed(SurfaceHolderholder){
destroyCamera();
}
privatevoidinitCamera(){
try{
mCamera=Camera.open(mCameraId);
mCamera.setPreviewDisplay(mSurfaceHolder);
Camera.Parametersparams=mCamera.getParameters();
//设置预览大小
params.setPreviewSize(width,height);
//设置生成的照片大小
params.setPictureSize(width,height);
params.setPreviewFormat(ImageFormat.NV21);
mCamera.setDisplayOrientation(90);
//params.setRotation(90);
/*Listsizes=params.getSupportedPreviewSizes();
for(Camera.Sizes:
sizes){
Log.e("LiveActivity",s.width+"X"+s.height);
}*/
mCamera.setParameters(params);
mCamera.setPreviewCallback(this);
mCamera.startPreview();
}catch(Exceptione){
e.printStackTrace();
}
}
privatevoiddestroyCamera(){
if(mCamera==null){
return;
}
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera=null;
}
}
我们通过menu来做了一个摄像头的切换功能,这样子就可以前摄像头直播或者后摄像头直播了。
到时会在onPreviewFrame里面获取到数据,然后交给jni进行一个编码的处理,然后就推流
那么这里就会有一个非常重要的知识点了:
我们通过setPreviewFormat方法把预览的数据(onPreviewFrame方法参数里面的data)的格式设置成了ImageFormat.NV21,一般来说,常用的格式是NV21或者YV12,因为这两种格式被所有的摄像头支持的,Android默认是会设置NV21的。
那么什么是NV21或YV12呢,其实这也是一种yuv格式的数据来的
上一篇文章我们已经说过了,就是通过把yuv通过编码,然后再封装就可以得到一个视频文件了,但我们还需要对这种yuv进行一定的处理,因为yuv也是有不同的各类的。
yuv通常分成两大格式,一种是planar:
把所有像素点的Y值全部存放在数组的最前面,然后再存放所有像素点的U值,最后再存放所有像素点的V值
还有一种就是packed:
它是依次存放每一个像素点的YUV值的
同时yuv还有不同的采样方式,一般主流的有三种:
YUV4:
4:
4每一个Y对应一组UV分量
YUV4:
2:
2每两个Y共用一组UV分量
YUV4:
2:
0每四个Y共用一组UV分量
假设一张720X480的图片存储成yuv格式:
YUV4:
4:
4Y=720*480U=V=720*480所以整个数组的大小就是720*480*3
YUV4:
2:
2Y=720*480U=V=720*480/2所以整个数组的大小就是720*480*2
YUV4:
2:
0Y=720*480U=V=720*480/4所以整个数组的大小就是720*480*1.5
NV21和YV12就是YUV4:
2:
0这种采样格式的,而且我们到时用FFmpeg编码采用的格式一般是AV_PIX_FMT_YUV420P,都是YUV4:
2:
0这种采样格式的
但还是有一些差别的
AV_PIX_FMT_YUV420P格式是planar,就是先存全部的Y再存全部的U再存全部的V,采样格式4:
2:
0。
存储格式类似yyyyyyyyuuvv这样
NV21格式也是planar,采样格式也是4:
2:
0。
存储格式类似yyyyyyyyvuvu
YV12格式也是planar,采样格式也是4:
2:
0。
存储格式类似yyyyyyyyvvuu
从上面可以看到,我们需要用的格式和预览的格式还是有些差别的,所以我们到时要处理一下。
那么现在我们可以先把我们的Camera的功能给测试一下先的,看看能不能预览成功,但在运行前,还需要去AndroidManifest里面配置一下
如果Camera模块测试没有问题的话,我们就可以来写native方法了,首先在LiveActivity里面定义好几个native方法
/**
*初始化编码的一些东西,比如编码器等
*@paramwidth编码视频的宽
*@paramheight编码视频的高
*@return0成功小于0失败
*/
privatenativeintstreamerInit(intwidth,intheight);
/**
*对每一次预览的数据进行编码推流
*@paramdataNV21格式的数据
*@return0成功,小于0失败
*/
privatenativeintstreamerHandle(byte[]data);
/**
*把缓冲帧的数据清空
*@return0成功,小于0失败
*/
privatenativeintstreamerFlush();
/**
*释放资源,比如编码器这些
*@return0成功,小于0失败
*/
privatenativeintstreamerRelease();
定义完成native方法后,我们先把LiveActivity里面的逻辑给处理一下先。
为了不影响UI线程(以后可能数据处理会有点多),我就使用了HandlerThread这个类来进行异步操作,先把类初始化
mHandlerThread=newHandlerThread("liveHandlerThread");
mHandlerThread.start();
mHandler=newLiveHandler(this,mHandlerThread.getLooper());
LiveHandler是我定义在LiveActivity的静态内部类,用来进行异步操作的
privatestaticclassLiveHandlerextendsHandler{
privateWeakReferencemActivity;
publicLiveHandler(LiveActivityactivity,Looperlooper){
super(looper);
mActivity=newWeakReference(activity);
}
@Override
publicvoidhandleMessage(Messagemsg){
super.handleMessage(msg);
LiveActivityactivity=mActivity.get();
if(activity==null){
return;
}
switch(msg.what){
caseSTREAMER_INIT:
break;
caseSTREAMER_HANDLE:
Bundlebundle=msg.getData();
if(bundle!
=null){
byte[]data=bundle.getByteArray("frame_data");
if(data!
=null&&data.length>0){
activity.streamerHandle(data);
}else{
Log.e("LiveActivity","bytedatanull");
}
}else{
Log.e("LiveActivity","bundlenull");
}
break;
caseSTREAMER_FLUSH:
activity.streamerFlush();
break;
caseSTREAMER_RELEASE:
activity.streamerRelease();
break;
}
}
}
LiveActivity里面的逻辑主要是一些细节的处理,完整的代码就下面那样:
packagecom.xiaoxiao.live;
importandroid.graphics.ImageFormat;
importandroid.hardware.Camera;
importandroid.os.Bundle;
importandroid.os.Handler;
importandroid.os.HandlerThread;
importandroid.os.Looper;
importandroid.os.Message;
importandroid.support.v7.app.AppCompatActivity;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.SurfaceHolder;
importandroid.view.SurfaceView;
importjava.lang.ref.WeakReference;
/**
*CreatedbyAdministratoron2017/2/20.
*/
publicclassLiveActivityextendsAppCompatActivityimplementsSurfaceHolder.Callback,Camera.PreviewCallback{
privatestaticfinalintSTREAMER_INIT=0;
privatestaticfinalintSTREAMER_HANDLE=1;
privatestaticfinalintSTREAMER_RELEASE=2;
privatestaticfinalintSTREAMER_FLUSH=3;
privateCameramCamera;
privateSurfaceViewmSurfaceView;
privateSurfaceHoldermSurfaceHolder;
privateintmCameraId=0;
privateintwidth=720;
privateintheight=480;
/**
*判断有没有初始化成功,不成功不不进行后续的编码处理
*/
privateintliveInitResult=-1;
/**
*异步操作
*/
privateHandlerThreadmHandlerThread;
privateLiveHandlermHandler;
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live);
mSurfaceView=(SurfaceView)findViewById(R.id.live_sv_live);
mSurfaceHolder=mSurfaceView.getHolder();
mSurfaceHolder.setFixedSize(width,height);
mSurfaceHolder.addCallback(this);
mHandlerThread=newHandlerThread("liveHandlerThread");
mHandlerThread.start();
mHandler=newLiveHandler(this,mHandlerThread.getLooper());
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_live,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
if(item.getItemId()==R.id.checkable_menu){
booleanisChecked=item.isChecked();
Log.e("LiveActivity","checked:
"+isChecked);
item.setChecked(!
isChecked);
mCameraId=1-mCameraId;
destroyCamera();
initCamera();
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
@Override
publicvoidonPreviewFrame(byte[]data,Cameracamera){
/**
*如果初始化成功,那就把数据发送到Handler,然后再调用native方法
*/
if(liveInitResult==0&&data!
=null&&data.length>0){
Messagemsg=Message.obtain();
Bundlebundle=newBundle();
bundle.putByteArray("frame_data",data);
msg.what=STREAMER_HANDLE;
msg.setData(bundle);
mHandler.sendMessage(msg);
}
}
@Override
publicvoidsurfaceCreated(SurfaceHolderholder){
/**
*在surface创建的时候进行初始化,如果失败了,也是需要释放已经开辟了