Android mediarecord: start failed: -38错误解决办法

Android mediarecord: start failed: -38错误解决办法

  • 最近在使用android MediaRecord类进行录像时,总在MediaRecord.start()就报错(start failed: -38),网上众说纷纭,但都没法解决我的情况,经过试验,发现麦克风有其他线程在使用,我想这就是这个错误的原因吧,于是把麦克风的使用先暂时注释后,再进行录像,竟然成功了。
  • 总结,MediaRecord录像时请确保没其他线程在占用录像相关的资源,比如预览要关闭,麦克风没被占用等
  • 最后,贴下代码记录一下:
package com.gosuncn.instant.camera2;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
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.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresPermission;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.util.Size;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.Gravity;
import android.view.Surface;
import android.view.TextureView;
import android.widget.FrameLayout;
import android.widget.Toast;

import com.gosuncn.instant.util.CompareSizesByArea;
import com.gosuncn.instant.util.ImageUtil;
import com.gosuncn.instant.util.SizeSelector;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Semaphore;

import static com.gosuncn.instant.util.ImageUtil.COLOR_FormatI420;

/**
 * Camera2使用助手
 * 使用时注意动态权限问题
 * 参考: https://github.com/OmarAflak/Android-Camera2-Library/blob/master/app/src/main/java/me/aflak/libraries/MainActivity.java
 */
public class GSCamera2Helper {
    /**
     * 启动录制成功
     */
    public static final int MEDIARECORD_STATUS_START_SUCCESS = 1;
    /**
     * 启动录制失败
     */
    public static final int MEDIARECORD_STATUS_START_FAILED = 2;
    /**
     * 正在录制中
     */
    public static final int MEDIARECORD_STATUS_RECORDING = 3;
    /**
     * 未录制
     */
    public static final int MEDIARECORD_STATUS_UNRECORDED = 4;
    /**
     * 不支持此机型或版本
     */
    public static final int MEDIARECORD_STATUS_NOT_SUPPORT = 5;
    /**
     * 停止录制成功
     */
    public static final int MEDIARECORD_STATUS_STOP_SUCCESS = 6;
    /**
     * 停止录制失败
     */
    public static final int MEDIARECORD_STATUS_STOP_FAILED = 7;

    @IntDef(flag = true, value = {MEDIARECORD_STATUS_START_SUCCESS, MEDIARECORD_STATUS_START_FAILED, MEDIARECORD_STATUS_RECORDING, MEDIARECORD_STATUS_UNRECORDED
            , MEDIARECORD_STATUS_NOT_SUPPORT, MEDIARECORD_STATUS_STOP_SUCCESS, MEDIARECORD_STATUS_STOP_FAILED})
    public @interface MediaRecordStatus {

    }


    private static final String TAG = "GSCamera2Helper";
    private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
    private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
    private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
    private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();

    static {
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
        DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    static {
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
        INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
    }

    private static final String[] VIDEO_PERMISSIONS = {
            Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO,
    };

    private Context mContext;
    private GSCamera2Callback mCameraCallback;//各种事件状态回调

    private SparseArray<String> mCamerasList;//存储不同类型的摄像头id,比如前置后置等
    private String mCurrentCamera;//当前选择的摄像头id
    private Size mPreviewOutputSize;//预览输出尺寸
    private Size mMediaOutputSize;//录像输出尺寸

    private CameraManager mCameraManager;
    private CameraDevice mCameraDevice;//在open成功后赋值

    private CameraCaptureSession mCameraCaptureSession;
    private CameraCharacteristics mCameraCharacteristics;//当前摄像头的特征值属性
    private CaptureRequest.Builder mPreviewRequestBuilder;//预览
    private CaptureRequest.Builder mCaptureRequestBuilder;//抓拍

    //在selectCamera后赋值
    private ImageReader mCaptureImageReader;//抓拍
    private ImageReader mPreviewImageReader;//预览

    private HandlerThread mBackgroundThread;
    private Handler mBackgroundHandler;

    boolean mSwappedDimensions;//是否需要交换尺寸
    private AutoFitTextureView mPreviewTextureView;//预览视图,在open时赋值

    private SizeSelector mOutputSizeSelector;//预览尺寸选择器

    boolean mFlashSupported = false;//是否支持闪光灯

    private MediaRecorder mMediaRecorder;
    private Integer mSensorOrientation;

    private String mNextVideoAbsolutePath;
    private String mMediaRecordStoreDirectory;//录像保存目录

    private boolean mIsRecordingVideo;//是否正在录制
    /**
     * A {@link Semaphore} to prevent the app from exiting before closing the camera.
     */
    private Semaphore mCameraOpenCloseLock = new Semaphore(1);

    public GSCamera2Helper(Context context) {
        this.mContext = context;
        this.mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);

    }

    开放接口///start

    /**
     * 设置录像存放目录,在启动录像前设置才生效,否则将默认路径保存
     * (文件命名规则:时间戳.mp4)
     *
     * @param directory 最后不需要“/”
     */
    public void setMediaRecordFileStoreDirectory(String directory) {
        this.mMediaRecordStoreDirectory = directory;
    }

    /**
     * 获得录像存放目录
     *
     * @return
     */
    public String getMediaRecordFileStoreDirectory() {
        if (mMediaRecordStoreDirectory != null) {
            return mMediaRecordStoreDirectory;
        }
        final File dir = mContext.getExternalFilesDir(null);
        return dir == null ? "" : dir.getAbsolutePath();
    }


    /**
     * 生成录像文件完整路径,包含名称
     *
     * @return
     */
    private String genRecordFilePath() {
      /*  if (mMediaRecordStoreDirectory != null) {
            return mMediaRecordStoreDirectory + "/" + System.currentTimeMillis() + ".mp4";
        }
        final File dir = mContext.getExternalFilesDir(null);
        return (dir == null ? "" : (dir.getAbsolutePath() + "/"))
                + System.currentTimeMillis() + ".mp4";*/

        String videoSaveTime=System.currentTimeMillis()+"";
        if (mMediaRecordStoreDirectory != null) {
            return mMediaRecordStoreDirectory.endsWith("/")?
                    mMediaRecordStoreDirectory+videoSaveTime + ".mp4":
                    mMediaRecordStoreDirectory+File.separator+videoSaveTime + ".mp4";
        }
        final File dir = mContext.getExternalFilesDir(null);
        if(dir==null){
            return "";
        }
        String path=dir.getAbsolutePath();
        if(!path.endsWith("/")){
            path+=File.separator;
        }
        String savePath=path+ videoSaveTime + ".mp4";
        Log.i(TAG, "genRecordFilePath: "+savePath);
        return savePath;
    }

    /**
     * 获得上次录像文件完整路径
     * (一般在停止录像成功时可通过此接口获得当前录像的保存地址)
     *
     * @return
     */
    public String getPreRecordFilepath() {
        return mNextVideoAbsolutePath;
    }

    /**
     * 设置camera状态回调
     *
     * @param mCameraCallback callback
     */
    public void setmCameraCallback(GSCamera2Callback mCameraCallback) {
        this.mCameraCallback = mCameraCallback;
    }

    /**
     * 设置输出数据尺寸选择器,在selectCamera之前设置有效
     * (一般手机支持多种输出尺寸,请用户根据自身需要选择最合适的一种)
     * 举例:
     * SizeSelector maxPreview = SizeSelectors.and(SizeSelectors.maxWidth(720), SizeSelectors.maxHeight(480));
     * SizeSelector minPreview = SizeSelectors.and(SizeSelectors.minWidth(320), SizeSelectors.minHeight(240));
     * camera.setmOutputSizeSelector(SizeSelectors.or(
     * SizeSelectors.and(maxPreview, minPreview)//先在最大和最小中寻找
     * , SizeSelectors.and(maxPreview, SizeSelectors.biggest())//找不到则按不超过最大尺寸的那个选择
     * ));
     *
     * @param selector
     */
    public void setOutputSizeSelector(SizeSelector selector) {
        this.mOutputSizeSelector = selector;
    }

    /**
     * 是否需要交换尺寸,即画面尺寸与预览数据尺寸是否不一致
     * selectCamera后此接口才生效
     *
     * @return
     */
    public boolean swappedDimensions() {
        return mSwappedDimensions;
    }

    /**
     * 获得可用的摄像头
     *
     * @return SparseArray of available cameras ids。key为摄像头方位,见CameraCharacteristics#LENS_FACING,value为对应的摄像头id
     */
    public SparseArray<String> getCameras() {
        mCamerasList = new SparseArray<>();
        try {
            String[] camerasAvailable = mCameraManager.getCameraIdList();
            CameraCharacteristics cam;
            Integer characteristic;
            for (String id : camerasAvailable) {
                cam = mCameraManager.getCameraCharacteristics(id);//获得摄像头的特征值
                characteristic = cam.get(CameraCharacteristics.LENS_FACING);//摄像头方位
                if (characteristic != null) {
                    switch (characteristic) {
                        case CameraCharacteristics.LENS_FACING_FRONT://前置摄像头
                            mCamerasList.put(CameraCharacteristics.LENS_FACING_FRONT, id);
                            break;

                        case CameraCharacteristics.LENS_FACING_BACK://后置摄像头
                            mCamerasList.put(CameraCharacteristics.LENS_FACING_BACK, id);
                            break;

                        case CameraCharacteristics.LENS_FACING_EXTERNAL://其他
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                                mCamerasList.put(CameraCharacteristics.LENS_FACING_EXTERNAL, id);
                            }
                            break;
                    }
                }
            }
            return mCamerasList;
        } catch (CameraAccessException e) {
            notifyError(e.getMessage());
            return mCamerasList;
        }
    }

    /**
     * 选择摄像头,在打开摄像头之前调用
     *
     * @param id Id of the camera which can be retrieved with getCameras().get(CameraCharacteristics.LENS_FACING_BACK)
     */
    public void selectCamera(String id) {
        if (mCamerasList == null) {
            getCameras();
        }

        mCurrentCamera = mCamerasList.indexOfValue(id) < 0 ? null : id;
        if (mCurrentCamera == null) {
            notifyError("Camera id not found.");
            return;
        }

        try {
            mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCamera);
            // 设置是否支持闪光灯
            Boolean available = mCameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
            mFlashSupported = available == null ? false : available;
            StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            if (map == null) {
                notifyError("Could not get configuration map.");
                return;
            }
            mSensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//这个方法来获取CameraSensor的方向。
            Log.i(TAG, "camera sensor orientation: " + mSensorOrientation);
            int displayRotation = getDisplayRotation((Activity) mContext);
            Toast.makeText(mContext, "camera sensor orientation:" + mSensorOrientation + ",display rotation=" + displayRotation, Toast.LENGTH_SHORT).show();
            // 获取手机目前的旋转方向(横屏还是竖屏, 对于"自然"状态下高度大于宽度的设备来说横屏是ROTATION_90
            // 或者ROTATION_270,竖屏是ROTATION_0或者ROTATION_180)
            // 获取相机传感器的方向("自然"状态下垂直放置为0, 顺时针算起, 每次加90读)
            // 注意, 这个参数, 是由设备的生产商来决定的, 大多数情况下, 该值为90, 以下的switch这么写
            // 是为了配适某些特殊的手机
            mSwappedDimensions = false;
            Log.i(TAG, "displayRotation: " + displayRotation);
            Log.i(TAG, "sensorOritentation: " + mSensorOrientation);
            switch (displayRotation) {
                // ROTATION_0和ROTATION_180都是竖屏只需做同样的处理操作
                // 显示为竖屏时, 若传感器方向为90或者270, 则需要进行转换(标志位置true)
                case Surface.ROTATION_0:
                case Surface.ROTATION_180:
                    if (mSensorOrientation == 90 || mSensorOrientation == 270) {
                        Log.i(TAG, "mSwappedDimensions set true !");
                        mSwappedDimensions = true;
                    }
                    break;
                // ROTATION_90和ROTATION_270都是横屏只需做同样的处理操作
                // 显示为横屏时, 若传感器方向为0或者180, 则需要进行转换(标志位置true)
                case Surface.ROTATION_90:
                case Surface.ROTATION_270:
                    if (mSensorOrientation == 0 || mSensorOrientation == 180) {
                        Log.i(TAG, "swappedDimensions2 set true !");
                        mSwappedDimensions = true;
                    }
                    break;
                default:
                    Log.e(TAG, "Display rotation is invalid: " + displayRotation);
            }


            int[] formats = map.getOutputFormats();//获得手机支持的输出格式,其中jpeg是一定会支持的,yuv_420_888是api21才开始支持的

            //todo: 以下代码测试用,实际并无作用
            //test begin
            for (int format : formats) {
                Log.i(TAG, "getOutputFormats(yuv_420_888:0x23,jpeg:0x100): " + format);
            }
            Size[] yuvOutputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
            Size[] mediaOutputSizes = map.getOutputSizes(MediaRecorder.class);
            Size[] previewOutputSizes = map.getOutputSizes(SurfaceTexture.class);
            Size[] jpegOutputSizes = map.getOutputSizes(ImageFormat.JPEG);

            List<com.gosuncn.instant.util.Size> previewSizes = new ArrayList<>();
            List<com.gosuncn.instant.util.Size> mediaSizes = new ArrayList<>();
            for (Size size : mediaOutputSizes) {
                Log.i(TAG, "mediaOutputSizes: " + size.toString());
                mediaSizes.add(new com.gosuncn.instant.util.Size(size.getWidth(), size.getHeight()));
            }
            for (Size size : previewOutputSizes) {
                Log.i(TAG, "previewSizes: " + size.toString());
                previewSizes.add(new com.gosuncn.instant.util.Size(size.getWidth(), size.getHeight()));
            }
            for (Size size : yuvOutputSizes) {
                Log.i(TAG, "yuvOutputSizes: " + size.toString());

            }
            for (Size size : jpegOutputSizes) {
                Log.i(TAG, "jpegOutputSizes: " + size.toString());
            }
            //test end
            /**
             outputSizes: 3264x2448
             outputSizes: 2448x2448
             outputSizes: 3264x1840
             outputSizes: 1920x1080
             outputSizes: 1440x1080
             outputSizes: 1536x864
             outputSizes: 1280x960
             outputSizes: 1280x720
             outputSizes: 960x720
             outputSizes: 720x720
             outputSizes: 720x480
             outputSizes: 640x480
             outputSizes: 736x414
             outputSizes: 544x408
             outputSizes: 400x400
             outputSizes: 352x288
             outputSizes: 320x240
             outputSizes: 208x144
             outputSizes: 176x144

             previewSizes: 3264x2448
             previewSizes: 2448x2448
             previewSizes: 3264x1840
             previewSizes: 1920x1080
             previewSizes: 1440x1080
             previewSizes: 1536x864
             previewSizes: 1280x960
             previewSizes: 1280x720
             previewSizes: 960x720
             previewSizes: 720x720
             previewSizes: 720x480
             previewSizes: 640x480
             previewSizes: 736x414
             previewSizes: 544x408
             previewSizes: 400x400
             previewSizes: 352x288
             previewSizes: 320x240
             previewSizes: 208x144
             previewSizes: 176x144

             yuvOutputSizes: 3264x2448
             yuvOutputSizes: 2448x2448
             yuvOutputSizes: 3264x1840
             yuvOutputSizes: 1920x1080
             yuvOutputSizes: 1440x1080
             yuvOutputSizes: 1536x864
             yuvOutputSizes: 1280x960
             yuvOutputSizes: 1280x720
             yuvOutputSizes: 960x720
             yuvOutputSizes: 720x720
             yuvOutputSizes: 720x480
             yuvOutputSizes: 640x480
             yuvOutputSizes: 736x414
             yuvOutputSizes: 544x408
             yuvOutputSizes: 400x400
             yuvOutputSizes: 352x288
             yuvOutputSizes: 320x240
             yuvOutputSizes: 208x144
             yuvOutputSizes: 176x144

             jpegOutputSizes: 320x240
             jpegOutputSizes: 640x480
             jpegOutputSizes: 1280x720
             jpegOutputSizes: 1920x1080
             jpegOutputSizes: 2048x1536
             jpegOutputSizes: 2592x1456
             jpegOutputSizes: 2448x2448
             jpegOutputSizes: 2592x1952
             jpegOutputSizes: 3264x1840
             jpegOutputSizes: 3264x2448*/
            //todo:需要采取合适的策略选择预览的尺寸
            //previewSize = Collections.min(Arrays.asList(yuvOutputSizes), new CompareSizesByArea());
            if (mOutputSizeSelector != null) {
                com.gosuncn.instant.util.Size previewSize = mOutputSizeSelector.select(previewSizes).get(0);
                com.gosuncn.instant.util.Size mediaSize = mOutputSizeSelector.select(mediaSizes).get(0);
                mPreviewOutputSize = new Size(previewSize.getWidth(), previewSize.getHeight());
                mMediaOutputSize = new Size(mediaSize.getWidth(), mediaSize.getHeight());
            } else {
                mPreviewOutputSize = Collections.min(Arrays.asList(yuvOutputSizes), new CompareSizesByArea());
                mMediaOutputSize = Collections.min(Arrays.asList(mediaOutputSizes), new CompareSizesByArea());
            }

            Log.i(TAG, "selectCamera: previewsize=" + mPreviewOutputSize.toString() + "mediasize=" + mMediaOutputSize.toString());

            mCaptureImageReader = ImageReader.newInstance(mPreviewOutputSize.getWidth(), mPreviewOutputSize.getHeight(), ImageFormat.JPEG, 1);
            mPreviewImageReader = ImageReader.newInstance(mPreviewOutputSize.getWidth(), mPreviewOutputSize.getHeight(), ImageFormat.YUV_420_888, 2);
            mCaptureImageReader.setOnImageAvailableListener(onCaptureImageAvailableListener, mBackgroundHandler);
            mPreviewImageReader.setOnImageAvailableListener(onPreviewImageAvailableListener, mBackgroundHandler);

        } catch (Exception e) {
            notifyError(e.getMessage());
        }
    }

    /**
     * 打开摄像头进行预览
     *
     * @param textureView Surface where preview should be displayed
     */
    @SuppressLint("MissingPermission")
    @RequiresPermission(allOf = {Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO})
    public void openCamera(final AutoFitTextureView textureView) {

        //保存并设置监听器
        mPreviewTextureView = textureView;
        mPreviewTextureView.setSurfaceTextureListener(surfaceTextureListener);

        //检查权限
        if (!hasPermissionsGranted(VIDEO_PERMISSIONS)) {
            notifyError("You don't have the required permissions.");
            return;
        }

        try {
            startBackgroundThread();
            /*int orientation = getCurrentOrientation();
            if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                mPreviewTextureView.setAspectRatio(mPreviewOutputSize.getWidth(), mPreviewOutputSize.getHeight());
            } else {
                mPreviewTextureView.setAspectRatio(mPreviewOutputSize.getHeight(), mPreviewOutputSize.getWidth());
            }
            configureTransform(textureView.getWidth(), textureView.getHeight());*/


            mCameraManager.openCamera(mCurrentCamera, cameraDeviceStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            notifyError(e.getMessage());
        }
    }

    /**
     * 不再使用时调用此接口关闭camera
     */
    public boolean closeCamera() {
        stopBackgroundThread();
        try {
            if (null != mCameraCaptureSession) {
                mCameraCaptureSession.close();
                mCameraCaptureSession = null;
            }
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            if (null != mCaptureImageReader) {
                mCaptureImageReader.close();
                mCaptureImageReader = null;
            }
            if (null != mPreviewImageReader) {
                mPreviewImageReader.close();
                mPreviewImageReader = null;
            }

        } catch (Exception e) {
            e.printStackTrace();
            notifyError(e.getMessage());
            return false;
        }
        return true;
    }

    /**
     * Set CaptureRequest parameters for preview e.g. flash, auto-focus, macro mode, etc.
     *
     * @param key   e.g. CaptureRequest.CONTROL_EFFECT_MODE
     * @param value e.g. CameraMetadata.CONTROL_EFFECT_MODE_NEGATIVE
     */
    public <T> void setCaptureSetting(CaptureRequest.Key<T> key, T value) {
        if (mPreviewRequestBuilder != null && mCaptureRequestBuilder != null) {
            mPreviewRequestBuilder.set(key, value);
            mCaptureRequestBuilder.set(key, value);
        }
    }

    /**
     * 抓拍
     *
     * @return
     */
    public boolean captureImage() {
        if (mIsRecordingVideo) {
            notifyError("正在录制,无法抓拍");
            return false;
        }

        // mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
        mCaptureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION));
        try {
            mCameraCaptureSession.capture(mCaptureRequestBuilder.build(), null, mBackgroundHandler);
            return true;
        } catch (CameraAccessException e) {
            e.printStackTrace();
            notifyError(e.getMessage());
        }
        return false;
    }

    /**
     * 设置自动闪光灯
     *
     * @return
     */
    public boolean setAutoFlash() {
        if (!checkFlash()) {
            return false;
        }
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
        updatePreview();
        return true;
    }


    /**
     * 打开闪光灯
     */
    public boolean openFlash() {
        if (!checkFlash()) {
            return false;
        }
        mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
        updatePreview();
        return true;
    }

    /**
     * 关闭闪光灯
     */
    public boolean closeFlash() {
        if (!checkFlash()) {
            return false;
        }
        mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
        updatePreview();
        return true;
    }

    /**
     * Get characteristic of selected camera e.g. available effects, scene modes, etc.
     *
     * @param key e.g. CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS
     */
    public <T> T getCharacteristic(CameraCharacteristics.Key<T> key) {
        if (mCameraCharacteristics != null) {
            return mCameraCharacteristics.get(key);
        }
        return null;
    }


    /**
     * 开始视频录制
     * 注意录像前请保证录像的资源没被占用,比如我之前遇到start总是失败就是因为麦克风被其他线程占用了
     */
    public void startRecordingVideo() {
        if (mIsRecordingVideo) {
            notifyMediaRecordStatus(MEDIARECORD_STATUS_RECORDING, "已在录制中");
            return;
        }
        if (null == mCameraDevice) {
            notifyMediaRecordStatus(MEDIARECORD_STATUS_START_FAILED, "CameraDevice object is null.");
            return;
        }
        if (!mPreviewTextureView.isAvailable()) {
            notifyMediaRecordStatus(MEDIARECORD_STATUS_START_FAILED, "Preview TextureView is unavailable.");
            return;
        }
        if (null == mMediaOutputSize) {
            notifyMediaRecordStatus(MEDIARECORD_STATUS_START_FAILED, "MediaOutputSize is null.");
            return;
        }
        try {
            mIsRecordingVideo = false;
            closePreviewSession();
            setUpMediaRecorder();
            SurfaceTexture texture = mPreviewTextureView.getSurfaceTexture();
            assert texture != null;
            texture.setDefaultBufferSize(mMediaOutputSize.getWidth(), mMediaOutputSize.getHeight());
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
            List<Surface> surfaces = new ArrayList<>();

            // Set up Surface for the camera preview
            Surface previewSurface = new Surface(texture);
            surfaces.add(previewSurface);
            mPreviewRequestBuilder.addTarget(previewSurface);

            // Set up Surface for the MediaRecorder
            Surface recorderSurface = mMediaRecorder.getSurface();
            surfaces.add(recorderSurface);
            mPreviewRequestBuilder.addTarget(recorderSurface);

            // Start a capture session
            // Once the session starts, we can update the UI and start recording
            mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {

                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    try {
                        mCameraCaptureSession = session;
                        updatePreview();
                        mMediaRecorder.start();
                        mIsRecordingVideo = true;
                        notifyMediaRecordStatus(MEDIARECORD_STATUS_START_SUCCESS, "开始录制");
                    } catch (Exception e) {
                        e.printStackTrace();
                        notifyMediaRecordStatus(MEDIARECORD_STATUS_NOT_SUPPORT, "不支持此机型");
                        //重新启动预览
                        startPreview();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                    notifyMediaRecordStatus(MEDIARECORD_STATUS_START_FAILED, "启动录制失败");
                }
            }, mBackgroundHandler);
        } catch (Exception e) {
            e.printStackTrace();
            notifyMediaRecordStatus(MEDIARECORD_STATUS_START_FAILED, e.getMessage());
            //重新启动预览
            startPreview();
        }


    }

    /**
     * 停止视频录制,至少startRecordingVideo一秒后才能调用此接口
     */
    public boolean stopRecordingVideo() {
        if (!mIsRecordingVideo) {
            Log.e(TAG, "stopRecordingVideo: 未启动录制");
            notifyMediaRecordStatus(MEDIARECORD_STATUS_UNRECORDED, "未启动录制 ");
            return false;
        }
        try {

            // Stop recording
            mMediaRecorder.stop();
            mMediaRecorder.release();
            mMediaRecorder=null;
            mIsRecordingVideo = false;
            //重新启动预览
            startPreview();
            notifyMediaRecordStatus(MEDIARECORD_STATUS_STOP_SUCCESS, "已停止录制");
        } catch (Exception e) {
            e.printStackTrace();
            notifyMediaRecordStatus(MEDIARECORD_STATUS_STOP_FAILED, e.getMessage());
        }
        return true;
    }

    public void releaseRecording(){
       /* if(mMediaRecorder.ini){

        }*/
    }

    //开放接口/end

    /**
     * start the preview
     */
    private void startPreview() {
        if (null == mCameraDevice || !mPreviewTextureView.isAvailable() || null == mPreviewOutputSize) {
            return;
        }

        List<Surface> surfaces = new ArrayList<>();
        try {
            closePreviewSession();
            SurfaceTexture texture = mPreviewTextureView.getSurfaceTexture();
            assert texture != null;
            texture.setDefaultBufferSize(mPreviewOutputSize.getWidth(), mPreviewOutputSize.getHeight());
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            Surface previewSurface = mPreviewImageReader.getSurface();
            mPreviewRequestBuilder.addTarget(previewSurface);
            surfaces.add(previewSurface);

            Surface textureSurface = new Surface(texture);
            mPreviewRequestBuilder.addTarget(textureSurface);
            surfaces.add(textureSurface);

            mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            Surface captureSurface = mCaptureImageReader.getSurface();
            mCaptureRequestBuilder.addTarget(captureSurface);
            surfaces.add(captureSurface);

            mCameraDevice.createCaptureSession(surfaces, cameraCaptureSessionStateCallback, mBackgroundHandler);
        } catch (Exception e) {
            e.printStackTrace();
            notifyError("startPreview failed:" + e.getMessage());
        }
    }


    /**
     * stop the preview
     */
    private void stopPreview() {
        if (mCameraCaptureSession == null) {
            notifyError("mCameraCaptureSession is null.");
            return;
        }
        try {
            mCameraCaptureSession.stopRepeating();
        } catch (CameraAccessException e) {
            e.printStackTrace();
            notifyError(e.getMessage());
        }
    }

    /**
     * shortcut to call stopPreview() then startPreview()
     */
    private void restartPreview() {
        stopPreview();
        startPreview();
    }


    /**
     * 关闭预览session
     */
    private void closePreviewSession() {
        if (mCameraCaptureSession != null) {
            mCameraCaptureSession.close();
            mCameraCaptureSession = null;
        }
    }

    /**
     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
     * width and height are at least as large as the respective requested values, and whose aspect
     * ratio matches with the specified value.
     *
     * @param choices     The list of sizes that the camera supports for the intended output class
     * @param width       The minimum desired width
     * @param height      The minimum desired height
     * @param aspectRatio The aspect ratio
     * @return The optimal {@code Size}, or an arbitrary one if none were big enough
     */
    private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
        // Collect the supported resolutions that are at least as big as the preview Surface
        List<Size> bigEnough = new ArrayList<>();
        int w = aspectRatio.getWidth();
        int h = aspectRatio.getHeight();
        for (Size option : choices) {
            if (option.getHeight() == option.getWidth() * h / w &&
                    option.getWidth() >= width && option.getHeight() >= height) {
                bigEnough.add(option);
            }
        }

        // Pick the smallest of those, assuming we found any
        if (bigEnough.size() > 0) {
            return Collections.min(bigEnough, new CompareSizesByArea());
        } else {
            Log.e(TAG, "Couldn't find any suitable preview size");
            return choices[0];
        }
    }

    private boolean hasPermissionsGranted(String[] permissions) {
        for (String permission : permissions) {
            if (ActivityCompat.checkSelfPermission(mContext, permission)
                    != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    /**
     * MediaRecorder相关设置
     *
     * @throws IOException
     */
    private void setUpMediaRecorder() throws IOException {
        final Activity activity = (Activity) mContext;
        if (null == activity) {
            return;
        }
        if(mMediaRecorder == null){
            mMediaRecorder = new MediaRecorder();
        }
        mMediaRecorder.reset();
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mNextVideoAbsolutePath = genRecordFilePath();
        mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
        mMediaRecorder.setVideoEncodingBitRate(10000000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(mMediaOutputSize.getWidth(), mMediaOutputSize.getHeight());
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        switch (mSensorOrientation) {
            case SENSOR_ORIENTATION_DEFAULT_DEGREES:
                mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
                break;
            case SENSOR_ORIENTATION_INVERSE_DEGREES:
                mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
                break;
        }
        mMediaRecorder.prepare();
    }

    /**
     * 回调录像状态
     *
     * @param code
     * @param message
     */
    private void notifyMediaRecordStatus(@MediaRecordStatus int code, String message) {
        if (mCameraCallback != null) {
            mCameraCallback.onCameraMediaRecordStatusCallback(code, message);
        }
    }

    private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
        builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
    }

    /**
     * Update the camera preview. {@link #startPreview()} needs to be called in advance.
     */
    private void updatePreview() {
        if (null == mCameraDevice) {
            return;
        }
        try {
            setUpCaptureRequestBuilder(mPreviewRequestBuilder);
            mCameraCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
            notifyError("updatePreview failed:" + e.getMessage());
        }
    }

    private void notifyError(String message) {
        if (mCameraCallback != null) {
            mCameraCallback.onCameraError(message);
        }
    }

    /**
     * Flash设置前检查
     *
     * @return
     */
    private boolean checkFlash() {
        if (!mFlashSupported) {
            Log.e(TAG, "flash is not supported.");
            notifyError("flash is not supported.");
            return false;
        }
        if (mPreviewRequestBuilder == null) {
            Log.e(TAG, "mCaptureRequestBuilder is null. ");
            notifyError("mCaptureRequestBuilder is null.");
            return false;
        }
        return true;
    }

    /**
     * @return Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, Surface.ROTATION_270
     */
    private int getDisplayRotation(Activity activity) {
        return activity.getWindowManager().getDefaultDisplay().getRotation();
    }

    /**
     * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
     * This method should not to be called until the camera preview size is determined in
     * openCamera, or until the size of `mTextureView` is fixed.
     *
     * @param viewWidth  The width of `mTextureView`
     * @param viewHeight The height of `mTextureView`
     */
    private void configureTransform(int viewWidth, int viewHeight) {
        if (null == mPreviewTextureView || null == mPreviewOutputSize || null == mContext) {
            return;
        }
        int rotation = getDisplayRotation((Activity) mContext);
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
        RectF bufferRect = new RectF(0, 0, mPreviewOutputSize.getHeight(), mPreviewOutputSize.getWidth());
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max(
                    (float) viewHeight / mPreviewOutputSize.getHeight(),
                    (float) viewWidth / mPreviewOutputSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        }
        mPreviewTextureView.setTransform(matrix);
    }

    @Deprecated
    private void setupPreview_(TextureView textureView) {
        Surface surface = new Surface(textureView.getSurfaceTexture());

        List<Surface> surfaces = new ArrayList<>();
        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);
            Surface previewSurface = mPreviewImageReader.getSurface();
            mPreviewRequestBuilder.addTarget(previewSurface);
            surfaces.add(surface);
            surfaces.add(previewSurface);


            mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            Surface captureSurface = mCaptureImageReader.getSurface();
            mCaptureRequestBuilder.addTarget(captureSurface);
            surfaces.add(captureSurface);

            setCaptureSetting(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE, CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY);

            //经测试,无法创建两个session,后面创建的会导致前面的失效,从而抛出一系列异常
            mCameraDevice.createCaptureSession(surfaces, cameraCaptureSessionStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            notifyError(e.getMessage());
        }
    }

    @Deprecated
    private void setupPreview(AutoFitTextureView outputSurface) {
        if (outputSurface.isAvailable()) {
            Log.i(TAG, "setupPreview: size=" + outputSurface.getMeasuredWidth() + "*" + outputSurface.getMeasuredHeight());
            setupPreview_(outputSurface);
        }
    }

    /**
     * @param textureView
     * @param surfaceWidth
     * @param surfaceHeight
     */
    private void setAspectRatioTextureView(TextureView textureView, int surfaceWidth, int surfaceHeight) {
        int rotation = ((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation();
        int newWidth = surfaceWidth, newHeight = surfaceHeight;

        switch (rotation) {
            case Surface.ROTATION_0:
                newWidth = surfaceWidth;
                newHeight = (surfaceWidth * mPreviewOutputSize.getWidth() / mPreviewOutputSize.getHeight());
                break;

            case Surface.ROTATION_180:
                newWidth = surfaceWidth;
                newHeight = (surfaceWidth * mPreviewOutputSize.getWidth() / mPreviewOutputSize.getHeight());
                break;

            case Surface.ROTATION_90:
                newWidth = surfaceHeight;
                newHeight = (surfaceHeight * mPreviewOutputSize.getWidth() / mPreviewOutputSize.getHeight());
                break;

            case Surface.ROTATION_270:
                newWidth = surfaceHeight;
                newHeight = (surfaceHeight * mPreviewOutputSize.getWidth() / mPreviewOutputSize.getHeight());
                break;
        }

        textureView.setLayoutParams(new FrameLayout.LayoutParams(newWidth, newHeight, Gravity.CENTER));
        rotatePreview(textureView, rotation, newWidth, newHeight);
    }

    /**
     * 获得当前屏幕方向
     *
     * @return Configuration.ORIENTATION_LANDSCAPE or ORIENTATION_PORTRAIT
     */
    private int getCurrentOrientation() {
        return mContext.getResources().getConfiguration().orientation;
    }

    private void rotatePreview(TextureView mTextureView, int rotation, int viewWidth, int viewHeight) {
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        } else if (Surface.ROTATION_180 == rotation) {
            matrix.postRotate(180, centerX, centerY);
        }
        mTextureView.setTransform(matrix);
    }


    /**
     * 开启后台线程
     */
    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("GSCamera2Helper");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    /**
     * 停止后台线程
     */
    private void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();//Waits for this thread to die
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            notifyError(e.getMessage());
        }
    }


    //回调or监听/

    private CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            Log.i(TAG, "onOpened: " + camera.getId());
            if (mCameraCallback != null) {
                mCameraCallback.onCameraReady();
            }
            //打开摄像头成功后,即可以进行预览
            mCameraDevice = camera;
            startPreview();
           /* if (null != mPreviewTextureView) {
                configureTransform(mPreviewTextureView.getWidth(), mPreviewTextureView.getHeight());
            }*/
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            Log.i(TAG, "onDisconnected: " + camera.getId());
            notifyError("CameraDevice.StateCallback.onDisconnected:opencamera failed.");
            try {
                mCameraDevice.close();
                mCameraDevice = null;
            } catch (Exception e) {
                e.printStackTrace();
            }


        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Log.i(TAG, "onError: " + camera.getId() + ",error=" + error);
            try {
                mCameraDevice.close();
                mCameraDevice = null;
            } catch (Exception e) {
                e.printStackTrace();
            }
            switch (error) {
                case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:
                    notifyError("Camera device has encountered a fatal error.");
                    break;
                case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:
                    notifyError("Camera device could not be opened due to a device policy.");
                    break;
                case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:
                    notifyError("Camera device is in use already.");
                    break;
                case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE:
                    notifyError("Camera service has encountered a fatal error.");
                    break;
                case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE:
                    notifyError("Camera device could not be opened because there are too many other open camera devices.");
                    break;
            }
        }
    };
    private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            Log.i(TAG, "onSurfaceTextureAvailable: w*h=" + width + "," + height);
            //setAspectRatioTextureView(outputSurface, width, height);
            //outputSurface.setAspectRatio(width,height);
            // setupPreview_(mPreviewTextureView);
            startPreview();
        }

        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
            Log.i(TAG, "onSurfaceTextureSizeChanged: w*h=" + width + "," + height);
        }

        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            Log.i(TAG, "onSurfaceTextureDestroyed:");
            return false;
        }

        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
            Log.i(TAG, "onSurfaceTextureUpdated: ");
        }
    };
    private CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            Log.i(TAG, "onConfigured: ");
            mCameraCaptureSession = session;
            updatePreview();
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
            Log.i(TAG, "onConfigureFailed: ");
            notifyError("Could not configure capture session.");
        }
    };
    /*
     * 抓拍监听
     */
    private ImageReader.OnImageAvailableListener onCaptureImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Log.i(TAG, "onImageAvailable: imageformat(YUV_420_888:35,jpeg:256)=" + reader.getImageFormat());
            if (mCameraCallback == null) {
                return;
            }
            Image img = reader.acquireNextImage();
            if (img == null) {
                return;
            }
            int w = img.getWidth();
            int h = img.getHeight();
            Log.i(TAG, "onImageAvailable: w*h=" + w + "*" + h);
            mCameraCallback.onCameraCapture(img);
            img.close();
        }
    };
    /**
     * 预览监听,可以在此处获得摄像头帧数据
     */
    private ImageReader.OnImageAvailableListener onPreviewImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(final ImageReader reader) {
            //此处如果处理耗时则会导致预览页面卡顿
            //一定要调用 reader.acquireNextImage()和close()方法,否则画面就会卡住
            //帧率为25,即40ms回调一次,如果在此处处理耗时超过40ms则会出现卡顿
            // Log.i(TAG, "onPreviewImageAvailable: imageformat(YUV_420_888:35,jpeg:256)=" + reader.getImageFormat());
            long s, e;
            s = System.currentTimeMillis();
            Image img = reader.acquireNextImage();
            Log.i(TAG, "onPreviewImageAvailable: format=" + img.getFormat() + ",w*h=" + img.getWidth() + "*" + img.getHeight());
            if (img == null) {
                return;
            }
            if (mCameraCallback == null) {
                img.close();
                return;
            }
            int width = img.getWidth();
            int height = img.getHeight();
            byte[] data = ImageUtil.getDataFromImage(img, COLOR_FormatI420);//此接口消耗的时间大约在5ms内,基本可以忽略
            img.close();
            long s1 = System.currentTimeMillis(), e1;
            mCameraCallback.onCameraFrameProcessor(data, data.length, width, height, mSwappedDimensions);//为了流畅,此处最好在35ms内处理完毕
            e1 = System.currentTimeMillis();
            Log.i(TAG, "onFrameProcessor take " + (e1 - s1) + " ms");
            e = System.currentTimeMillis();
            Log.i(TAG, "onImageAvailable take " + (e - s) + " ms");
        }
    };
}
AndroidMediaRecord类可以用来录制音频,包括通过蓝牙设备进行录音。 要使用蓝牙设备录音,首先需要确保设备已经正确配对并连接了蓝牙耳机或扬声器。可以使用BluetoothAdapter类来管理蓝牙连接,以及使用BluetoothDevice类来获取已配对的蓝牙设备。 在开始录音之前,需要先创建一个MediaRecord对象,并设置音频的采样率、编码格式、音频源等参数。可以使用setAudioSource()方法来指定音频源为蓝牙耳机,例如MediaRecorder.AudioSource.MIC。 然后,使用setOutputFile()方法指定音频文件的保存路径和格式。可以使用getFileDescriptor()方法将蓝牙耳机连接的文件描述符作为录音文件的输出。 接下来,需要调用prepare()方法进行准备工作,并使用start()方法开始录音。录音过程中,可以使用stop()方法停止录音,并使用release()方法释放MediaRecord对象。 在录音完成后,可以使用MediaPlayer类来播放录音文件,或者使用其他处理方式对录音进行编辑和处理。 需要注意的是,蓝牙耳机的音质和录音效果可能会受到设备和耳机本身的限制,因此可能会在使用过程中遇到问题。在录音过程中,可以使用onErrorListener来处理录音出错的情况,并给出相应的提示。 总体而言,使用AndroidMediaRecord类进行蓝牙录音是很方便的。只需注意正确设置参数和处理录音异常的情况,就可以顺利实现蓝牙录音功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值