说明:
单纯的用代码记录下实现过程,目前预览分辨率写死1280*720,实际使用过程中需要去选择合适分辨率。
my_test_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="320dp"
android:layout_height="180dp" />
</LinearLayout>
MyTestActivity
package org.wx.signalmediasdkdemo;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
import com.rtc_sdk.RtcCamera2;
public class MyTestActivity extends Activity {
private SurfaceView previewSurface;
private RtcCamera2 rtcCamera2;
private String TAG = MyTestActivity.class.getName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_test_activity);
previewSurface = findViewById(R.id.surfaceView);
previewSurface.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
rtcCamera2.openCamera();
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
});
rtcCamera2 = new RtcCamera2(this, previewSurface.getHolder());
}
}
RtcCamera2
package com.rtc_sdk;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.media.ImageReader;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
public class RtcCamera2 {
private static final String TAG = "RtcCamera2";
private Activity mActivity;
private int mCameraId = CameraCharacteristics.LENS_FACING_FRONT; // 要打开的摄像头ID
private Size mPreviewSize; // 预览大小
private CameraManager mCameraManager; // 相机管理者
private CameraCharacteristics mCameraCharacteristics; // 相机属性
private CameraDevice mCameraDevice; // 相机对象
private CameraCaptureSession mCaptureSession;
private CaptureRequest.Builder mPreviewRequestBuilder; // 相机预览请求的构造器
private CaptureRequest mPreviewRequest;
private Handler mBackgroundHandler;
private HandlerThread mBackgroundThread;
private ImageReader mImageReader;
private SurfaceHolder previewSurfaceHolder;
private int mDisplayRotate = 0;
/**
* 打开摄像头的回调
*/
private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
Log.d(TAG, "onOpened");
mCameraDevice = camera;
initPreviewRequest();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
Log.d(TAG, "onDisconnected");
releaseCamera();
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
Log.e(TAG, "Camera Open failed, error: " + error);
releaseCamera();
}
};
@TargetApi(Build.VERSION_CODES.M)
public RtcCamera2(Activity activity, SurfaceHolder previewSurfaceHolder) {
mActivity = activity;
mCameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
this.previewSurfaceHolder = previewSurfaceHolder;
// previewSurfaceHolder.setFixedSize(1280, 720);
}
@SuppressLint("MissingPermission")
public void openCamera() {
Log.v(TAG, "openCamera");
startBackgroundThread(); // 对应 releaseCamera() 方法中的 stopBackgroundThread()
try {
// mCameraCharacteristics = mCameraManager.getCameraCharacteristics(Integer.toString(mCameraId));
// StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// // 拍照大小,选择能支持的一个最大的图片大小
// Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea());
// Log.d(TAG, "picture size: " + largest.getWidth() + "*" + largest.getHeight());
// mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 2);
// // 预览大小,根据上面选择的拍照图片的长宽比,选择一个和控件长宽差不多的大小
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height, largest);
mPreviewSize = new Size(1280, 720);
Log.d(TAG, "preview size: " + mPreviewSize.getWidth() + "*" + mPreviewSize.getHeight());
startCodec();
// 打开摄像头
mCameraManager.openCamera(Integer.toString(mCameraId), mStateCallback, mBackgroundHandler);
} catch (Exception e) {
e.printStackTrace();
}
}
public void releaseCamera() {
Log.v(TAG, "releaseCamera");
stopCodec();
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (mCameraDevice != null) {
mCameraDevice.close();
mCameraDevice = null;
}
if (mImageReader != null) {
mImageReader.close();
mImageReader = null;
}
stopBackgroundThread(); // 对应 openCamera() 方法中的 startBackgroundThread()
}
private void initPreviewRequest() {
try {
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(previewSurfaceHolder.getSurface()); // 设置预览输出的 Surface
mPreviewRequestBuilder.addTarget(mEncoderSurface); // 设置预览输出的 Surface
mCameraDevice.createCaptureSession(Arrays.asList(previewSurfaceHolder.getSurface(), mEncoderSurface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCaptureSession = session;
// 设置连续自动对焦
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置自动曝光
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 设置完后自动开始预览
mPreviewRequest = mPreviewRequestBuilder.build();
startPreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Log.e(TAG, "ConfigureFailed. session: mCaptureSession");
}
}, mBackgroundHandler); // handle 传入 null 表示使用当前线程的 Looper
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "initPreviewRequest: Exception = " + e.getMessage());
}
}
public void startPreview() {
Log.v(TAG, "startPreview");
if (mCaptureSession == null || mPreviewRequestBuilder == null) {
Log.w(TAG, "startPreview: mCaptureSession or mPreviewRequestBuilder is null");
return;
}
try {
// 开始预览
mCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
public void stopPreview() {
Log.v(TAG, "stopPreview");
if (mCaptureSession == null || mPreviewRequestBuilder == null) {
Log.w(TAG, "stopPreview: mCaptureSession or mPreviewRequestBuilder is null");
return;
}
try {
mCaptureSession.stopRepeating();
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
public boolean isFrontCamera() {
return mCameraId == CameraCharacteristics.LENS_FACING_BACK;
}
public Size getPreviewSize() {
return mPreviewSize;
}
public void switchCamera(int width, int height) {
mCameraId ^= 1;
Log.d(TAG, "switchCamera: mCameraId: " + mCameraId);
releaseCamera();
openCamera();
}
private Size chooseOptimalSize(Size[] sizes, int viewWidth, int viewHeight, Size pictureSize) {
int totalRotation = getRotation();
boolean swapRotation = totalRotation == 90 || totalRotation == 270;
int width = swapRotation ? viewHeight : viewWidth;
int height = swapRotation ? viewWidth : viewHeight;
return getSuitableSize(sizes, width, height, pictureSize);
}
private int getRotation() {
int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
switch (displayRotation) {
case Surface.ROTATION_0:
displayRotation = 90;
break;
case Surface.ROTATION_90:
displayRotation = 0;
break;
case Surface.ROTATION_180:
displayRotation = 270;
break;
case Surface.ROTATION_270:
displayRotation = 180;
break;
}
int sensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
mDisplayRotate = (displayRotation + sensorOrientation + 270) % 360;
return mDisplayRotate;
}
private Size getSuitableSize(Size[] sizes, int width, int height, Size pictureSize) {
int minDelta = Integer.MAX_VALUE; // 最小的差值,初始值应该设置大点保证之后的计算中会被重置
int index = 0; // 最小的差值对应的索引坐标
float aspectRatio = pictureSize.getHeight() * 1.0f / pictureSize.getWidth();
Log.d(TAG, "getSuitableSize. aspectRatio: " + aspectRatio);
for (int i = 0; i < sizes.length; i++) {
Size size = sizes[i];
// 先判断比例是否相等
if (size.getWidth() * aspectRatio == size.getHeight()) {
int delta = Math.abs(width - size.getWidth());
if (delta == 0) {
return size;
}
if (minDelta > delta) {
minDelta = delta;
index = i;
}
}
}
return sizes[index];
}
private void startBackgroundThread() {
if (mBackgroundThread == null || mBackgroundHandler == null) {
Log.v(TAG, "startBackgroundThread");
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
private void stopBackgroundThread() {
Log.v(TAG, "stopBackgroundThread");
if (mBackgroundThread != null) {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Compares two {@code Size}s based on their areas.
*/
static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
private MediaCodec mCodec = null;
private Surface mEncoderSurface;
private byte[] spspps = new byte[512];
private byte[] fakeFrame = new byte[160 * 90 * 3 / 2];
private int spspps_size = 0;
public int BITRATE = 3000 * 1000;
private static final int BITRATE_ADJUSTMENT_FPS = 30;
private long mStreamId = 123123;
private void startCodec() {
try {
Log.e(TAG, "cameraId=" + mCameraId + " 编码器:宽=" + mPreviewSize.getWidth() + " 高=" + mPreviewSize.getHeight());
// Log.e(TAG, "cameraId=" + mCameraId + " 编码器:宽=" + PREVIEW_WIDTH + " 高=" + PREVIEW_HEIGHT);
mCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
// MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720);
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mPreviewSize.getWidth(), mPreviewSize.getHeight());
// mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ);
// mediaFormat.setInteger(MediaFormat.KEY_QUALITY, 30);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BITRATE);//500kbps
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, BITRATE_ADJUSTMENT_FPS);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//mediaFormat.setInteger(MediaFormat.KEY_ROTATION, 90);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3);
mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
// mediaFormat.setInteger(MediaFormat.KEY_PROFILE,MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
// mediaFormat.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel41);
mCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mEncoderSurface = mCodec.createInputSurface();
//method 1
mCodec.setCallback(new EncoderCallback());
mCodec.start();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "startCodec: Exception = " + e.getMessage());
}
}
private void stopCodec() {
try {
if (mCodec != null) {
spspps = new byte[512];
mCodec.setCallback(null);
mCodec.stop();
mCodec.release();
mCodec = null;
mEncoderSurface = null;
}
} catch (Exception e) {
e.printStackTrace();
spspps = new byte[512];
mCodec = null;
mEncoderSurface = null;
}
}
private class EncoderCallback extends MediaCodec.Callback {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
try {
ByteBuffer outPutByteBuffer = mCodec.getOutputBuffer(index);
byte[] outDate = new byte[info.size];
outPutByteBuffer.get(outDate);
Log.e(TAG, "编码数据长度 outDate.length=" + outDate.length);
if ((outDate[4] & 0x1f) == (byte) 0x07) {
System.arraycopy(outDate, 0, spspps, 0, outDate.length);
spspps_size = outDate.length;
mCodec.releaseOutputBuffer(index, false);
return;
}
rtc_impl.GetInstance().AveOnVideoCaptureFrame(mStreamId, fakeFrame, 160, 90, 0, 0);
if ((outDate[4] & 0x1f) == (byte) 0x05 && spspps_size != 0) {
rtc_impl.GetInstance().AveOnVideoEncodedFrame(mStreamId, spspps, spspps_size, info.presentationTimeUs / 1000);
}
rtc_impl.GetInstance().AveOnVideoEncodedFrame(mStreamId, outDate, outDate.length, info.presentationTimeUs / 1000);
mCodec.releaseOutputBuffer(index, false);
} catch (Exception e) {
e.printStackTrace();
Log.i("hwk", "onOutputBufferAvailable e=" + e.getMessage());
}
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
Log.d(TAG, "Error: " + e);
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
Log.d(TAG, "encoder output format changed: " + format);
}
}
}