音视频学习
Camera2
Camera的学习与使用
- 通过Camera.open()创建Camera实例
- 再通过camera.unlock来进行释放锁,否则无法使用
- 然后通过MediaCorder.setCamera来进行设置使用
由于Camera已经过时了,谷歌如今推出的是Camera2以及CameraX
Camera2的学习与使用
-
通过
context.getSystemService(Context.CAMERA_SERVICE)
获取CameraManager
. -
获取当前选中的CameraID
private void selectCamera() { if (mCameraManager != null) { Log.e(TAG, "selectCamera: CameraManager is null"); } try { String[] cameraIdList = mCameraManager.getCameraIdList(); //获取当前设备的全部摄像头id集合 if (cameraIdList.length == 0) { Log.e(TAG, "selectCamera: cameraIdList length is 0"); } for (String cameraId : cameraIdList) { //遍历所有摄像头 CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);//得到当前id的摄像头描述特征 Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); //获取摄像头的方向特征信息 if (facing == CameraCharacteristics.LENS_FACING_BACK) { //这里选择了后摄像头 mCurrentSelectCamera = cameraId; } } } catch (CameraAccessException e) { e.printStackTrace(); } }
-
设置Handler来进行子线程操作
-
调用
CameraManager .openCamera()
方法参数为刚才获取的CameraID,回调CameraDevice.StateCallback()以及Handler,在回调中得到CameraDevice
. -
通过
CameraDevice.createCaptureSession()
在回调中获取CameraCaptureSession
. -
通过
Device调用createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
构建PreviewCaptureRequest
, 有三种模式可选 预览/拍照/录像. -
通过
mCameraDevice
发送createCaptureSession
, capture表示只发一次请求, setRepeatingRequest表示不断发送请求.
在使用的过程中,Camera2需要进行权限声明式确认,createCaptureSession的第一个参数需要两个Surface,一个用于预览一个用于录制
MediaRecorder
-
首先声明权限
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
-
通过new MediaRecorder()来进行创建,获取MediaRecorder实例
-
通过setAudioSource设置音频来源
-
通过setVideoSource设置视频来源
-
通过setOutputFormat设置输出格式
-
通过setAudioEncoder设置音频编码格式,项目中需要考虑兼容问题,应该选择AAC
-
通过setVideoEncoder设置视频编码格式,项目中需要考虑兼容问题,应该选择H264
-
通过setVideoEncodingBitRate设置比特率 一般是 1分辨率 到 10分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。
-
通过setVideoFrameRate设置帧数,一般30即可
-
通过setVideoSize来进行宽度和高度设置,如果宽高不合适的话,会出现预览变形,视频播放失败等情况
/通过CameraManager.getCameraCharacteristics(当前选中的CameraID)获取cameraCharacteristics对象,然后获取size private Size getMathSize() { Size selectSize = null; try { CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentSelectCamera); StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG); //因为我这里是将预览铺满屏幕,所以直接获取屏幕分辨率 DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int deviceWidth = displayMetrics.widthPixels; //屏幕分辨率宽 int deviceHeight = displayMetrics.heightPixels; //屏幕分辨率高 /** * 循环40次,让宽度范围从最小逐步增加,找到最符合屏幕宽度的分辨率, * ,但是循环越大后获取的分辨率就越不匹配 */ for (int j = 1; j < 41; j++) { for (int i = 0; i < sizes.length; i++) { //遍历所有Size Size itemSize = sizes[i]; Log.e(TAG, "当前itemSize 宽=" + itemSize.getWidth() + "高=" + itemSize.getHeight()); //判断当前Size高度小于屏幕宽度+j*5 && 判断当前Size高度大于屏幕宽度-j*5 && 判断当前Size宽度小于当前屏幕高度 if (itemSize.getHeight() < (deviceWidth + j * 5) && itemSize.getHeight() > (deviceWidth - j * 5)) { if (selectSize != null) { //如果之前已经找到一个匹配的宽度 if (Math.abs(deviceHeight - itemSize.getWidth()) < Math.abs(deviceHeight - selectSize.getWidth())) { //求绝对值算出最接近设备高度的尺寸 selectSize = itemSize; continue; } } else { selectSize = itemSize; } } } if (selectSize != null) { //如果不等于null 说明已经找到了 跳出循环 break; } } } catch (CameraAccessException e) { e.printStackTrace(); } Log.e(TAG, "getMatchingSize2: 选择的分辨率宽度=" + selectSize.getWidth()); Log.e(TAG, "getMatchingSize2: 选择的分辨率高度=" + selectSize.getHeight()); return selectSize; }
-
通过setOrientationHint来进行录像旋转,否则录像预览是倒着的,录制的视频也是一样
-
通过setPreviewDisplay来进行录像预览设置
-
通过setOutputFile来设置输出文件位置
-
通过MediaRecorder.prepare来进行准备
-
通过MediaRecorder.Start来进行开始录制
-
通过MediaRecorder.Stop以及MediaRecorder.reset录制停止或者通过MediaRecorder.release进行资源释放
MediaPlayer
-
通过new MediaPlayer()进行MediaPlayer的实例创建
-
通过设置准备监听
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { mMediaPlayer.start(); } });
-
设置视频结束设置
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { mMediaPlayer.stop(); mMediaPlayer.release(); } });
-
通过setDataSource设置视频资源
-
通过setSurface设置播放的控件
-
通过prepareAsync来进行异步准备
SoundPool
-
通过new SoundPool.Builder().setMaxStreams(5).build()构建者模式创建实例
-
通过如下代码进行播放
mSoundPool.play(sound.getSoundId(),0.5f,0.5f,1,0,1.0f);
-
在关闭时,通过SoundPool.unload以及SoundPool.release()来进行资源释放
CameraX
通过上面的Camera2的学习,可以看出Camera2的配置是比较复杂的,于是官方发布了CameraX来进行使用,简化用户操作
添加依赖
// CameraX core library using the camera2 implementation
def camerax_version = "1.0.1"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha27"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:1.0.0-alpha27"
添加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
创建布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraXFragment">
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_take_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/btn_take_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="录像"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_jump"
app:layout_constraintStart_toEndOf="@+id/btn_take_photo" />
<Button
android:id="@+id/btn_jump"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
创建预览界面
//可以绑定生命周期的ProcessCameraProvider,ProcessCameraProvider
// 它会和宿主绑定生命周期,这样就不用担心打开相机和关闭的问题
mProviderListenableFuture
= ProcessCameraProvider.getInstance(requireContext());
//注册监听,第一个参数是一个 runnable,第二个参数是线程池
mProviderListenableFuture.addListener(() -> {
try {
// //将相机的生命周期和activity的生命周期绑定,camerax会自己释放,不用担心了
mProcessCameraProvider = mProviderListenableFuture.get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
//创建preview,支持角度换算
mPreview = new Preview.Builder().build();
//将Preview连接到 PreviewView,进行预览
mPreview.setSurfaceProvider(mCameraXBinding.previewView.getSurfaceProvider());
//创建图片的capture
mImageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.setTargetResolution(new Size(1920, 1080))
.build();
//创建录像的capture
mVideoCapture = new VideoCapture.Builder()
.setTargetRotation(mCameraXBinding.previewView.getDisplay().getRotation())
.setVideoFrameRate(30)
.setBitRate(5 * 1024 * 1024)
.build();
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(requireContext()),
image -> image.close());
//解绑
mProcessCameraProvider.unbindAll();
//将数据绑定到相机的生命周期中
mCamera = mProcessCameraProvider.bindToLifecycle(requireActivity(),
CameraSelector.DEFAULT_BACK_CAMERA //选择后置摄像头
, mVideoCapture, mPreview);
//点击聚焦
mCameraXBinding.previewView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
float x = motionEvent.getX();
float y = motionEvent.getY();
FocusMeteringAction focusMeteringAction = new FocusMeteringAction.Builder(
mCameraXBinding.previewView.getMeteringPointFactory()
.createPoint(x, y)).build();
mCamera.getCameraControl().startFocusAndMetering(focusMeteringAction);
return true;
}
});
}, ContextCompat.getMainExecutor(requireContext()));
开始拍照
File file = new File(requireActivity().getExternalFilesDir("").getAbsoluteFile(), "camerax.jpg");
if (file.exists()) {
file.delete();
}
//创建图片文件的数据
ImageCapture.OutputFileOptions outputFileOptions =
new ImageCapture.OutputFileOptions.Builder(file).build();
mProcessCameraProvider.bindToLifecycle(requireActivity(), CameraSelector.DEFAULT_BACK_CAMERA
, mImageCapture);
//开始拍照
mImageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(requireActivity()),
new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
Uri savedUri = outputFileResults.getSavedUri();
if (savedUri == null) {
savedUri = Uri.fromFile(file);
}
Toast.makeText(requireActivity(), "????,???" + savedUri
, Toast.LENGTH_SHORT).show();
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Log.e("TAG", "onError: ");
}
});
开始录像
File file = new File(requireActivity().getExternalFilesDir("").getAbsoluteFile(), "camerax.mp4");
if (file.exists()) {
file.delete();
}
//创建视频文件的数据,比如创建文件
VideoCapture.OutputFileOptions fileOptions = new VideoCapture.OutputFileOptions
.Builder(file).build();
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
return;
}
//开始拍照,拍照之前需要进行权限确认
mVideoCapture.startRecording(fileOptions, ContextCompat.getMainExecutor(requireActivity())
, new VideoCapture.OnVideoSavedCallback() {
//保存录像
@Override
public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) {
Toast.makeText(requireActivity(), "视频已保存"
+ outputFileResults.getSavedUri().getPath(), Toast.LENGTH_SHORT).show();
}
@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
Log.e("TAG", "onError: ");
//视频不足一秒会走到这里来,但是视频依然生成了,所以得删掉
file.delete();
}
});
停止录像
//判断视频的Capture是否为空
if (mVideoCapture != null){
//停止录像
mVideoCapture.stopRecording();
}
完整代码地址
https://gitee.com/thereisnoif/media-recorder-demo