完成音视频的VR自行车数据采集地址采集、编码、封包成 mp4 输出

音频采集:AudioRecord

VR自行车数据采集地址vip7.maltapi.com

视频采集:Camera预览回调YUV数据

编码:MediaCodec

合成封包MP4:MediaMuxer


首先确定几条线程处理任务

1.audioThread 音频采集和编码

2.videoThread 视频编码

3.muxerThread 合成


示例代码:Kotlin


所有详细代码已上传github,后面会给出地址,示例Activity是Camera1PreviewActivity


代码中少了一些验证,比如设备支持预览的格式,这在之前的文章提到过,要注意自己的设备是否支持该设置。


在最后,会写出容易出现的问题,代码运行不正确的时候,可以对照下,是否

犯了这些错误


1.初始化和打开相机


预览界面用的SurfaceView,通过前面的学习应该知道相机预览,就不多说


  private fun initView() {

        surfaceView = findViewById(com.example.mediastudyproject.R.id.surface_view)

        surfaceView.holder.addCallback(object : SurfaceHolder.Callback2 {

            override fun surfaceRedrawNeeded(holder: SurfaceHolder?) {

            }


            override fun surfaceChanged(

                holder: SurfaceHolder?,

                format: Int,

                width: Int,

                height: Int

            ) {

                isSurfaceAvailiable = true

                this@Camera1PreviewActivity.holder = holder

            }


            override fun surfaceDestroyed(holder: SurfaceHolder?) {

                isSurfaceAvailiable = false

                mCamera?.stopPreview()

                //这里要把之前设置的预览回调取消,不然关闭app,camera释放了,但是还在回调,会报异常

                mCamera?.setPreviewCallback(null)

                mCamera?.release()

                mCamera = null

            }


            override fun surfaceCreated(holder: SurfaceHolder?) {

                isSurfaceAvailiable = true

                this@Camera1PreviewActivity.holder = holder

                thread {

                //打开相机

                    openCamera(Camera.CameraInfo.CAMERA_FACING_BACK)

                }

            }

        })

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

相机参数设置


 /**

     * 初始化并打开相机,我这里默认打开的后置摄像头

     */

    private fun openCamera(cameraId: Int) {

        mCamera = Camera.open(cameraId)

        mCamera?.run {

            setPreviewDisplay(holder)

            setDisplayOrientation(WindowDegree.getDegree(this@Camera1PreviewActivity))


            var cameraInfo = Camera.CameraInfo()

            Camera.getCameraInfo(cameraId, cameraInfo)

            Log.i("camera1", "相机方向 ${cameraInfo.orientation}")



            val parameters = parameters


            parameters?.run {


                //自动曝光结果给我爆一团黑,不能忍 自己设置

                exposureCompensation = maxExposureCompensation


                //自动白平衡

                autoWhiteBalanceLock = isAutoWhiteBalanceLockSupported



                //设置预览大小

                appropriatePreviewSizes = getAppropriatePreviewSizes(parameters)

                setPreviewSize(appropriatePreviewSizes?.width!!, appropriatePreviewSizes?.height!!)


                //设置对焦模式

                val supportedFocusModes = supportedFocusModes

                if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {

                //设置自动对焦,启动自动对焦是通过Camera的autoFocus方法实现

                //如果要连续对焦,这个方法要多次调用,这里就没有调用autoFocus

                //想要连续对焦的可以自己实现,通过Handler连续发送消息就行

                    focusMode = Camera.Parameters.FOCUS_MODE_AUTO

                }

                previewFormat = ImageFormat.NV21

            }


//相机资源回收的时候,注意setPreviewCallBack(null),将回调移除

            setPreviewCallback { data, camera ->

            //isRecording是一个开启录制的标志,回调帧数据存放在集合中等待编码器编码

                if (isRecording) {

                    if (data != null) {

                        Log.i("camera1", "获取视频数据 ${data.size}")

                        Log.i("camera1", "视频线程是否为   $videoThread")

                        videoThread.addVideoData(data)

                    }

                }


            }

//开始预览

            startPreview()

        }

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

为避免文章过长,有些代码未贴出,可以直接到github查看,getAppropriatePreviewSizes(parameters)未贴出。


2.录像处理线程

录像的YUV数据设置的格式是NV21,Camera1的API可以返回这个,但是Camera2是不支持的,视频编码最好是NV12数据,最后要转换一下,录像线程主要做的是获取数据,转换成NV12 -> 编码为H264 ->写入Muxer


/**

*代码没有分离,直接在Activity创建的内部类,想要代码更简洁的可以分开

**/

  inner class VideoEncodeThread : Thread() {

  //预览的数据就直接添加到这个集合中

        private val videoData = LinkedBlockingQueue<ByteArray>()



        fun addVideoData(byteArray: ByteArray) {

            videoData.offer(byteArray)

        }



        override fun run() {

            super.run()

            //创建编码用的MediaFormat,下面贴出

            initVideoFormat()

            

//创建视频编码器MediaCodec

            videoCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)

            videoCodec!!.configure(videoMediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

            videoCodec!!.start()

            //如果未设置结束,就循环编码数据

            while (!videoExit) {


                val poll = videoData.poll()

                if (poll != null) {

                    encodeVideo(poll, false)

                }

            }


            //发送编码结束标志

            encodeVideo(ByteArray(0), true)

            //注意释放资源

            videoCodec!!.release()

            Log.i("camera1", "视频释放")

        }

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

初始化MediaFormat


    private fun initVideoFormat() {

        videoMediaFormat =

            MediaFormat.createVideoFormat(

                MediaFormat.MIMETYPE_VIDEO_AVC,

                appropriatePreviewSizes!!.width,

                appropriatePreviewSizes!!.height

            )

        //设置颜色类型  5.0新加的颜色格式

        videoMediaFormat.setInteger(

            MediaFormat.KEY_COLOR_FORMAT,

            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible

        )

        //设置帧率

        videoMediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)

        //设置比特率

        videoMediaFormat.setInteger(

            MediaFormat.KEY_BIT_RATE,

            appropriatePreviewSizes!!.width * appropriatePreviewSizes!!.height * 5

        )

        //设置每秒关键帧间隔

        videoMediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5)

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

视频编码(同步方式)


   private fun encodeVideo(data: ByteArray, isFinish: Boolean) {

        val videoArray = ByteArray(data.size)

        if (!isFinish) {

        //NV21转NV12  网上找的,他两不同就是排列方式一个是VUVUVU一个是UVUVUV

        //具体看github代码

            NV21toI420SemiPlanar(

                data,

                videoArray,

                appropriatePreviewSizes!!.width,

                appropriatePreviewSizes!!.height

            )

        }

        val videoInputBuffers = videoCodec!!.inputBuffers

        var videoOutputBuffers = videoCodec!!.outputBuffers



        //这个TIME_OUT_US设置的是0.01s也就是10000微秒,之前设置成1s,结果视频掉帧

        //严重,声音也播放不了,说明这个值不能设置太大

        val index = videoCodec!!.dequeueInputBuffer(TIME_OUT_US)


        if (index >= 0) {

            val byteBuffer = videoInputBuffers[index]

            byteBuffer.clear()

            byteBuffer.put(videoArray)

            if (!isFinish) {

                videoCodec!!.queueInputBuffer(index, 0, videoArray.size, System.nanoTime()/1000, 0)

            } else {

                videoCodec!!.queueInputBuffer(

                    index,

                    0,

                    0,

                    System.nanoTime()/1000,

                    MediaCodec.BUFFER_FLAG_END_OF_STREAM

                )


            }

            val bufferInfo = MediaCodec.BufferInfo()

            Log.i("camera1", "编码video  $index 写入buffer ${videoArray?.size}")


            var dequeueIndex = videoCodec!!.dequeueOutputBuffer(bufferInfo, TIME_OUT_US)


//这里需要注意,MediaMuxer要设置的音视频MediaFormat要在这里获取,设置过了就不用重新在更改

//如果不使用在这里获取的MediaFormat,极有可能最后MediaMuxer关闭时候出现关闭失败异常

            if (dequeueIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

                if (MuxThread.videoMediaFormat == null)

                    MuxThread.videoMediaFormat = videoCodec!!.outputFormat

            }


            if (dequeueIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {

                videoOutputBuffers = videoCodec!!.outputBuffers

            }


            while (dequeueIndex >= 0) {

                val outputBuffer = videoOutputBuffers[dequeueIndex]

                //由于配置性信息在之前的MediaFormat已经包含,这里就不需要写入MediaMuxer了

                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) {

                    bufferInfo.size = 0

                }

                //将编码数据加入队列等待Muxer写入

                if (bufferInfo.size != 0) {

                    muxerThread?.addVideoData(outputBuffer, bufferInfo)

                }

                Log.i(

                    "camera1",

                    "编码后video $dequeueIndex buffer.size ${bufferInfo.size} buff.position ${outputBuffer.position()}"

                )

                videoCodec!!.releaseOutputBuffer(dequeueIndex, false)

                //检查是否结束

                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {

                    break

                } else{

                    dequeueIndex = videoCodec!!.dequeueOutputBuffer(bufferInfo, TIME_OUT_US)

                }

            }

        }

    }

--------------------- 

作者:One_Month 

来源:CSDN 

原文:https://blog.csdn.net/One_Month/article/details/90765636 

版权声明:本文为博主原创文章,转载请附上博文链接!


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69931768/viewspace-2647506/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/69931768/viewspace-2647506/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值