应用 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
不会显示货色。
不同于 SurfaceView
,TextureView
不会建设一个独自的窗口,而是像一个惯例的 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 音视频开发系列教程】