原文 : 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<ByteArray>(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.1KHz
const val CHANNEL = AudioFormat.CHANNEL_IN_MONO //单声道,立体声:CHANNEL_IN_STEREO
const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT //每个采样点16bit
const 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 LCval freqIdx: Int = 4// 44.1kHzval chanCfg = 2 // CPEpacket[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码流为mp4val mediaMuxer =MediaMuxer(saveMp4Path,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);var muxerTrackIndex = -1val 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 = 0FileInputStream(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;//视频编码为mp4
val videoEncoder = VideoEncoder()
override fun initView() {videoEncoder.setUpVideoCodec(640, 480)videoEncoder.start()binding.cameraview0.apply {cameraParams.facing = 1cameraParams.isScaleWidth = falsecameraParams.oritationDisplay = 90cameraParams.previewSize.previewWidth = 640cameraParams.previewSize.previewHeight = 480cameraParams.isFilp = falseaddPreviewFrameCallback(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码流为mp4val mediaMuxer =MediaMuxer(saveMp4Path,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)var muxerTrackIndex = -1videoEncoder.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混合器合并视频和音频为一路流

研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264相关推荐

  1. 【FFmpeg编码实战】(2)将YUV420P图片集编码成H.264视频文件(方法二)

    [FFmpeg编码实战](2)将YUV420P图片集编码成H.264视频文件(方法二) 一.编码成 H.264 视频文件,运行结果 二.编码成 MPEG4 视频文件,运行结果 三.编码成 AV_COD ...

  2. 为android系统添加USB AUDIO设备的放音和录音功能

    转载请注明出处:http://blog.csdn.net/adits/article/details/8242146 开发环境简介 1. 主机系统: Unbuntu10.10 2. android系统 ...

  3. 为android系统添加USB AUDIO设备的放音和录音功能(转载)

    开发环境简介 1. 主机系统: Unbuntu10.10 2. android系统版本: 4.0.3(Linux kernel 3.0.8) 综述 android的音频系统非常庞大复杂:涉及到java ...

  4. ios avi_转换DVD,ISO和AVI视频以在iOS设备上播放

    ios avi If you own an iOS device,  you know that by default it only plays certain video formats. You ...

  5. 视频编码标准H.264/AVC

    H.264/AVC 是ITU-T VCEG 和ISO/IEC MPEG 共同开发的视频处理标准,ITU-T作为标准建议H.264,ISO/IEC作为国际标准14496-10(MPEG-4 第10部分) ...

  6. 视频编码:H.264编码

    本文参考毕厚杰老师<新一代视频压缩编码标准-----H.264/AVC>一书以及雷霄骅博客<视音频编解码技术零基础学习方法>整理. 1.概念部分: H.264编码: 视频编解码 ...

  7. linphone android源码,无法在实际设备上运行Linphone-android源代码

    我已经使用git clone下载了Linphone- android源代码.当我将源代码导入eclipse时,没有发现错误.然后我尝试在实际设备上运行应用程序,但是,应用程序无法加载并且崩溃了.这是错 ...

  8. android怎么玩大头特效,安卓系统上抖音大头特效怎么弄?

    安卓系统上抖音大头特效怎么弄?相信很多小伙伴都下载过抖音或者玩过抖音,小编之前刚下抖音的时候,一天刷短视频2个小时,感觉时间好快啊.不过,光看,自己不动手制作,好像缺了点什么?那本期小编就根据自己用安 ...

  9. 模拟器 android 升级,android – 如何在模拟器和真实设备上升级SystemUI.apk

    我正在使用自定义ROM,我需要对SytemUI执行一些自定义(例如状态栏).所以,这是我做的步骤 1. $. build/envsetup.sh 2. $lunch 1 // normal emula ...

最新文章

  1. idea内Maven的全局配置
  2. 4G EPS 中建立 UE 和 MME 之间的 NAS(非接入服务)信令连接
  3. c语言NULL和0区别
  4. [导入]为Exchange Server安装WAP电子邮件网关
  5. 微软的 SQL Server 你学会了吗?
  6. 打不死我的,终将使我强大!DevOps黑客马拉松参赛心得
  7. Neo4j:使用Cypher生成实时建议
  8. C# 参考之方法参数关键字:params、ref及out
  9. 路由器连接久点就慢下来了,怎么回事?
  10. 换SSD,WIN10系统备份镜像迁移GHOST入坑,DISM好用,修复WINRE,绿联集线器导致USB硬盘供电不足
  11. MFC TeeChart 用法整理二
  12. 获取代理IP的三种途径
  13. 普渡大学计算机工程专业提前毕业,Purdue的ECE「普渡大学西拉法叶分校电气与计算机工程学院」...
  14. 解决合并压缩包分卷无法解压 错误信息:文件格式未知或者压缩文件数据已经损坏
  15. ZooZ推出应用内移动支付SDK
  16. CASE SOLVED: ubuntu16.04 搜狗拼音中文乱码
  17. dellr740服务器智能风扇开启,dell r740服务器 BIOS设置
  18. mysql 1265错误_Mysql出现ERROR 1265: Data truncated for
  19. 离散Laplace-Beltrami 算子
  20. FPGA学习前导:FPGA/CPLD简介

热门文章

  1. c语言中的tanh函数,tanh()函数,用于C ++中的复数
  2. python网上有免费资源吗_【转载】学习Python无从下手?最好的免费资源想要拿走...
  3. php udp发送和接收_php socket通信(tcp/udp)实例分析
  4. 七、DNS报文及抓包分析
  5. oracle插入表为文件,将文本文件插入Oracle表中
  6. 老旧的金融机构,是时候赶赶云计算的时髦了
  7. Selenium支持高版本的FireFox
  8. FZU 2159 WuYou
  9. 设计模式学习每天一个——Factory模式 和 Abstract Factory模式
  10. 胖子哥的大数据之路(7)- 传统企业切入核心or外围