应用 Camera API 进行视频的采集,别离应用 SurfaceView、TextureView 来预览 Camera 数据,取到NV21 的数据回调

筹备

应用相机权限

<uses-permission android:name="android.permission.CAMERA" />

camera 预览回调中默认应用 NV21格局。
查看手机是否反对摄像头。

UI筹备

<!-- 全屏显示 --><style name="FullScreenTheme" parent="AppTheme">    <item name="windowNoTitle">true</item>    <item name="android:windowFullscreen">true</item></style>

承载预览图像

<FrameLayout    android:id="@+id/camera_preview"    android:layout_width="match_parent"    android:layout_height="match_parent" />

应用 SurfaceView 预览 Camera,取到 NV21 数据

自定义 CameraPreview 继承 SurfaceView,实现 SurfaceHolder.Callback 接口

获取 NV21 数据,Camera.setPreviewCallback() 要放在 Camera.startPreview() 之前。 应用Camera.PreviewCallback 获取预览数据回调。默认是NV21格局。

surfaceChanged 中,camera 启动预览前能够进行设置,例如设置尺寸,调整方向

/** * camera预览视图 * Created by Rust on 2018/2/26. */public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {    private static final String TAG = "rustApp";    private SurfaceHolder mHolder;    private Camera mCamera;    private int mFrameCount = 0;    public CameraPreview(Context context) {        super(context);    }    public CameraPreview(Context context, Camera camera) {        super(context);        mCamera = camera;        mHolder = getHolder();        mHolder.addCallback(this);        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);    }    public void setCamera(Camera c) {        this.mCamera = c;    }    @Override    public void surfaceCreated(SurfaceHolder holder) {        // 开启预览        try {            mCamera.setPreviewDisplay(holder);            mCamera.startPreview();        } catch (IOException e) {            Log.d(TAG, "Error setting camera preview: " + e.getMessage());        }    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {        // 可在此开释camera    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {        // 若须要旋转、更改大小或从新设置,请确保障已进行预览        if (mHolder.getSurface() == null) {            return;        }        try {            mCamera.stopPreview();        } catch (Exception e) {            // ignore: tried to stop a non-existent preview        }        Camera.Parameters parameters = mCamera.getParameters();        // ImageFormat.NV21 == 17        Log.d(TAG, "parameters.getPreviewFormat(): " + parameters.getPreviewFormat());        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {            mCamera.setDisplayOrientation(90);        } else {            mCamera.setDisplayOrientation(0);        }        try {            mCamera.setPreviewDisplay(mHolder);            mCamera.setPreviewCallback(mCameraPreviewCallback); // 回调要放在 startPreview() 之前            mCamera.startPreview();        } catch (Exception e) {            Log.d(TAG, "Error starting camera preview: " + e.getMessage());        }    }    private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() {        @Override        public void onPreviewFrame(byte[] data, Camera camera) {            mFrameCount++;            Log.d(TAG, "onPreviewFrame: data.length=" + data.length + ", frameCount=" + mFrameCount);        }    };}

为了避免阻塞 UI 线程,在子线程中关上 camera。camera 常放在 try catch 中应用。

public class MainActivity extends AppCompatActivity {    private static final String TAG = "rustApp";    private Camera mCamera;    private CameraPreview mPreview;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        new InitCameraThread().start();    }    @Override    protected void onResume() {        if (null == mCamera) {            if (safeCameraOpen()) {                mPreview.setCamera(mCamera); // 从新获取camera操作权            } else {                Log.e(TAG, "无奈操作camera");            }        }        super.onResume();    }    @Override    protected void onPause() {        super.onPause();        releaseCamera();    }    private boolean safeCameraOpen() {        boolean qOpened = false;        try {            releaseCamera();            mCamera = Camera.open();            qOpened = (mCamera != null);        } catch (Exception e) {            Log.e(TAG, "failed to open Camera");            e.printStackTrace();        }        return qOpened;    }    private void releaseCamera() {        if (mCamera != null) {            mCamera.setPreviewCallback(null);            mCamera.release();        // release the camera for other applications            mCamera = null;        }    }    private class InitCameraThread extends Thread {        @Override        public void run() {            super.run();            if (safeCameraOpen()) {                Log.d(TAG, "开启摄像头");                runOnUiThread(new Runnable() {                    @Override                    public void run() {                        mPreview = new CameraPreview(MainActivity.this, mCamera);                        FrameLayout preview = findViewById(R.id.camera_preview);                        preview.addView(mPreview);                    }                });            }        }    }}

应用 TextureView 预览 Camera,取到 NV21 数据

TextureView可用于显示内容流。内容流能够是视频或者OpenGL的场景。内容流可来自利用过程或是近程其它过程。

Textureview必须在硬件加速开启的窗口中应用。若是软解,TextureView 不会显示货色。

不同于 SurfaceViewTextureView 不会建设一个独自的窗口,而是像一个惯例的View一样(集体认为这是个长处)。 这使得 TextureView 能够被挪动,转换或是增加动画。比方,能够调用myView.setAlpha(0.5f) 将其设置成半透明。

应用 TextureView 很简略:获取到它的 SurfaceTexture,应用 SurfaceTexture 出现内容。

CameraPreview 继承了 TextureView,内部须要传入 camera 实例。在 onSurfaceTextureAvailable中,配置 camera,比方设置图像方向。 通过设置 Camera.PreviewCallback 来获得预览数据。

import java.io.IOException;import android.content.Context;import android.content.res.Configuration;import android.graphics.SurfaceTexture;import android.hardware.Camera;import android.util.Log;import android.view.TextureView;public class CameraPreview extends TextureView implements TextureView.SurfaceTextureListener {    private static final String TAG = "rustApp";    private Camera mCamera;    public CameraPreview(Context context) {        super(context);    }    public CameraPreview(Context context, Camera camera) {        super(context);        mCamera = camera;    }    public void setCamera(Camera camera) {        this.mCamera = camera;    }    @Override    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {        Log.d(TAG, "TextureView onSurfaceTextureAvailable");        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {            mCamera.setDisplayOrientation(90);        } else {            mCamera.setDisplayOrientation(0);        }        try {            mCamera.setPreviewCallback(mCameraPreviewCallback);            mCamera.setPreviewTexture(surface); // 应用SurfaceTexture            mCamera.startPreview();        } catch (IOException ioe) {            // Something bad happened        }    }    @Override    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {        Log.d(TAG, "TextureView onSurfaceTextureSizeChanged"); // Ignored, Camera does all the work for us    }    @Override    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {        Log.d(TAG, "TextureView onSurfaceTextureDestroyed");        mCamera.stopPreview();        mCamera.release();        return true;    }    @Override    public void onSurfaceTextureUpdated(SurfaceTexture surface) {        // Invoked every time there's a new Camera preview frame    }    private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() {        @Override        public void onPreviewFrame(byte[] data, Camera camera) {            Log.d(TAG, "onPreviewFrame: data.length=" + data.length);        }    };}

操作界面 TextureAct。获取 camera 操作权,初始化 CameraPreview 并增加到布局中。第一次获取camera时在子线程中操作。

onPause 中开释 camera,onResume 中尝试取回 camera 控制权。这样利用临时退回后盾时,其余利用能够操作摄像头。

public class TextureAct extends AppCompatActivity {    private static final String TAG = "rustApp";    private Camera mCamera;    private CameraPreview mPreview;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_texture);        new InitCameraThread().start();    }    @Override    protected void onResume() {        if (null == mCamera) {            if (safeCameraOpen()) {                mPreview.setCamera(mCamera); // 从新获取camera操作权            } else {                Log.e(TAG, "无奈操作camera");            }        }        super.onResume();    }    @Override    protected void onPause() {        super.onPause();        releaseCamera();    }    @Override    protected void onDestroy() {        super.onDestroy();        releaseCamera();    }    private boolean safeCameraOpen() {        boolean qOpened = false;        try {            releaseCamera();            mCamera = Camera.open();            qOpened = (mCamera != null);        } catch (Exception e) {            Log.e(TAG, "failed to open Camera");            e.printStackTrace();        }        return qOpened;    }    private void releaseCamera() {        if (mCamera != null) {            mCamera.setPreviewCallback(null);            mCamera.release();        // release the camera for other applications            mCamera = null;        }    }    private class InitCameraThread extends Thread {        @Override        public void run() {            super.run();            if (safeCameraOpen()) {                Log.d(TAG, "TextureAct 开启摄像头");                runOnUiThread(new Runnable() {                    @Override                    public void run() {                        mPreview = new CameraPreview(TextureAct.this, mCamera);                        mPreview.setSurfaceTextureListener(mPreview);                        FrameLayout preview = findViewById(R.id.camera_preview);                        preview.addView(mPreview);                    }                });            }        }    }}

Textureview 必须在硬件加速开启的窗口中应用。android:hardwareAccelerated="true" 默认的这个属性就是 true,无需再设置。

每接到一帧数据,就会调用一次 onSurfaceTextureUpdated()。通过这个接口。可能将上来的SurfaceTexture 送给 OpenGL 再去解决。

【Android音视频开发系列教程】