android短視訊錄製與頭像跟隨(一)

wall_j發表於2016-12-07

最近在為公司的短視訊專案做技術預研,研究了很久發現網上關於音視訊的資料實在是有點少,所以我想自己來堅持寫點東西,我會盡力把這個系列寫完,相信會對後來的人有點幫助,因為本人對音視訊研究並不深,難免整理有錯,希望理解。


       在網上調研了很久安卓的視訊錄製,發現基本都是講的使用MediaRecorder,來進行錄製的方案,這是個使用非常簡單,封裝度很高的方式,最重要我不能使用它的原因是這種方案裡面我並不能在不hook任何系統類的情況下拿到視訊幀資料。


        經過一段時間的調研後,發現有些人的方案是這樣子的,通過為Camera類setPreviewCallback的,從而在這個Callback中我們能不斷的收到視訊資料回撥,這裡的視訊資料都是完全沒有經過編碼處理的。然後把這些視訊資料餵給jni層的視訊處理函式,視訊處理函式可能使用的是opengl之類的影象處理庫,將資料處理(例如:加水印)後,再傳遞給編碼器,編碼器可以是安卓系統的硬編碼方式,也可以是自己使用的第三方庫來進行的軟編碼方式(我目前瞭解到的大部分都是使用h264編碼標準)。編碼過後可以得到一個視訊資料。

      在我們開始錄製視訊的同時(即開始給jni的視訊處理函式喂資料時開始),我們需要啟動一個執行緒來執行AudioRecorder,從而獲得音訊資料,音訊錄製程式比較靠譜的一點是我們從AudioRecorder是拿到的完全未編碼的資料,這意味著我們需要自行編碼(音訊編碼幾乎都是使用的aac標準),關於音訊的採集和編碼個人覺得是相對簡單的,剛好我前段時間剛做完一個音訊直播的技術調研,有完整程式碼與文件,等下次專門寫篇文章來介紹一下。總之這裡也會要得到一個音訊資料。

      分別拿到了音訊和視訊的資料之後就涉及到了音視訊的複用和封裝了。這一塊我目前還正在苦苦趟坑中,等我搞定了就來寫下一篇文章。也希望能有大神看到了能幫我指點一下,先在這裡貼出我寫的從Camera的預覽回撥中拿到攝像頭資料的程式碼,以示誠意。


package com.example.shortvideodemo;

import java.io.IOException;
import java.util.List;

import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.widget.Toast;

public class CameraView extends SurfaceView implements Callback, PreviewCallback{

	private Camera camera;
	private int defaultWidth = 640,defaultHeight = 480,previewWidth = 0,previewHeight = 0;
	private byte[] previewBuffer = null;
	private boolean isPreviewing = false;
	
	public CameraView(Context context) {
		super(context);
		this.getHolder().addCallback(this);
	}
	
	public CameraView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.getHolder().addCallback(this);
	}
	
	private void prepareCamera(){
		this.camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
		if(camera == null){
			return;
		}
		Parameters params = camera.getParameters();
		params.setPreviewFormat(ImageFormat.NV21);
		List<Size> preSizeList = params.getSupportedPictureSizes();
		boolean supportDefault = false;
		for(Size size: preSizeList){
			if(size.width == defaultWidth && size.height == defaultHeight){
				supportDefault = true;
				break;
			}
		}
		if(supportDefault){
			params.setPreviewSize(defaultWidth, defaultHeight);
		}else{
			Toast.makeText(getContext(),"預設尺寸不匹配",Toast.LENGTH_SHORT).show();
		}
		this.camera.setParameters(params);
		this.camera.setDisplayOrientation(90);
		this.previewWidth = params.getPreviewSize().width;
		this.previewHeight = params.getPreviewSize().height;
		this.previewBuffer = new byte[this.previewHeight * this.previewWidth * (ImageFormat.getBitsPerPixel(ImageFormat.NV21)/8)];
		this.camera.addCallbackBuffer(this.previewBuffer);
		this.camera.setPreviewCallbackWithBuffer(this);
		try {
			this.camera.setPreviewDisplay(this.getHolder());
			this.camera.startPreview();
			this.isPreviewing = true;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void pausePreview(boolean pause){
		if(pause){
			if(this.isPreviewing && this.camera != null){
				this.camera.stopPreview();
				this.isPreviewing = false;
			}
		}else{
			if(!this.isPreviewing && this.camera != null){
				this.camera.startPreview();
				this.isPreviewing = true;
			}
		}
	}
	
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		prepareCamera();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		if(this.isPreviewing)
			this.camera.stopPreview();
		this.camera.release();
	}

	@Override
	public void onPreviewFrame(byte[] data, Camera camera) {
		// TODO Auto-generated method stub
		
	}

}


相關文章