原文 : https://juejin.cn/post/69601302052266311754

本文解决的问题

本文主要使用 MediaCodec 硬编码器对 Android 设备采集的音视频编码

  1. 封装音视频基础编码器
  2. 封装音频编码器
  3. 封装视频编码器
  4. 使用新封装的视频编码器改造示例2
  5. 使用Camera进行视频录制并保存为视频流
  6. 使用AudioRecord进行音频录制并保存为音频流(待完成)
  7. 使用MediaMuxer混合器合并视频和音频为一路流(待完成)

示例链接

一、封装音视频基础编码器

  1. 定义编码接口类 ICodec :
interface ICodec {    //入队    fun putBuf(data: ByteArray, offset: Int, size: Int)    //处理数据    fun dealWith(data: ByteArray)    //停止线程    fun stopWorld()}
  1. 定义编码任务线程 BaseCodec :

音视频逐帧编码,编码是一项耗时任务,所以需要定义一队列来存储需要编码的数据帧.

BaseCodec 为抽象类并且实现了ICodec接口,在任务启动时不停的从队列中取出数据,dealWith方法进行处理

abstract class BaseCodec : Thread(), ICodec {    val inBlockingQueue = ArrayBlockingQueue(30)    override fun putBuf(data: ByteArray, offset: Int, size: Int) {        val byteArray = ByteArray(size)        System.arraycopy(data, offset, byteArray, 0, size)        inBlockingQueue.put(byteArray)    }    var threadRunning = true;    override fun run() {        try {            BaseCodecLoop1@ while (threadRunning) {                val item = inBlockingQueue.take()                dealWith(item)            }        } catch (e: Exception) {            e.printStackTrace()        }    }    override fun dealWith(data: ByteArray) {}    override fun stopWorld() {        inBlockingQueue.clear()        threadRunning = false;        interrupt()        join(1000)    }}const val SAMPLE_RATE_IN_HZ = 44100 //采样率44.1KHzconst val CHANNEL = AudioFormat.CHANNEL_IN_MONO //单声道,立体声:CHANNEL_IN_STEREOconst val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT //每个采样点16bitconst val DEST_BIT_RATE = 128000 //编码码率
  1. 封装 MediaCodec 音视频编码基础类 BaseMediaCodec :

该类包含

  • 初始化对应mime类型的MediaCodec
/** * mime类型对应的格式 * video/avc: h.264 * video/hevc: h.265 * audio/mp4a-latm: aac */
CodecListener

二、封装音频编码器

  • 初始化:默认初始化 audio/mp4a-latm 编码器
createCodec("audio/mp4a-latm")val format = MediaFormat.createAudioFormat(mime, SAMPLE_RATE_IN_HZ, CHANNEL)format.setInteger(MediaFormat.KEY_BIT_RATE, DEST_BIT_RATE)//buffer 最大值val bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_IN_HZ, CHANNEL, AUDIO_FORMAT)format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize)format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC)configEncoderBitrateMode(format)codec.start()
  • 接收编码产生的数据
override fun bufferUpdate(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) {    buffer.position(bufferInfo.offset)    buffer.limit(bufferInfo.offset + bufferInfo.size)    val data = ByteArray(bufferInfo.size + 7)    addADTStoPacket(data, data.size)    buffer.get(data, 7, bufferInfo.size)    buffer.position(bufferInfo.offset)    listener?.bufferUpdate(data)}/** * 添加ADTS头部的7个字节 */private fun addADTStoPacket(packet: ByteArray, packetLen: Int) {    val profile = 2 // AAC LC    val freqIdx: Int = 4// 44.1kHz    val chanCfg = 2 // CPE    packet[0] = 0xFF.toByte()    packet[1] = 0xF9.toByte()    packet[2] = ((profile - 1 shl 6) + (freqIdx shl 2) + (chanCfg shr 2)).toByte()    packet[3] = ((chanCfg and 3 shl 6) + (packetLen shr 11)).toByte()    packet[4] = ((packetLen and 0x7FF) shr 3).toByte()    packet[5] = ((packetLen and 7 shl 5) + 0x1F).toByte()    packet[6] = 0xFC.toByte()}

三、封装视频编码器

  • 初始化
init {    createCodec("video/avc")}fun setUpVideoCodec(width: Int, height: Int) {    val format = MediaFormat.createVideoFormat(mime, width, height)    format.setInteger(        MediaFormat.KEY_COLOR_FORMAT,        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible    )    //width*height*frameRate*[0.1-0.2]码率控制清晰度    format.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 3)    format.setInteger(MediaFormat.KEY_FRAME_RATE, 30)    //每秒出一个关键帧,设置0为每帧都是关键帧    format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)//        format.setInteger(//            MediaFormat.KEY_BITRATE_MODE,//            MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR//遵守用户设置的码率//        )    configEncoderBitrateMode(format)//        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)//        codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)    codec.start()}
  • 接收编码产物
override fun bufferUpdate(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) {    listener?.bufferUpdate(buffer, bufferInfo)}

四、使用新封装的视频编码器改造示例2

接下来改造第二篇文章的yuv编码mp4代码:

步骤如下: 示例代码链接

CodecListener
fun convertYuv2Mp4_2(context: Context) {    val yuvPath = "${context.filesDir}/test.yuv"    val saveMp4Path = "${context.filesDir}/test.mp4"    File(saveMp4Path).deleteOnExit()    //定义混合器:输出并保存h.264码流为mp4    val mediaMuxer =        MediaMuxer(            saveMp4Path,            MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4        );    var muxerTrackIndex = -1    val videoEncoder = VideoEncoder()    videoEncoder.setUpVideoCodec(1920, 1080)    videoEncoder.start()    videoEncoder.setCodecListener(object : CodecListener {        override fun formatUpdate(format: MediaFormat) {            //step3.1 标记新的解码数据到来,在此添加视频轨道到混合器            muxerTrackIndex = mediaMuxer.addTrack(format)            mediaMuxer.start()        }        override fun bufferUpdate(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) {            mediaMuxer.writeSampleData(muxerTrackIndex, buffer, bufferInfo)        }        override fun bufferOutputEnd() {            mediaMuxer.release()            videoEncoder.stopWorld()        }    })    val byteArray = ByteArray(1920 * 1080 * 3 / 2)    var read = 0    FileInputStream(yuvPath).use { fis ->        while (true) {            read = fis.read(byteArray)            if (read == byteArray.size) {                Thread.sleep(30)                videoEncoder.putBuf(byteArray, 0, byteArray.size)            } else {                videoEncoder.putBufEnd()                break            }        }    }}

使用Camera进行视频录制并保存为视频流

流程与前一示例基本一致,只是获取yuv数据从文件修改到camera实时流

  • 定义编码器 VideoEncoder ,混合器 MediaMuxer
  • 启动Camera,并在回调中获取yuv数据流输入编码器
  • 在编码器回调中获取编码完成的数据,使用混合器保存至本地
  • 退出页面时,调用 videoEncoder.putBufEnd() 方法通知编码器结束

示例代码如下: 链接

var capture = false;//视频编码为mp4val videoEncoder = VideoEncoder()override fun initView() {    videoEncoder.setUpVideoCodec(640, 480)    videoEncoder.start()    binding.cameraview0.apply {        cameraParams.facing = 1        cameraParams.isScaleWidth = false        cameraParams.oritationDisplay = 90        cameraParams.previewSize.previewWidth = 640        cameraParams.previewSize.previewHeight = 480        cameraParams.isFilp = false        addPreviewFrameCallback(object : CameraView.PreviewFrameCallback {            override fun analyseData(data: ByteArray?): Any {                if (capture) {                    videoEncoder.putBuf(data!!, 0, data.size)                }                return 0            }            override fun analyseDataEnd(p0: Any?) {}        })    }    addLifecycleObserver(binding.cameraview0)    val saveMp4Path = "${filesDir}/test.mp4"    File(saveMp4Path).deleteOnExit()    //定义混合器:输出并保存h.264码流为mp4    val mediaMuxer =        MediaMuxer(            saveMp4Path,            MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4        )    var muxerTrackIndex = -1    videoEncoder.setCodecListener(object : CodecListener {        override fun formatUpdate(format: MediaFormat) {            muxerTrackIndex = mediaMuxer.addTrack(format)            mediaMuxer.start()        }        override fun bufferUpdate(buffer: ByteBuffer, bufferInfo: MediaCodec.BufferInfo) {            mediaMuxer.writeSampleData(muxerTrackIndex, buffer, bufferInfo)        }        override fun bufferOutputEnd() {            mediaMuxer.release()            videoEncoder.stopWorld()        }    })    binding.cameraview0.setOnClickListener {        DLog.d("录制:$capture")        capture = !capture    }}override fun onDestroy() {    videoEncoder.putBufEnd()    super.onDestroy()}

使用AudioRecord进行音频录制并保存为音频流

使用MediaMuxer混合器合并视频和音频为一路流

更多相关文章

  1. android实现emoji输入
  2. Android数据加密之Base64编码算法
  3. Android屏幕直播方案
  4. Android数据加密之Base64编码算法
  5. [Android(安卓)调试/测试] Android(安卓)LOGCAT输出编码,eclipse
  6. Android中MediaMuxer跟MediaCodec用例
  7. Android上的Native以及JNI开发(1)
  8. android amr编解码
  9. [Android(安卓)调试/测试] Android(安卓)LOGCAT输出编码,eclipse

随机推荐

  1. 【边做项目边学Android】异常处理:android
  2. Windows7 64位系统搭建Cocos2d-x-2.2.1最
  3. 【Android】技术调研:用代码模拟屏幕点击
  4. Android软件安全开发实践(上)
  5. [转]android开发新浪微博客户端 完整攻略
  6. Android Launcher开发(一)LiveFolder(实
  7. 国内几大Android应用市场试用小记——开
  8. android 解决wifi断线不稳定的问题-终极
  9. Android实现仿QQ登录可编辑下拉框
  10. Android广播机制——广播的注册