当前位置:   article > 正文

Camera2+MediaCodec+AudioRecord+MediaMuxer实现录制功能(带源码)_camera2 录制视频

camera2 录制视频

源码在最下面 最下面 最下面!

前言

最近在学习音视频开发,涉及到MediaCodec的相关知识点,了解到MediaCodec+MediaMuxer可以编码视频流以及音频流最后合成MP4。

现在网络上流动的帖子大部分都是通过CameraAPI去进行视频流的采集,很少使用Camera2API或者CameraXAPI去进行视频流的采集。这不正巧我的工作就是相机影像方面,咱最熟的就是Camera2。

那废话不多说!献丑了!

预览实现

按照惯例,我们先使用Camera2将相机的一个基本预览拉起来,这样也方便后续的调试。这边我就不会太过详细去讲解,如果有不是很了解的同学,可以去看我之前的帖子,毕竟输入了Camera2这个关键词看到我这篇帖子多多少少都是学习过相关API知识的。(50条消息) 使用Camera2实现预览功能_camera2 预览_JS-s的博客-CSDN博客

这边只会快速的过一下核心代码。

首先看一下布局文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context=".MainActivity"
  8. android:orientation="vertical"
  9. android:keepScreenOn="true">
  10. <TextureView
  11. android:id="@+id/previewtexture"
  12. android:layout_weight="1"
  13. android:layout_width="720dp"
  14. android:layout_height="720dp"/>
  15. <Button
  16. android:id="@+id/shutterclick"
  17. android:layout_margin="20dp"
  18. android:layout_width="match_parent"
  19. android:layout_height="50dp"
  20. android:text="点击录像"/>
  21. </LinearLayout>

非常简单,就是一个TextureView与一个开始录制的按钮 ,TextureView主要负责预览流的显示,按钮进行录制按钮的开始与结束。

在textureview可用之后,设置相机属性,打开相机,创建预览流,一气呵成。部分核心代码如下所示。(看不看懂无所谓噢,后面会直接把所有代码附上,让你慢慢看)

  1. previewTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
  2. @Override
  3. public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
  4. Log.d(TAG, "onSurfaceTextureAvailable");
  5. setupCamera(width, height);
  6. }
  7. @Override
  8. public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
  9. }
  10. @Override
  11. public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
  12. return false;
  13. }
  14. @Override
  15. public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
  16. }
  17. });
  18. private void setupCamera(int width, int height) {
  19. CameraManager cameraManage = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  20. try {
  21. String[] cameraIdList = cameraManage.getCameraIdList();
  22. for (String cameraId : cameraIdList) {
  23. CameraCharacteristics cameraCharacteristics = cameraManage.getCameraCharacteristics(cameraId);
  24. //demo就就简单写写后摄录像
  25. if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) != CameraCharacteristics.LENS_FACING_BACK) {
  26. //表示匹配到前摄,直接跳过这次循环
  27. continue;
  28. }
  29. StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  30. Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
  31. //因为重点不在相机预览,我们这边给定一个预览尺寸,不进行最优选择。
  32. Size size = new Size(1440, 1440);
  33. previewSize = size;
  34. mWidth = previewSize.getWidth();
  35. mHeight = previewSize.getHeight();
  36. mCameraId = cameraId;
  37. }
  38. } catch (CameraAccessException e) {
  39. throw new RuntimeException(e);
  40. }
  41. openCamera();
  42. }
  43. private void openCamera() {
  44. Log.d(TAG, "openCamera: success");
  45. CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  46. try {
  47. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  48. // TODO: Consider calling
  49. // ActivityCompat#requestPermissions
  50. // here to request the missing permissions, and then overriding
  51. // public void onRequestPermissionsResult(int requestCode, String[] permissions,
  52. // int[] grantResults)
  53. // to handle the case where the user grants the permission. See the documentation
  54. // for ActivityCompat#requestPermissions for more details.
  55. return;
  56. }
  57. cameraManager.openCamera(mCameraId, stateCallback, cameraHandler);
  58. } catch (CameraAccessException e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
  63. @Override
  64. public void onOpened(@NonNull CameraDevice cameraDevice) {
  65. Log.d(TAG, "onOpen");
  66. mCameraDevice = cameraDevice;
  67. startPreview(mCameraDevice);
  68. }
  69. @Override
  70. public void onDisconnected(@NonNull CameraDevice cameraDevice) {
  71. Log.d(TAG, "onDisconnected");
  72. }
  73. @Override
  74. public void onError(@NonNull CameraDevice cameraDevice, int i) {
  75. Log.d(TAG, "onError");
  76. }
  77. };
  78. private void startPreview(CameraDevice mCameraDevice) {
  79. try {
  80. Log.d(TAG, "startPreview");
  81. setPreviewImageReader();
  82. previewCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  83. SurfaceTexture previewSurfaceTexture = previewTextureView.getSurfaceTexture();
  84. previewSurfaceTexture.setDefaultBufferSize(mWidth, mHeight);
  85. Surface previewSurface = new Surface(previewSurfaceTexture);
  86. Surface previewImageReaderSurface = previewImageReader.getSurface();
  87. previewCaptureRequestBuilder.addTarget(previewSurface);
  88. mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, previewImageReaderSurface), new CameraCaptureSession.StateCallback() {
  89. @Override
  90. public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
  91. Log.d(TAG, "onConfigured");
  92. previewCaptureSession = cameraCaptureSession;
  93. try {
  94. cameraCaptureSession.setRepeatingRequest(previewCaptureRequestBuilder.build(), cameraPreviewCallback, cameraHandler);
  95. } catch (CameraAccessException e) {
  96. throw new RuntimeException(e);
  97. }
  98. }
  99. @Override
  100. public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
  101. }
  102. }, cameraHandler);
  103. } catch (CameraAccessException e) {
  104. throw new RuntimeException(e);
  105. }
  106. }

一个预览的基本流程就是这么一点东西,里面具体啥意思,我这边真不讲啊,要真不会的话,可以去看看我上面分享的博文链接。

这边我只需要额外注意一个方法,setPreviewImageReader(),这个跟后面录制视频的流程关系非常大。这个咱们来开第二个章节详细说道说道。

录像前的知识捡漏

AudioRecord

AudioRecordAndroid系统提供的用于实现录音的功能类。 这个录音使用流程非常简单,但是该类录制出来的是PCM文件是无法直接用常见的播放器进行播放的。但是使用MediaCodec实现录像功能,使用AudioRecord类来捕获音频流就非常合适,MediaCodec可以将PCM格式的数据转换成AAC格式,这个格式就非常适合合成MP4的音频。

实现 Android 录音的流程为:
1. 构造一个 AudioRecord 对象,其中需要的最小录音缓存buffffer大小可以通过 getMinBufffferSize方法得到。如果 buffffer容量过小,将导致对象构造的失败。
2. 初始化一个 buffffer ,该 buffffer 大于等于 AudioRecord 对象用于写声音数据的 buffffer 大小。
3. 开始录音
4. 创建一个数据流,一边从 AudioRecord 中读取声音数据到初始化的 buffffer ,一边将 buffffer 中数据导入数据流。
5. 关闭数据流
6. 停止录音

我们现在使用AudioRecord录制的音频是PCM格式,我们需要将录制到的buffer数据放入到MediaCodec中转换成AAC编码格式。 

MediaCodec 

Android 在 API 16 后引入的音视频编解码 API,Android 应用层统一由 MediaCodec API 提供音视频编解码的功能,由参数配置来决定采用何种编解码算法、是否采用硬件编解码加速等。由于使用硬件编解码,兼容性有不少问题,据说 MediaCodec 坑比较多。本人在使用的过程中那是非常多问题,煎熬的很。

MediaCodec 可以处理具体的流,主要有这几个方法:
getInputBuffffers :获取需要编码数据的输入流队列,返回的是一个 ByteBuffffer 数组
queueInputBuffffer :输入流入队列
dequeueInputBuffffer :从输入流队列中取数据进行编码操作
getOutputBuffffers :获取编解码之后的数据输出流队列,返回的是一个 ByteBuffffer 数组
dequeueOutputBuffffer :从输出队列中取出编码操作之后的数据
releaseOutputBuffffer :处理完成,释放 ByteBuffffer 数据
  1. //基本上的代码流程就是这样
  2. //创建MediaCodec
  3. - createEncoderByType/createDecoderByType
  4. //配置,这个必不可少,是MediaCodec的生命周期
  5. - configure
  6. //启动MediaCodec
  7. - start
  8. //不断获取待处理的数据流
  9. - while(true) {
  10. //在MediaCodec中拿到一个输入缓存区的索引index,可以将MeidaCodec缓存区理解成一个数组,
  11. //每个数组元素可进行buffer的存储,我们现在这个方法是拿到每个数组元素的下标。
  12. //好在下一步往每个数组元素里面存inputbuffer
  13. - dequeueInputBuffer
  14. //通过该方法将之前获取的流数据放入到缓存区中等到MediaCodec进行处理
  15. - queueInputBuffer
  16. //在MediaCodec中拿到一个输出缓存区的索引index
  17. //这个输出缓存区是之前输入的数据处理之后就会存放在这
  18. - dequeueOutputBuffer
  19. //通过拿到的索引获取处理之后的buffer值
  20. - getOutputBuffer
  21. //释放处理之后的buffer值
  22. - releaseOutputBuffer
  23. }
  24. - stop
  25. - release

MediaMuxer 

MediaMuxer的作用是生成音频或视频文件;还可以把音频与视频混合成一个音视频文件。

相关 API 介绍:
MediaMuxer(String path, int format)
path: 输出文件的名称                           format:输出文件的格式;当前只支持 MP4 格式;
addTrack(MediaFormat format):添加通道;
start() :开始合成文件
writeSampleData(int trackIndex, ByteBuffffer byteBuf,MediaCodec.BufffferInfobufffferInfo) :把 ByteBuffffer 中的数据写入到在构造器设置的文件中;
stop() :停止合成文件
release() :释放资源

实现录像流程 

流程图总结

我们先从流程图看看我们需要获取到什么数据,需要解决什么问题。

 根据流程我们可以总结一下:

  1. 我们需要想办法使用Camera2在录像的时候实时获取到预览的数据,并将其转换成NV21的格式方便后续处理。
  2. 使用AudioRecord实时录制音频,并且需要去到pcm音频数据。
  3. 因为视频流跟音频流需要分别编码成两个完全不相同的编码格式(h.264、AAC),我们需要分别创建两个MediaCodec进行分开处理。
  4. 两个MediaCodec处理的数据传入到MediaMuxer中,MediaMuxer进行合并成MP4。

接下来我们按照步骤一个一个将其走通。

Camera2获取YUV数据并转换NV21

首先咱们来说一下思路,Camera2跟Camera区别还是比较大的,Camera提供了一个回调可以直接拿到预览数据,但是Camera2就莫得,但是hold on hold on,先别骂。办法总比困难多,我们回想一下我们是怎么用Camera2实现拍照的。我们是通过imagereader当session.capture完之后,imagereader可用之后提取image变量从而进行保存图片,发现没有,这要稍做设置,image就可以提取出来YUV数据。按照这个思路,我们是否可以当执行录像的时候,例如session.setRepeatRequest的时候设置一个videoImageReader,每次捕获完之后videoImageReader就开始可以开始回调Image,从而获取到录像的YUV数据。

话不多说,思路有了之后,我们便可以进行代码实现。

 上面的代码是设置好相机各种属性之后,准备开启预览的时候的session设置,一切都跟Camera2的预览差不多,但是需要注意一下的是我红色框标注的部分,我新建了一个previewImageReader,这个ImageReader就是我上面说的录像的时候捕获YUV数据用的。因为我录像与预览使用的session是准备使用同一个的,所以我在新建session的时候就将previewImageReader的surface放入到了创建session中。但是需要注意的是,仔细看一下上面代码previewCaptureRequestBuilder.addTarget,会发现只add了textureview的surface,咱可千万不要一激动就把previewImageReaderSurface也给add进去了,要这样的话,到时候预览的时候previewImageReader可都会回调。我们在session进行录像的时候,重建一个capturerequestbuilder将previewImageReaderSurface给add进去,这个后面应该也会有相关的代码展示。

  1. private void startVideoSession() {
  2. Log.d(TAG, "startVideoSession");
  3. if (previewCaptureSession!=null){
  4. try {
  5. previewCaptureSession.stopRepeating();
  6. } catch (CameraAccessException e) {
  7. throw new RuntimeException(e);
  8. }
  9. }
  10. SurfaceTexture previewSurfaceTexture = previewTextureView.getSurfaceTexture();
  11. Surface previewSurface = new Surface(previewSurfaceTexture);
  12. Surface previewImageReaderSurface = previewImageReader.getSurface();
  13. try {
  14. videoRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
  15. videoRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
  16. videoRequest.addTarget(previewSurface);
  17. videoRequest.addTarget(previewImageReaderSurface);
  18. previewCaptureSession.setRepeatingRequest(videoRequest.build(), videosessioncallback, cameraHandler);
  19. } catch (CameraAccessException e) {
  20. throw new RuntimeException(e);
  21. }
  22. }

看见没有在这里才去addTarget,然后重新开一个重复捕获模式去进行录像。 

接下来我们来看看previewImageReader的创建代码:

  1. private void setPreviewImageReader() {
  2. previewImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 1);
  3. previewImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
  4. @Override
  5. public void onImageAvailable(ImageReader imageReader) {
  6. Log.d(TAG,"onImageAvailable");
  7. Image image = imageReader.acquireNextImage();
  8. int width = image.getWidth();
  9. int height = image.getHeight();
  10. int I420size = width * height*3/2;
  11. Log.d(TAG,"I420size:"+I420size);
  12. byte[] nv21 = new byte[I420size];
  13. YUVToNV21_NV12(image,nv21,image.getWidth(),image.getHeight(),"NV21");
  14. encodeVideo(nv21);
  15. image.close();
  16. }
  17. }, previewCaptureHandler);
  18. }

可以看到这个ImageReader是设置成了YUV_420_888的格式,我之前有想过直接设置成NV21的格式,毕竟有这个选项,但是最后证明这是不行的,只能老老实实将YUV_420_888通过逻辑装换成NV21的格式。

  1. private static void YUVToNV21_NV12(Image image, byte[] nv21, int w, int h, String type) {
  2. Image.Plane[] planes = image.getPlanes();
  3. int remaining0 = planes[0].getBuffer().remaining();
  4. int remaining1 = planes[1].getBuffer().remaining();
  5. int remaining2 = planes[2].getBuffer().remaining();
  6. //分别准备三个数组接收YUV分量。
  7. byte[] yRawSrcBytes = new byte[remaining0];
  8. byte[] uRawSrcBytes = new byte[remaining1];
  9. byte[] vRawSrcBytes = new byte[remaining2];
  10. planes[0].getBuffer().get(yRawSrcBytes);
  11. planes[1].getBuffer().get(uRawSrcBytes);
  12. planes[2].getBuffer().get(vRawSrcBytes);
  13. int j = 0, k = 0;
  14. boolean flag = type.equals("NV21");
  15. for (int i = 0; i < nv21.length; i++) {
  16. if (i < w * h) {
  17. //首先填充w*h个Y分量
  18. nv21[i] = yRawSrcBytes[i];
  19. } else {
  20. if (flag) {
  21. //若NV21类型 则Y分量分配完后第一个将是V分量
  22. nv21[i] = vRawSrcBytes[j];
  23. //PixelStride有用数据步长 = 1紧凑按顺序填充,=2每间隔一个填充数据
  24. j += planes[1].getPixelStride();
  25. } else {
  26. //若NV12类型 则Y分量分配完后第一个将是U分量
  27. nv21[i] = uRawSrcBytes[k];
  28. //PixelStride有用数据步长 = 1紧凑按顺序填充,=2每间隔一个填充数据
  29. k += planes[2].getPixelStride();
  30. }
  31. //紧接着可以交错UV或者VU排列不停的改变flag标志即可交错排列
  32. flag = !flag;
  33. }
  34. }
  35. }

具体的原理可以看看这篇博客

(51条消息) 【Android Camera2】彻底弄清图像数据YUV420_888转NV21问题/良心教学/避坑必读!_yuv_420_888_奔跑的鲁班七号的博客-CSDN博客

 转换成NV21之后我们需要做的就是将这些数据传进MediaCodec中,也就是那个encodeVideo方法。我们先不进行讲解,等到后面的MediaCodec再一起讲讲。

AudioRecord录制音频

这个在网上的资料还是比较多的,就那一套流程,创建初始化,把那些参数啥啥的设置后,之后就开始就行。

  1. private void initAudioRecord() {
  2. //这minBufferSize是代表设备能接受的最小buffersize
  3. //sample_rate是44100,最常见就是这个值
  4. AudioiMinBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
  5. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
  6. // TODO: Consider calling
  7. // ActivityCompat#requestPermissions
  8. // here to request the missing permissions, and then overriding
  9. // public void onRequestPermissionsResult(int requestCode, String[] permissions,
  10. // int[] grantResults)
  11. // to handle the case where the user grants the permission. See the documentation
  12. // for ActivityCompat#requestPermissions for more details.
  13. return;
  14. }
  15. //这下面的一些参数例如什么16BIT可以自行去了解一下,现阶段基本知道一下就行吧
  16. audioRecord = new AudioRecord.Builder()
  17. .setAudioFormat(new AudioFormat.Builder()
  18. .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
  19. .setSampleRate(SAMPLE_RATE)
  20. .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
  21. .setBufferSizeInBytes(AudioiMinBufferSize)
  22. .setAudioSource(MediaRecorder.AudioSource.MIC)
  23. .build();
  24. }

初始化之后,我们在开始录像的时候,便可以将起启动

  1. private void startAudioRecord() {
  2. audioRecordThread = new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. mIsAudioRecording = true;
  6. audioRecord.startRecording();
  7. while (mIsAudioRecording) {
  8. byte[] inputAudioData = new byte[AudioiMinBufferSize];
  9. int res = audioRecord.read(inputAudioData, 0, inputAudioData.length);
  10. if (res > 0) {
  11. //Log.d(TAG,res+"");
  12. if (AudioCodec!=null){
  13. if (captureListener!=null){
  14. captureListener.onCaptureListener(inputAudioData,res);
  15. }
  16. //callbackData(inputAudioData,inputAudioData.length);
  17. }
  18. }
  19. }
  20. }
  21. });
  22. audioRecordThread.start();
  23. }

inputAudioData就是每次录制出来的byte数据,这些数据是PCM编码格式的,我们要做的也是将其放入到专门的MediaCodec中 。而captureListener.onCaptureListener(inputAudioData,res)就是将pcm传入MediaCodec中。

MediaCodec接收数据,处理数据

视频流设置MediaCodec

1.初始化视频流的MediaCodec,并进行Configure

  1. private void initMediaCodec(int width, int height) {
  2. Log.d(TAG, "width:" + width);
  3. Log.d(TAG, "Height:" + height);
  4. try {
  5. //先拿到格式容器
  6. /*
  7. MediaFormat.createVideoFormat中的宽高参数,不能为奇数
  8. 过小或超过屏幕尺寸,也会出现这个错误
  9. */
  10. //这里面的长宽设置应该跟imagereader里面获得的image的长宽进行对齐
  11. MediaFormat videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1440, 1440);
  12. //设置色彩控件
  13. videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
  14. //设置码率,码率就是数据传输单位时间传递的数据位数
  15. videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500_000);
  16. //设置帧率
  17. videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
  18. //设置关键帧间隔
  19. videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
  20. videoFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,4000000);
  21. //创建MediaCodc,注意这个格式,视频编码一般都是使用这个type
  22. videoMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
  23. videoMediaCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
  24. } catch (IOException e) {
  25. throw new RuntimeException(e);
  26. }
  27. }

 2.将NV21数据传递进输入缓存区

  1. private void encodeVideo(byte[] nv21) {
  2. //输入
  3. int index = videoMediaCodec.dequeueInputBuffer(WAIT_TIME);
  4. //Log.d(TAG,"video encord video index:"+index);
  5. if (index >= 0) {
  6. ByteBuffer inputBuffer = videoMediaCodec.getInputBuffer(index);
  7. inputBuffer.clear();
  8. int remaining = inputBuffer.remaining();
  9. inputBuffer.put(nv21, 0, nv21.length);
  10. videoMediaCodec.queueInputBuffer(index, 0, nv21.length, (System.nanoTime()-nanoTime) / 1000, 0);
  11. }
  12. }

图解如下图 

3.从输出缓存区中取出已经处理好的数据

  1. private void encodeVideoH264() {
  2. MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
  3. //获取输出缓存区index索引
  4. int videobufferindex = videoMediaCodec.dequeueOutputBuffer(videoBufferInfo, 0);
  5. Log.d(TAG,"videobufferindex:"+videobufferindex);
  6. //这个状态无论有没有outputbuffer,只要第一次都会执行,可以在这里进行一些MediaMuxer的逻辑操作,例如加轨道
  7. if (videobufferindex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
  8. //添加轨道
  9. mVideoTrackIndex = mediaMuxer.addTrack(videoMediaCodec.getOutputFormat());
  10. Log.d(TAG,"mVideoTrackIndex:"+mVideoTrackIndex);
  11. if (mAudioTrackIndex != -1) {
  12. Log.d(TAG,"encodeVideoH264:mediaMuxer is Start");
  13. mediaMuxer.start();
  14. isMediaMuxerStarted+=1;
  15. setPCMListener();
  16. }
  17. } else {
  18. if (isMediaMuxerStarted>=0) {
  19. while (videobufferindex >= 0) {
  20. //获取输出数据成功
  21. ByteBuffer videoOutputBuffer = videoMediaCodec.getOutputBuffer(videobufferindex);
  22. Log.d(TAG,"Video mediaMuxer writeSampleData");
  23. mediaMuxer.writeSampleData(mVideoTrackIndex, videoOutputBuffer, videoBufferInfo);
  24. videoMediaCodec.releaseOutputBuffer(videobufferindex, false);
  25. videobufferindex=videoMediaCodec.dequeueOutputBuffer(videoBufferInfo,0);
  26. }
  27. }
  28. }
  29. }

音频流设置MediaCodec 

其实跟视频流的MediaCodec也是差不多的,我这边就不那么详细了

1.初始化音频流的MediaCodec,并进行Configure

  1. private void initAudioCodec() {
  2. MediaFormat format = MediaFormat.createAudioFormat(MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNEL_COUNT);
  3. format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
  4. format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
  5. format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 8192);
  6. try {
  7. //编码类型AAC格式
  8. AudioCodec = MediaCodec.createEncoderByType(MIMETYPE_AUDIO_AAC);
  9. AudioCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
  10. } catch (IOException e) {
  11. throw new RuntimeException(e);
  12. }
  13. }

2. 将PCM数据传递进输入缓存区

  1. private void callbackData(byte[] inputAudioData, int length) {
  2. //已经拿到AudioRecord的byte数据
  3. //准备将其放入到MediaCodc中
  4. int index = AudioCodec.dequeueInputBuffer(-1);
  5. if (index<0){
  6. return;
  7. }
  8. Log.d(TAG,"AudioCodec.dequeueInputBuffer:"+index);
  9. ByteBuffer[] inputBuffers = AudioCodec.getInputBuffers();
  10. ByteBuffer audioInputBuffer = inputBuffers[index];
  11. audioInputBuffer.clear();
  12. Log.d(TAG, "call back Data length:" + length);
  13. Log.d(TAG, "call back Data audioInputBuffer remain:" + audioInputBuffer.remaining());
  14. audioInputBuffer.put(inputAudioData);
  15. audioInputBuffer.limit(inputAudioData.length);
  16. presentationTimeUs += (long) (1.0 * length / (44100 * 2 * (16 / 8)) * 1000000.0);
  17. AudioCodec.queueInputBuffer(index, 0, inputAudioData.length, (System.nanoTime()-nanoTime)/1000, 0);
  18. }

 3.从输出缓存区中取出已经处理好的数据

  1. private void encodePCMToAC() {
  2. MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();
  3. //获得输出
  4. int audioBufferFlag = AudioCodec.dequeueOutputBuffer(audioBufferInfo, 0);
  5. Log.d(TAG, "CALL BACK DATA FLAG:" + audioBufferFlag);
  6. if (audioBufferFlag==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
  7. //這時候進行添加軌道
  8. mAudioTrackIndex=mediaMuxer.addTrack(AudioCodec.getOutputFormat());
  9. Log.d(TAG,"mAudioTrackIndex:"+mAudioTrackIndex);
  10. if (mVideoTrackIndex!=-1){
  11. Log.d(TAG,"encodecPCMToACC:mediaMuxer is Start");
  12. mediaMuxer.start();
  13. isMediaMuxerStarted+=1;
  14. //开始了再创建录音回调
  15. setPCMListener();
  16. }
  17. }else {
  18. Log.d(TAG,"isMediaMuxerStarted:"+isMediaMuxerStarted);
  19. if (isMediaMuxerStarted>=0){
  20. while (audioBufferFlag>=0){
  21. //获取输出数据成功
  22. ByteBuffer outputBuffer = AudioCodec.getOutputBuffer(audioBufferFlag);
  23. mediaMuxer.writeSampleData(mAudioTrackIndex,outputBuffer,audioBufferInfo);
  24. AudioCodec.releaseOutputBuffer(audioBufferFlag,false);
  25. audioBufferFlag = AudioCodec.dequeueOutputBuffer(audioBufferInfo, 0);
  26. }
  27. }
  28. }
  29. }

自此MediaCodec的输入输出数据便大体完成了。

我们现在拿到了H.264的视频数据以及AAC的音频数据 ,我们可以准备使用MediaMuxer进行合并最后导出MP4。

MediaMuxer合成MP4

我们先理一下整体的合成流程

  1. 首先我们需要初始化一下,设置一下参数啥的
  2. 一个MP4包括两个轨道,一个是视频轨道,一个是音频轨道,需要在合适的时候进行添加
  3. 添加好轨道之后,我们就可以启动
  4. 往里面写入编码后的数据
  5. 结束,生成MP4

因为MediaMuxer一般都是嵌套在MediaCodec取输出数据的逻辑中使用,所有我这边只会放一些核心的代码出来。

截至到目前为止所有代码都是节选的,咱们最主要是了解他的过程,已经重要的方法,整个流程的梳理,可以通过看最后的完整代码进行走通。

  1. private void initMediaMuxer() {
  2. String filename = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/" + getCurrentTime() + ".mp4";
  3. try {
  4. mediaMuxer = new MediaMuxer(filename, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
  5. mediaMuxer.setOrientationHint(90);
  6. } catch (IOException e) {
  7. throw new RuntimeException(e);
  8. }
  9. }

 添加轨道,启动

  1. mAudioTrackIndex=mediaMuxer.addTrack(AudioCodec.getOutputFormat());
  2. mVideoTrackIndex = mediaMuxer.addTrack(videoMediaCodec.getOutputFormat());
  3. mediaMuxer.start();

添加数据

  1. mediaMuxer.writeSampleData(mVideoTrackIndex, videoOutputBuffer, videoBufferInfo);
  2. mediaMuxer.writeSampleData(mAudioTrackIndex,outputBuffer,audioBufferInfo);

停止MedaiMuxer并释放

  1. private void stopMediaMuxer() {
  2. isMediaMuxerStarted=-1;
  3. mediaMuxer.stop();
  4. mediaMuxer.release();
  5. }

停止释放之后,MP4就基本上合成成功。

自此,大体的录像流程就这么多。

笔者也是过来人,知道这么讲其实会比较晕,还不如直接丢所有的源码出来,自己慢慢开。所以咱这次直接就全给丢出来。

在此之前,我先总结一下遇到的一些问题 

常见问题

dequeueInputBuffer或dequeueOutputBuffer为-1

首先这个问题得先知道这两个方法返回-1,意味着什么

dequeueInputBuffer返回-1是代表输入缓存区没有空闲的位子,给我们输入数据了。这里经过探究,一般缓存区最大就相当于长度为4的数组,如果一直没有释放掉,可能就会输入几帧数据之后一直放回-1。这个问题的排查解决方式就是。如果一开始就返回-1,那这种情况就比较棘手了,我也不知道怎么搞,没遇到过,看看是不是MediaCodec什么参数没有设置好导致的。

但是如果是走了几帧,后就一直返回-1。拿这时候就有一个比较明确的思路了,赶紧去看看,输出缓存区的数据有没有被处理释放掉,因为输出缓存区的数据一直不去拿去处理释放,输入这边的也就不会释放,所以都是相连的,我之前一直卡着我的就是这个原因导致的 。

而如果dequeueOutputBuffer一直返回-1的话,这时候就代表一直无法可用的输出数据,这时候可以优先去排查一下输入数据有没有输入成功。

录制出来视频绿屏

这个主要就是和长宽之类的参数有关了,从Imagereader到MediaCodec的长宽设置一定要一致,否则极易绿屏,还有可能是YUV转NV21的时候有问题,可以着重排查一下这两个地方。

源码展示

布局文件

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context=".MainActivity"
  8. android:orientation="vertical"
  9. android:keepScreenOn="true">
  10. <TextureView
  11. android:id="@+id/previewtexture"
  12. android:layout_weight="1"
  13. android:layout_width="720dp"
  14. android:layout_height="720dp"/>
  15. <Button
  16. android:id="@+id/shutterclick"
  17. android:layout_margin="20dp"
  18. android:layout_width="match_parent"
  19. android:layout_height="50dp"
  20. android:text="点击录像"/>
  21. </LinearLayout>

逻辑文件 

  1. package com.example.newwaytorecordcameraapplication;
  2. import static android.media.MediaFormat.MIMETYPE_AUDIO_AAC;
  3. import androidx.annotation.NonNull;
  4. import androidx.appcompat.app.AppCompatActivity;
  5. import androidx.core.app.ActivityCompat;
  6. import android.Manifest;
  7. import android.content.Context;
  8. import android.content.pm.PackageManager;
  9. import android.graphics.ImageFormat;
  10. import android.graphics.SurfaceTexture;
  11. import android.hardware.camera2.CameraAccessException;
  12. import android.hardware.camera2.CameraCaptureSession;
  13. import android.hardware.camera2.CameraCharacteristics;
  14. import android.hardware.camera2.CameraDevice;
  15. import android.hardware.camera2.CameraManager;
  16. import android.hardware.camera2.CaptureRequest;
  17. import android.hardware.camera2.TotalCaptureResult;
  18. import android.hardware.camera2.params.StreamConfigurationMap;
  19. import android.media.AudioFormat;
  20. import android.media.AudioRecord;
  21. import android.media.Image;
  22. import android.media.ImageReader;
  23. import android.media.MediaCodec;
  24. import android.media.MediaCodecInfo;
  25. import android.media.MediaFormat;
  26. import android.media.MediaMuxer;
  27. import android.media.MediaRecorder;
  28. import android.os.Bundle;
  29. import android.os.Environment;
  30. import android.os.Handler;
  31. import android.os.HandlerThread;
  32. import android.os.Message;
  33. import android.os.SystemClock;
  34. import android.provider.MediaStore;
  35. import android.util.Log;
  36. import android.util.Size;
  37. import android.view.Surface;
  38. import android.view.TextureView;
  39. import android.view.View;
  40. import android.widget.Button;
  41. import android.widget.Toast;
  42. import java.io.IOException;
  43. import java.nio.ByteBuffer;
  44. import java.text.DateFormat;
  45. import java.text.SimpleDateFormat;
  46. import java.util.ArrayList;
  47. import java.util.Arrays;
  48. import java.util.Collections;
  49. import java.util.Date;
  50. import java.util.List;
  51. import java.util.concurrent.atomic.AtomicBoolean;
  52. public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  53. private TextureView previewTextureView;
  54. private Button shutterClick;
  55. private Size previewSize;
  56. private static final String TAG = "VideoRecord";
  57. private String mCameraId;
  58. private Handler cameraHandler;
  59. private CameraCaptureSession previewCaptureSession;
  60. private MediaCodec videoMediaCodec;
  61. private AudioRecord audioRecord;
  62. private boolean isRecordingVideo = false;
  63. private int mWidth;
  64. private int mHeight;
  65. private ImageReader previewImageReader;
  66. private Handler previewCaptureHandler;
  67. private CaptureRequest.Builder videoRequest;
  68. private static final int WAIT_TIME = 0;
  69. private static final int SAMPLE_RATE = 44100;
  70. private long nanoTime;
  71. private int AudioiMinBufferSize;
  72. private boolean mIsAudioRecording = false;
  73. private static final int CHANNEL_COUNT = 2;
  74. private MediaCodec AudioCodec;
  75. private static final int BIT_RATE = 96000;
  76. private MediaMuxer mediaMuxer;
  77. private volatile int mAudioTrackIndex = -1;
  78. private volatile int mVideoTrackIndex = -1;
  79. private volatile int isMediaMuxerStarted = -1;
  80. private Thread audioRecordThread;
  81. private Thread VideoCodecThread;
  82. private Thread AudioCodecThread;
  83. private long presentationTimeUs;
  84. private AudioCaptureListener captureListener;
  85. private volatile int isStop=0;
  86. CaptureRequest.Builder previewCaptureRequestBuilder;
  87. private volatile int videoMediaCodecIsStoped=0;
  88. private volatile int AudioCodecIsStoped=0;
  89. @Override
  90. protected void onCreate(Bundle savedInstanceState) {
  91. super.onCreate(savedInstanceState);
  92. setContentView(R.layout.activity_main);
  93. initView();
  94. }
  95. //1.初始化MediaCodec视频编码
  96. //设置mediaformat,到时候放到MediaRecodc里面
  97. private void initMediaCodec(int width, int height) {
  98. Log.d(TAG, "width:" + width);
  99. Log.d(TAG, "Height:" + height);
  100. try {
  101. //先拿到格式容器
  102. /*
  103. MediaFormat.createVideoFormat中的宽高参数,不能为奇数
  104. 过小或超过屏幕尺寸,也会出现这个错误
  105. */
  106. MediaFormat videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1440, 1440);
  107. //设置色彩控件
  108. videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
  109. //设置码率,码率就是数据传输单位时间传递的数据位数
  110. videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500_000);
  111. //设置帧率
  112. videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
  113. //设置关键帧间隔
  114. videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
  115. videoFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,4000000);
  116. //创建MediaCodc
  117. videoMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
  118. videoMediaCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
  119. } catch (IOException e) {
  120. throw new RuntimeException(e);
  121. }
  122. }
  123. private void initView() {
  124. previewTextureView = findViewById(R.id.previewtexture);
  125. shutterClick = findViewById(R.id.shutterclick);
  126. shutterClick.setOnClickListener(this);
  127. }
  128. @Override
  129. protected void onResume() {
  130. super.onResume();
  131. previewTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
  132. @Override
  133. public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
  134. Log.d(TAG, "onSurfaceTextureAvailable");
  135. setupCamera(width, height);
  136. }
  137. @Override
  138. public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
  139. }
  140. @Override
  141. public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
  142. return false;
  143. }
  144. @Override
  145. public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
  146. }
  147. });
  148. HandlerThread videoRecordThread = new HandlerThread("VideoRecordThread");
  149. videoRecordThread.start();
  150. cameraHandler = new Handler(videoRecordThread.getLooper()) {
  151. @Override
  152. public void handleMessage(@NonNull Message msg) {
  153. super.handleMessage(msg);
  154. }
  155. };
  156. HandlerThread preivewImageReaderThread = new HandlerThread("preivewImageReaderThread");
  157. preivewImageReaderThread.start();
  158. previewCaptureHandler = new Handler(preivewImageReaderThread.getLooper()) {
  159. @Override
  160. public void handleMessage(@NonNull Message msg) {
  161. super.handleMessage(msg);
  162. }
  163. };
  164. }
  165. //设置预览的ImageReader
  166. private void setPreviewImageReader() {
  167. previewImageReader = ImageReader.newInstance(previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 1);
  168. previewImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
  169. @Override
  170. public void onImageAvailable(ImageReader imageReader) {
  171. Log.d(TAG,"onImageAvailable");
  172. Image image = imageReader.acquireNextImage();
  173. int width = image.getWidth();
  174. int height = image.getHeight();
  175. int I420size = width * height*3/2;
  176. Log.d(TAG,"I420size:"+I420size);
  177. byte[] nv21 = new byte[I420size];
  178. YUVToNV21_NV12(image,nv21,image.getWidth(),image.getHeight(),"NV21");
  179. encodeVideo(nv21);
  180. image.close();
  181. }
  182. }, previewCaptureHandler);
  183. }
  184. private static byte[] YUV_420_888toNV21(Image image) {
  185. int width = image.getWidth();
  186. int height = image.getHeight();
  187. Log.d(TAG,"image.getWidth():"+image.getWidth());
  188. Log.d(TAG,"image.getHeight()"+image.getHeight());
  189. ByteBuffer yBuffer = getBufferWithoutPadding(image.getPlanes()[0].getBuffer(), image.getWidth(), image.getPlanes()[0].getRowStride(),image.getHeight(),false);
  190. ByteBuffer vBuffer;
  191. //part1 获得真正的消除padding的ybuffer和ubuffer。需要对P格式和SP格式做不同的处理。如果是P格式的话只能逐像素去做,性能会降低。
  192. if(image.getPlanes()[2].getPixelStride()==1){ //如果为true,说明是P格式。
  193. vBuffer = getuvBufferWithoutPaddingP(image.getPlanes()[1].getBuffer(), image.getPlanes()[2].getBuffer(),
  194. width,height,image.getPlanes()[1].getRowStride(),image.getPlanes()[1].getPixelStride());
  195. }else{
  196. vBuffer = getBufferWithoutPadding(image.getPlanes()[2].getBuffer(), image.getWidth(), image.getPlanes()[2].getRowStride(),image.getHeight()/2,true);
  197. }
  198. //part2 将y数据和uv的交替数据(除去最后一个v值)赋值给nv21
  199. int ySize = yBuffer.remaining();
  200. int vSize = vBuffer.remaining();
  201. byte[] nv21;
  202. int byteSize = width*height*3/2;
  203. nv21 = new byte[byteSize];
  204. yBuffer.get(nv21, 0, ySize);
  205. vBuffer.get(nv21, ySize, vSize);
  206. //part3 最后一个像素值的u值是缺失的,因此需要从u平面取一下。
  207. ByteBuffer uPlane = image.getPlanes()[1].getBuffer();
  208. byte lastValue = uPlane.get(uPlane.capacity() - 1);
  209. nv21[byteSize - 1] = lastValue;
  210. return nv21;
  211. }
  212. //Semi-Planar格式(SP)的处理和y通道的数据
  213. private static ByteBuffer getBufferWithoutPadding(ByteBuffer buffer, int width, int rowStride, int times,boolean isVbuffer){
  214. if(width == rowStride) return buffer; //没有buffer,不用处理。
  215. int bufferPos = buffer.position();
  216. int cap = buffer.capacity();
  217. byte []byteArray = new byte[times*width];
  218. int pos = 0;
  219. //对于y平面,要逐行赋值的次数就是height次。对于uv交替的平面,赋值的次数是height/2次
  220. for (int i=0;i<times;i++) {
  221. buffer.position(bufferPos);
  222. //part 1.1 对于u,v通道,会缺失最后一个像u值或者v值,因此需要特殊处理,否则会crash
  223. if(isVbuffer && i==times-1){
  224. width = width -1;
  225. }
  226. buffer.get(byteArray, pos, width);
  227. bufferPos+= rowStride;
  228. pos = pos+width;
  229. }
  230. //nv21数组转成buffer并返回
  231. ByteBuffer bufferWithoutPaddings=ByteBuffer.allocate(byteArray.length);
  232. // 数组放到buffer中
  233. bufferWithoutPaddings.put(byteArray);
  234. //重置 limit 和postion 值否则 buffer 读取数据不对
  235. bufferWithoutPaddings.flip();
  236. return bufferWithoutPaddings;
  237. }
  238. //Planar格式(P)的处理
  239. private static ByteBuffer getuvBufferWithoutPaddingP(ByteBuffer uBuffer,ByteBuffer vBuffer, int width, int height, int rowStride, int pixelStride){
  240. int pos = 0;
  241. byte []byteArray = new byte[height*width/2];
  242. for (int row=0; row<height/2; row++) {
  243. for (int col=0; col<width/2; col++) {
  244. int vuPos = col*pixelStride + row*rowStride;
  245. byteArray[pos++] = vBuffer.get(vuPos);
  246. byteArray[pos++] = uBuffer.get(vuPos);
  247. }
  248. }
  249. ByteBuffer bufferWithoutPaddings=ByteBuffer.allocate(byteArray.length);
  250. // 数组放到buffer中
  251. bufferWithoutPaddings.put(byteArray);
  252. //重置 limit 和postion 值否则 buffer 读取数据不对
  253. bufferWithoutPaddings.flip();
  254. return bufferWithoutPaddings;
  255. }
  256. private void encodeVideo(byte[] nv21) {
  257. //输入
  258. int index = videoMediaCodec.dequeueInputBuffer(WAIT_TIME);
  259. //Log.d(TAG,"video encord video index:"+index);
  260. if (index >= 0) {
  261. ByteBuffer inputBuffer = videoMediaCodec.getInputBuffer(index);
  262. inputBuffer.clear();
  263. int remaining = inputBuffer.remaining();
  264. inputBuffer.put(nv21, 0, nv21.length);
  265. videoMediaCodec.queueInputBuffer(index, 0, nv21.length, (System.nanoTime()-nanoTime) / 1000, 0);
  266. }
  267. }
  268. private void encodeVideoH264() {
  269. MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
  270. int videobufferindex = videoMediaCodec.dequeueOutputBuffer(videoBufferInfo, 0);
  271. Log.d(TAG,"videobufferindex:"+videobufferindex);
  272. if (videobufferindex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
  273. //添加轨道
  274. mVideoTrackIndex = mediaMuxer.addTrack(videoMediaCodec.getOutputFormat());
  275. Log.d(TAG,"mVideoTrackIndex:"+mVideoTrackIndex);
  276. if (mAudioTrackIndex != -1) {
  277. Log.d(TAG,"encodeVideoH264:mediaMuxer is Start");
  278. mediaMuxer.start();
  279. isMediaMuxerStarted+=1;
  280. setPCMListener();
  281. }
  282. } else {
  283. if (isMediaMuxerStarted>=0) {
  284. while (videobufferindex >= 0) {
  285. //获取输出数据成功
  286. ByteBuffer videoOutputBuffer = videoMediaCodec.getOutputBuffer(videobufferindex);
  287. Log.d(TAG,"Video mediaMuxer writeSampleData");
  288. mediaMuxer.writeSampleData(mVideoTrackIndex, videoOutputBuffer, videoBufferInfo);
  289. videoMediaCodec.releaseOutputBuffer(videobufferindex, false);
  290. videobufferindex=videoMediaCodec.dequeueOutputBuffer(videoBufferInfo,0);
  291. }
  292. }
  293. }
  294. }
  295. private void encodePCMToAC() {
  296. MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();
  297. //获得输出
  298. int audioBufferFlag = AudioCodec.dequeueOutputBuffer(audioBufferInfo, 0);
  299. Log.d(TAG, "CALL BACK DATA FLAG:" + audioBufferFlag);
  300. if (audioBufferFlag==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
  301. //這時候進行添加軌道
  302. mAudioTrackIndex=mediaMuxer.addTrack(AudioCodec.getOutputFormat());
  303. Log.d(TAG,"mAudioTrackIndex:"+mAudioTrackIndex);
  304. if (mVideoTrackIndex!=-1){
  305. Log.d(TAG,"encodecPCMToACC:mediaMuxer is Start");
  306. mediaMuxer.start();
  307. isMediaMuxerStarted+=1;
  308. //开始了再创建录音回调
  309. setPCMListener();
  310. }
  311. }else {
  312. Log.d(TAG,"isMediaMuxerStarted:"+isMediaMuxerStarted);
  313. if (isMediaMuxerStarted>=0){
  314. while (audioBufferFlag>=0){
  315. ByteBuffer outputBuffer = AudioCodec.getOutputBuffer(audioBufferFlag);
  316. mediaMuxer.writeSampleData(mAudioTrackIndex,outputBuffer,audioBufferInfo);
  317. AudioCodec.releaseOutputBuffer(audioBufferFlag,false);
  318. audioBufferFlag = AudioCodec.dequeueOutputBuffer(audioBufferInfo, 0);
  319. }
  320. }
  321. }
  322. }
  323. private void setPCMListener() {
  324. setCaptureListener(new AudioCaptureListener() {
  325. @Override
  326. public void onCaptureListener(byte[] audioSource, int audioReadSize) {
  327. callbackData(audioSource,audioReadSize);
  328. }
  329. });
  330. }
  331. private static void YUVToNV21_NV12(Image image, byte[] nv21, int w, int h, String type) {
  332. Image.Plane[] planes = image.getPlanes();
  333. int remaining0 = planes[0].getBuffer().remaining();
  334. int remaining1 = planes[1].getBuffer().remaining();
  335. int remaining2 = planes[2].getBuffer().remaining();
  336. //分别准备三个数组接收YUV分量。
  337. byte[] yRawSrcBytes = new byte[remaining0];
  338. byte[] uRawSrcBytes = new byte[remaining1];
  339. byte[] vRawSrcBytes = new byte[remaining2];
  340. planes[0].getBuffer().get(yRawSrcBytes);
  341. planes[1].getBuffer().get(uRawSrcBytes);
  342. planes[2].getBuffer().get(vRawSrcBytes);
  343. int j = 0, k = 0;
  344. boolean flag = type.equals("NV21");
  345. for (int i = 0; i < nv21.length; i++) {
  346. if (i < w * h) {
  347. //首先填充w*h个Y分量
  348. nv21[i] = yRawSrcBytes[i];
  349. } else {
  350. if (flag) {
  351. //若NV21类型 则Y分量分配完后第一个将是V分量
  352. nv21[i] = vRawSrcBytes[j];
  353. //PixelStride有用数据步长 = 1紧凑按顺序填充,=2每间隔一个填充数据
  354. j += planes[1].getPixelStride();
  355. } else {
  356. //若NV12类型 则Y分量分配完后第一个将是U分量
  357. nv21[i] = uRawSrcBytes[k];
  358. //PixelStride有用数据步长 = 1紧凑按顺序填充,=2每间隔一个填充数据
  359. k += planes[2].getPixelStride();
  360. }
  361. //紧接着可以交错UV或者VU排列不停的改变flag标志即可交错排列
  362. flag = !flag;
  363. }
  364. }
  365. }
  366. private void setupCamera(int width, int height) {
  367. CameraManager cameraManage = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  368. try {
  369. String[] cameraIdList = cameraManage.getCameraIdList();
  370. for (String cameraId : cameraIdList) {
  371. CameraCharacteristics cameraCharacteristics = cameraManage.getCameraCharacteristics(cameraId);
  372. //demo就就简单写写后摄录像
  373. if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) != CameraCharacteristics.LENS_FACING_BACK) {
  374. //表示匹配到前摄,直接跳过这次循环
  375. continue;
  376. }
  377. StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  378. Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
  379. Size size = new Size(1440, 1440);
  380. previewSize = size;
  381. mWidth = previewSize.getWidth();
  382. mHeight = previewSize.getHeight();
  383. mCameraId = cameraId;
  384. }
  385. } catch (CameraAccessException e) {
  386. throw new RuntimeException(e);
  387. }
  388. openCamera();
  389. }
  390. private void initAudioRecord() {
  391. AudioiMinBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
  392. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
  393. // TODO: Consider calling
  394. // ActivityCompat#requestPermissions
  395. // here to request the missing permissions, and then overriding
  396. // public void onRequestPermissionsResult(int requestCode, String[] permissions,
  397. // int[] grantResults)
  398. // to handle the case where the user grants the permission. See the documentation
  399. // for ActivityCompat#requestPermissions for more details.
  400. return;
  401. }
  402. audioRecord = new AudioRecord.Builder()
  403. .setAudioFormat(new AudioFormat.Builder()
  404. .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
  405. .setSampleRate(SAMPLE_RATE)
  406. .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
  407. .setBufferSizeInBytes(AudioiMinBufferSize)
  408. .setAudioSource(MediaRecorder.AudioSource.MIC)
  409. .build();
  410. }
  411. private void openCamera() {
  412. Log.d(TAG, "openCamera: success");
  413. CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  414. try {
  415. if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
  416. // TODO: Consider calling
  417. // ActivityCompat#requestPermissions
  418. // here to request the missing permissions, and then overriding
  419. // public void onRequestPermissionsResult(int requestCode, String[] permissions,
  420. // int[] grantResults)
  421. // to handle the case where the user grants the permission. See the documentation
  422. // for ActivityCompat#requestPermissions for more details.
  423. return;
  424. }
  425. cameraManager.openCamera(mCameraId, stateCallback, cameraHandler);
  426. } catch (CameraAccessException e) {
  427. e.printStackTrace();
  428. }
  429. }
  430. private CameraDevice mCameraDevice;
  431. private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
  432. @Override
  433. public void onOpened(@NonNull CameraDevice cameraDevice) {
  434. Log.d(TAG, "onOpen");
  435. mCameraDevice = cameraDevice;
  436. startPreview(mCameraDevice);
  437. }
  438. @Override
  439. public void onDisconnected(@NonNull CameraDevice cameraDevice) {
  440. Log.d(TAG, "onDisconnected");
  441. }
  442. @Override
  443. public void onError(@NonNull CameraDevice cameraDevice, int i) {
  444. Log.d(TAG, "onError");
  445. }
  446. };
  447. private void startPreview(CameraDevice mCameraDevice) {
  448. try {
  449. Log.d(TAG, "startPreview");
  450. setPreviewImageReader();
  451. previewCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  452. SurfaceTexture previewSurfaceTexture = previewTextureView.getSurfaceTexture();
  453. previewSurfaceTexture.setDefaultBufferSize(mWidth, mHeight);
  454. Surface previewSurface = new Surface(previewSurfaceTexture);
  455. Surface previewImageReaderSurface = previewImageReader.getSurface();
  456. previewCaptureRequestBuilder.addTarget(previewSurface);
  457. mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, previewImageReaderSurface), new CameraCaptureSession.StateCallback() {
  458. @Override
  459. public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
  460. Log.d(TAG, "onConfigured");
  461. previewCaptureSession = cameraCaptureSession;
  462. try {
  463. cameraCaptureSession.setRepeatingRequest(previewCaptureRequestBuilder.build(), cameraPreviewCallback, cameraHandler);
  464. } catch (CameraAccessException e) {
  465. throw new RuntimeException(e);
  466. }
  467. }
  468. @Override
  469. public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
  470. }
  471. }, cameraHandler);
  472. } catch (CameraAccessException e) {
  473. throw new RuntimeException(e);
  474. }
  475. }
  476. private volatile boolean videoIsReadyToStop=false;
  477. private CameraCaptureSession.CaptureCallback cameraPreviewCallback=new CameraCaptureSession.CaptureCallback() {
  478. @Override
  479. public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
  480. super.onCaptureCompleted(session, request, result);
  481. if(videoIsReadyToStop){
  482. try {
  483. Thread.sleep(1000);
  484. } catch (InterruptedException e) {
  485. throw new RuntimeException(e);
  486. }
  487. videoIsReadyToStop=false;
  488. stopMediaCodecThread();
  489. }
  490. }
  491. };
  492. private Size getOptimalSize(Size[] sizes, int width, int height) {
  493. Size tempSize = new Size(width, height);
  494. List<Size> adaptSize = new ArrayList<>();
  495. for (Size size : sizes) {
  496. if (width > height) {
  497. //横屏的时候看,或是平板形式
  498. if (size.getHeight() > height && size.getWidth() > width) {
  499. adaptSize.add(size);
  500. }
  501. } else {
  502. //竖屏的时候
  503. if (size.getWidth() > height && size.getHeight() > width) {
  504. adaptSize.add(size);
  505. }
  506. }
  507. }
  508. if (adaptSize.size() > 0) {
  509. tempSize = adaptSize.get(0);
  510. int minnum = 999999;
  511. for (Size size : adaptSize) {
  512. int num = size.getHeight() * size.getHeight() - width * height;
  513. if (num < minnum) {
  514. minnum = num;
  515. tempSize = size;
  516. }
  517. }
  518. }
  519. return tempSize;
  520. }
  521. @Override
  522. public void onClick(View view) {
  523. switch (view.getId()) {
  524. case R.id.shutterclick:
  525. if (isRecordingVideo) {
  526. //stop recording video
  527. isRecordingVideo = false;
  528. //开始停止录像
  529. Log.d(TAG,"Stop recording video");
  530. stopRecordingVideo();
  531. } else {
  532. isRecordingVideo = true;
  533. //开始录像
  534. startRecording();
  535. }
  536. break;
  537. }
  538. }
  539. private void stopRecordingVideo() {
  540. stopVideoSession();
  541. stopAudioRecord();
  542. }
  543. private void stopMediaMuxer() {
  544. isMediaMuxerStarted=-1;
  545. mediaMuxer.stop();
  546. mediaMuxer.release();
  547. }
  548. private void stopMediaCodecThread() {
  549. isStop=2;
  550. AudioCodecThread=null;
  551. VideoCodecThread=null;
  552. }
  553. private void stopMediaCodec() {
  554. AudioCodec.stop();
  555. AudioCodec.release();
  556. videoMediaCodec.stop();
  557. videoMediaCodec.release();
  558. }
  559. private void stopVideoSession() {
  560. if (previewCaptureSession!=null){
  561. try {
  562. previewCaptureSession.stopRepeating();
  563. videoIsReadyToStop=true;
  564. } catch (CameraAccessException e) {
  565. throw new RuntimeException(e);
  566. }
  567. }
  568. try {
  569. previewCaptureSession.setRepeatingRequest(previewCaptureRequestBuilder.build(), cameraPreviewCallback, cameraHandler);
  570. } catch (CameraAccessException e) {
  571. throw new RuntimeException(e);
  572. }
  573. }
  574. private void stopAudioRecord() {
  575. mIsAudioRecording=false;
  576. audioRecord.stop();
  577. audioRecord.release();
  578. audioRecord=null;
  579. audioRecordThread=null;
  580. }
  581. private void startRecording() {
  582. isStop=1;
  583. nanoTime = System.nanoTime();
  584. initMediaMuxer();
  585. initAudioRecord();
  586. initMediaCodec(mWidth, mHeight);
  587. initAudioCodec();
  588. //將MediaCodec分成獨立的線程
  589. initMediaCodecThread();
  590. //啟動MediaCodec以及线程
  591. startMediaCodec();
  592. //开启录像session
  593. startVideoSession();
  594. //音頻開始錄製
  595. startAudioRecord();
  596. }
  597. private void startMediaCodec() {
  598. if (AudioCodec!=null){
  599. AudioCodecIsStoped=0;
  600. AudioCodec.start();
  601. }
  602. if (videoMediaCodec!=null){
  603. videoMediaCodecIsStoped=0;
  604. videoMediaCodec.start();
  605. }
  606. if (VideoCodecThread!=null){
  607. VideoCodecThread.start();
  608. }
  609. if (AudioCodecThread!=null){
  610. AudioCodecThread.start();
  611. }
  612. }
  613. private void initMediaCodecThread() {
  614. VideoCodecThread=new Thread(new Runnable() {
  615. @Override
  616. public void run() {
  617. //输出为H264
  618. while (true){
  619. if (isStop==2){
  620. Log.d(TAG,"videoMediaCodec is stopping");
  621. break;
  622. }
  623. encodeVideoH264();
  624. }
  625. videoMediaCodec.stop();
  626. videoMediaCodec.release();
  627. videoMediaCodecIsStoped=1;
  628. if (AudioCodecIsStoped==1){
  629. stopMediaMuxer();
  630. }
  631. }
  632. });
  633. AudioCodecThread=new Thread(new Runnable() {
  634. @Override
  635. public void run() {
  636. while (true){
  637. if (isStop==2){
  638. Log.d(TAG,"AudioCodec is stopping");
  639. break;
  640. }
  641. encodePCMToAC();
  642. }
  643. AudioCodec.stop();
  644. AudioCodec.release();
  645. AudioCodecIsStoped=1;
  646. if (videoMediaCodecIsStoped==1){
  647. stopMediaMuxer();
  648. }
  649. }
  650. });
  651. }
  652. private void startAudioRecord() {
  653. audioRecordThread = new Thread(new Runnable() {
  654. @Override
  655. public void run() {
  656. mIsAudioRecording = true;
  657. audioRecord.startRecording();
  658. while (mIsAudioRecording) {
  659. byte[] inputAudioData = new byte[AudioiMinBufferSize];
  660. int res = audioRecord.read(inputAudioData, 0, inputAudioData.length);
  661. if (res > 0) {
  662. //Log.d(TAG,res+"");
  663. if (AudioCodec!=null){
  664. if (captureListener!=null){
  665. captureListener.onCaptureListener(inputAudioData,res);
  666. }
  667. //callbackData(inputAudioData,inputAudioData.length);
  668. }
  669. }
  670. }
  671. }
  672. });
  673. audioRecordThread.start();
  674. }
  675. private void initMediaMuxer() {
  676. String filename = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/" + getCurrentTime() + ".mp4";
  677. try {
  678. mediaMuxer = new MediaMuxer(filename, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
  679. mediaMuxer.setOrientationHint(90);
  680. } catch (IOException e) {
  681. throw new RuntimeException(e);
  682. }
  683. }
  684. public String getCurrentTime() {
  685. Date date = new Date(System.currentTimeMillis());
  686. DateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
  687. return dateFormat.format(date);
  688. }
  689. //初始化Audio MediaCodec
  690. private void initAudioCodec() {
  691. MediaFormat format = MediaFormat.createAudioFormat(MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNEL_COUNT);
  692. format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
  693. format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
  694. format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 8192);
  695. try {
  696. AudioCodec = MediaCodec.createEncoderByType(MIMETYPE_AUDIO_AAC);
  697. AudioCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
  698. } catch (IOException e) {
  699. throw new RuntimeException(e);
  700. }
  701. }
  702. private void callbackData(byte[] inputAudioData, int length) {
  703. //已经拿到AudioRecord的byte数据
  704. //准备将其放入到MediaCodc中
  705. int index = AudioCodec.dequeueInputBuffer(-1);
  706. if (index<0){
  707. return;
  708. }
  709. Log.d(TAG,"AudioCodec.dequeueInputBuffer:"+index);
  710. ByteBuffer[] inputBuffers = AudioCodec.getInputBuffers();
  711. ByteBuffer audioInputBuffer = inputBuffers[index];
  712. audioInputBuffer.clear();
  713. Log.d(TAG, "call back Data length:" + length);
  714. Log.d(TAG, "call back Data audioInputBuffer remain:" + audioInputBuffer.remaining());
  715. audioInputBuffer.put(inputAudioData);
  716. audioInputBuffer.limit(inputAudioData.length);
  717. presentationTimeUs += (long) (1.0 * length / (44100 * 2 * (16 / 8)) * 1000000.0);
  718. AudioCodec.queueInputBuffer(index, 0, inputAudioData.length, (System.nanoTime()-nanoTime)/1000, 0);
  719. }
  720. /*private void getEncordData() {
  721. MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
  722. //获得输出
  723. int flag = AudioCodec.dequeueOutputBuffer(outputBufferInfo, 0);
  724. Log.d(TAG, "CALL BACK DATA FLAG:" + flag);
  725. if (flag == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
  726. //第一次都会执行这个
  727. //这时候可以加轨道到MediaMuxer中,但是先不要启动,等到两个轨道都加好再start
  728. mAudioTrackIndex = mediaMuxer.addTrack(AudioCodec.getOutputFormat());
  729. if (mAudioTrackIndex != -1 && mVideoTrackIndex != -1) {
  730. mediaMuxer.start();
  731. Log.d(TAG, "AudioMediaCodec start mediaMuxer");
  732. isMediaMuxerStarted = true;
  733. }
  734. } else {
  735. if (isMediaMuxerStarted) {
  736. if (flag >= 0) {
  737. if (mAudioTrackIndex != -1) {
  738. Log.d(TAG, "AudioCodec.getOutputBuffer:");
  739. ByteBuffer outputBuffer = AudioCodec.getOutputBuffer(flag);
  740. mediaMuxer.writeSampleData(mAudioTrackIndex, outputBuffer, outputBufferInfo);
  741. AudioCodec.releaseOutputBuffer(flag, false);
  742. }
  743. }
  744. }
  745. }
  746. }*/
  747. private void startVideoSession() {
  748. Log.d(TAG, "startVideoSession");
  749. if (previewCaptureSession!=null){
  750. try {
  751. previewCaptureSession.stopRepeating();
  752. } catch (CameraAccessException e) {
  753. throw new RuntimeException(e);
  754. }
  755. }
  756. SurfaceTexture previewSurfaceTexture = previewTextureView.getSurfaceTexture();
  757. Surface previewSurface = new Surface(previewSurfaceTexture);
  758. Surface previewImageReaderSurface = previewImageReader.getSurface();
  759. try {
  760. videoRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
  761. videoRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
  762. videoRequest.addTarget(previewSurface);
  763. videoRequest.addTarget(previewImageReaderSurface);
  764. previewCaptureSession.setRepeatingRequest(videoRequest.build(), videosessioncallback, cameraHandler);
  765. } catch (CameraAccessException e) {
  766. throw new RuntimeException(e);
  767. }
  768. }
  769. private CameraCaptureSession.CaptureCallback videosessioncallback = new CameraCaptureSession.CaptureCallback() {
  770. @Override
  771. public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
  772. super.onCaptureCompleted(session, request, result);
  773. }
  774. };
  775. public interface AudioCaptureListener {
  776. /**
  777. * 音频采集回调数据源
  778. *
  779. * @param audioSource :音频采集回调数据源
  780. * @param audioReadSize :每次读取数据的大小
  781. */
  782. void onCaptureListener(byte[] audioSource,int audioReadSize);
  783. }
  784. public AudioCaptureListener getCaptureListener() {
  785. return captureListener;
  786. }
  787. public void setCaptureListener(AudioCaptureListener captureListener) {
  788. this.captureListener = captureListener;
  789. }
  790. }

 自此一个基本功能就完成了。

但是我这边留了一些问题,没有解决,因为不是这个demo的重点。首先就是录像结束后第二次点击录像会闪退。

其次音频时间戳有点对不上,导致有点变声。

各位观众可以后续研究一下。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号