前言
方案選擇
視頻預覽
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
@Override
public void onDrawFrame(GL10 gl) {
}
mCamera = Camera.open(cameraId);
if (mCamera != null){
/**選擇當前設備允許的預覽尺寸*/
Camera.Parameters param = mCamera.getParameters();
preSize = getPropPreviewSize(param.getSupportedPreviewSizes(), mConfig.rate,
mConfig.minPreviewWidth);
picSize = getPropPictureSize(param.getSupportedPictureSizes(),mConfig.rate,
mConfig.minPictureWidth);
param.setPictureSize(picSize.width, picSize.height);
param.setPreviewSize(preSize.width,preSize.height);
mCamera.setParameters(param);
}
private Camera.Size getPropPictureSize(List<Camera.Size> list, float th, int minWidth){
Collections.sort(list, sizeComparator);
int i = 0;
for(Camera.Size s:list){
if((s.height >= minWidth) && equalRate(s, th)){
break;
}
i++;
}
if(i == list.size()){
i = 0;
}
return list.get(i);
}
private Camera.Size getPropPreviewSize(List<Camera.Size> list, float th, int minWidth){
Collections.sort(list, sizeComparator);
int i = 0;
for(Camera.Size s:list){
if((s.height >= minWidth) && equalRate(s, th)){
break;
}
i++;
}
if(i == list.size()){
i = 0;
}
return list.get(i);
}
private static boolean equalRate(Camera.Size s, float rate){
float r = (float)(s.width)/(float)(s.height);
if(Math.abs(r - rate) <= 0.03) {
return true;
}else{
return false;
}
}
private Comparator<Camera.Size> sizeComparator=new Comparator<Camera.Size>(){
public int compare(Camera.Size lhs, Camera.Size rhs) {
if(lhs.height == rhs.height){
return 0;
}else if(lhs.height > rhs.height){
return 1;
}else{
return -1;
}
}
};
這個代碼還是相當簡單,這裏就不過多介紹了,網上也有很多不同的但是類似功能的適配方法,大家可以多瞭解下,相互對照。第二個就是,攝像頭取數據的座標系和屏幕顯示的座標系不太相同,簡單的說就是,不管是前置還是後置攝像頭,我們都需要對攝像頭取的數據進行一些座標系旋轉操作,才能正常的顯示到屏幕上,不然的話就會出現畫面扭曲的情況。因爲我們是採用的OpengGL進行視頻錄製的,所以我們會有一系列的AFilter來進行shader的加載和畫面的渲染工作,所以我們將攝像頭數據的旋轉也放到這個裏面來做。這部分後面再說,CameraController類主要就是Camera的一個包裝類,還會包括一些視頻尺寸控制等代碼,具體的請下載完整demo,進行查看。
public static int uLoadShader(int shaderType,String source){
int shader= GLES20.glCreateShader(shaderType);
if(0!=shader){
GLES20.glShaderSource(shader,source);
GLES20.glCompileShader(shader);
int[] compiled=new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,compiled,0);
if(compiled[0]==0){
glError(1,"Could not compile shader:"+shaderType);
glError(1,"GLES20 Error:"+ GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader=0;
}
}
return shader;
}
Buffer的初始化
/**
* Buffer初始化
*/
protected void initBuffer(){
ByteBuffer a= ByteBuffer.allocateDirect(32);
a.order(ByteOrder.nativeOrder());
mVerBuffer=a.asFloatBuffer();
mVerBuffer.put(pos);
mVerBuffer.position(0);
ByteBuffer b= ByteBuffer.allocateDirect(32);
b.order(ByteOrder.nativeOrder());
mTexBuffer=b.asFloatBuffer();
mTexBuffer.put(coord);
mTexBuffer.position(0);
}
綁定默認的紋理
/**
* 綁定默認紋理
*/
protected void onBindTexture(){
GLES20.glActiveTexture(GLES20.GL_TEXTURE0+textureType);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,getTextureId());
GLES20.glUniform1i(mHTexture,textureType);
}
每次繪製前需要清理畫布
/**
* 清理畫布
*/
protected void onClear(){
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
}
這些代碼在不同的filter中其實都是公用的,所以我們通過一個抽象類,來進行管理。
public void setFlag(int flag) {
super.setFlag(flag);
float[] coord;
if(getFlag()==1){ //前置攝像頭 順時針旋轉90,並上下顛倒
coord=new float[]{
1.0f, 1.0f,
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
};
}else{ //後置攝像頭 順時針旋轉90度
coord=new float[]{
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
}
mTexBuffer.clear();
mTexBuffer.put(coord);
mTexBuffer.position(0);
}
攝像頭和AFilter我們都已經準備好了,下一步,就是我們需要把Camera取的數據顯示在GLSurfaceView上面了,也就是需要將AFilter、CameraController和GLSurfaceView聯繫起來。然後,因爲我們後續會涉及到很多不同AFilter的管理,所以我們創建一個CameraDraw類,來管理AFilter。讓其實現GLSurfaceView.Renderer接口,便於管理。
public class CameraDrawer implements GLSurfaceView.Renderer
然後,在類的構造函數中,進行AFilter的初始化 public CameraDrawer(Resources resources){
//初始化一個濾鏡 也可以叫控制器
showFilter = new ShowFilter(resources);
}
在onSurfaceCreated中,進行SurfaceTextured的創建,並且和AFilter進行綁定 @Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
textureID = createTextureID();
mSurfaceTextrue = new SurfaceTexture(textureID);
showFilter.create();
showFilter.setTextureId(textureID);
}
在onSurfaceChanged中,進行一些參數的更改和紋理的重新綁定 @Override
public void onSurfaceChanged(GL10 gl10, int i, int i1) {
width = i;
height = i1;
/**創建一個幀染緩衝區對象*/
GLES20.glGenFramebuffers(1,fFrame,0);
/**根據紋理數量 返回的紋理索引*/
GLES20.glGenTextures(1, fTexture, 0);
/**將生產的紋理名稱和對應紋理進行綁定*/
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fTexture[0]);
/**根據指定的參數 生產一個2D的紋理 調用該函數前 必須調用glBindTexture以指定要操作的紋理*/
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, mPreviewWidth, mPreviewHeight,
0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
useTexParameter();
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);
}
在onDrawFrame中進行圖像的繪製工作。@Override
public void onDrawFrame(GL10 gl10) {
/**更新界面中的數據*/
mSurfaceTextrue.updateTexImage();
/**繪製顯示的filter*/
GLES20.glViewport(0,0,width,height);
showFilter.draw();
}
CameraDraw目前所做的主要工作就是這樣,然後我們將CameraController、CameraDraw和自定義的CameraView控件進行綁定,就可以實現攝像頭數據預覽了。 private void init() {
/**初始化OpenGL的相關信息*/
setEGLContextClientVersion(2);//設置版本
setRenderer(this);//設置Renderer
setRenderMode(RENDERMODE_WHEN_DIRTY);//主動調用渲染
setPreserveEGLContextOnPause(true);//保存Context當pause時
setCameraDistance(100);//相機距離
/**初始化Camera的繪製類*/
mCameraDrawer = new CameraDrawer(getResources());
/**初始化相機的管理類*/
mCamera = new CameraController();
}
然後,分別在三個生命週期的函數中調用CameraController和CameraDrawer的相關方法,以及打開攝像頭 @Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mCameraDrawer.onSurfaceCreated(gl,config);
if (!isSetParm){
open(0);
stickerInit();
}
mCameraDrawer.setPreviewSize(dataWidth,dataHeight);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mCameraDrawer.onSurfaceChanged(gl,width,height);
}
@Override
public void onDrawFrame(GL10 gl) {
if (isSetParm){
mCameraDrawer.onDrawFrame(gl);
}
}
然後在onFrameAvailable函數中,調用即可 @Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
this.requestRender();
}
我們的視頻預覽的主體流程就是這樣,然後我們可以直接在佈局中使用CameraView類即可。
視頻錄製和斷點錄製
drawFilter = new ShowFilter(resources);
這裏需要注意一下,爲了顯示在屏幕上是正常的,我們進行了旋轉的操作。所以,我們在錄製的AFilter裏面需要加上矩陣翻轉的控制。 OM= MatrixUtils.getOriginalMatrix();
MatrixUtils.flip(OM,false,true);//矩陣上下翻轉
drawFilter.setMatrix(OM);
然後同樣分別進行drawFilter的create,在onDrawFrame裏面講textureId進行綁定以及繪製。還有就是添加錄製控制的相關代碼 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, fTexture[0], 0);
GLES20.glViewport(0,0,mPreviewWidth,mPreviewHeight);
drawFilter.setTextureId(fTexture[0]);
drawFilter.draw();
//解綁
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);
if (recordingEnabled){
/**說明是錄製狀態*/
switch (recordingStatus){
case RECORDING_OFF:
videoEncoder = new TextureMovieEncoder();
videoEncoder.setPreviewSize(mPreviewWidth,mPreviewHeight);
videoEncoder.startRecording(new TextureMovieEncoder.EncoderConfig(
savePath, mPreviewWidth, mPreviewHeight,
3500000, EGL14.eglGetCurrentContext(),
null));
recordingStatus = RECORDING_ON;
break;
case RECORDING_ON:
case RECORDING_PAUSED:
break;
case RECORDING_RESUMED:
videoEncoder.updateSharedContext(EGL14.eglGetCurrentContext());
videoEncoder.resumeRecording();
recordingStatus = RECORDING_ON;
break;
case RECORDING_RESUME:
videoEncoder.resumeRecording();
recordingStatus=RECORDING_ON;
break;
case RECORDING_PAUSE:
videoEncoder.pauseRecording();
recordingStatus=RECORDING_PAUSED;
break;
default:
throw new RuntimeException("unknown recording status "+recordingStatus);
}
}else {
switch (recordingStatus) {
case RECORDING_ON:
case RECORDING_RESUMED:
case RECORDING_PAUSE:
case RECORDING_RESUME:
case RECORDING_PAUSED:
videoEncoder.stopRecording();
recordingStatus = RECORDING_OFF;
break;
case RECORDING_OFF:
break;
default:
throw new RuntimeException("unknown recording status " + recordingStatus);
}
}
if (videoEncoder != null && recordingEnabled && recordingStatus == RECORDING_ON){
videoEncoder.setTextureId(fTexture[0]);
videoEncoder.frameAvailable(mSurfaceTextrue);
}
上面主要邏輯是,在數據返回的視頻判斷當前的錄製狀態,如果是正在錄製,就將SurfaceTexture給到VideoEncoder進行數據的編碼,如果沒有錄製,就跳過該幀,這樣就可以實現斷點續錄,即錄製 —>暫停錄製—>繼續錄製,而且這樣錄製出來的是一個整體的視頻文件。這裏就不貼出TexureMovieEncoder和VideoEncoderCore類的詳細代碼了。
Camera的手動對焦
Camera.Parameters parameters = mCamera.getParameters();
boolean supportFocus=true;
boolean supportMetering=true;
//不支持設置自定義聚焦,則使用自動聚焦,返回
if (parameters.getMaxNumFocusAreas() <= 0) {
supportFocus=false;
}
if (parameters.getMaxNumMeteringAreas() <= 0){
supportMetering=false;
}
List<Camera.Area> areas = new ArrayList<Camera.Area>();
List<Camera.Area> areas1 = new ArrayList<Camera.Area>();
//再次進行轉換
point.x= (int) (((float)point.x)/ MyApplication.screenWidth*2000-1000);
point.y= (int) (((float)point.y)/MyApplication.screenHeight*2000-1000);
int left = point.x - 300;
int top = point.y - 300;
int right = point.x + 300;
int bottom = point.y + 300;
left = left < -1000 ? -1000 : left;
top = top < -1000 ? -1000 : top;
right = right > 1000 ? 1000 : right;
bottom = bottom > 1000 ? 1000 : bottom;
areas.add(new Camera.Area(new Rect(left, top, right, bottom), 100));
areas1.add(new Camera.Area(new Rect(left, top, right, bottom), 100));
if(supportFocus){
parameters.setFocusAreas(areas);
}
if(supportMetering){
parameters.setMeteringAreas(areas1);
}
try {
mCamera.setParameters(parameters);// 部分手機 會出Exception(紅米)
mCamera.autoFocus(callback);
} catch (Exception e) {
e.printStackTrace();
}
主要涉及到了一下座標變換,因爲大部分的手機的前置攝像頭不支持對焦功能,所以我們不進行前置攝像頭的對焦。