研究Android音视频-3-在Android设备上采集音视频并使用MediaCodec编码为H.264
16lz
2022-05-12
原文 : https://juejin.cn/post/69601302052266311754
本文解决的问题
本文主要使用 MediaCodec
硬编码器对 Android
设备采集的音视频编码
- 封装音视频基础编码器
- 封装音频编码器
- 封装视频编码器
- 使用新封装的视频编码器改造示例2
- 使用Camera进行视频录制并保存为视频流
- 使用AudioRecord进行音频录制并保存为音频流(待完成)
- 使用MediaMuxer混合器合并视频和音频为一路流(待完成)
示例链接
一、封装音视频基础编码器
- 定义编码接口类
ICodec
:
interface ICodec { //入队 fun putBuf(data: ByteArray, offset: Int, size: Int) //处理数据 fun dealWith(data: ByteArray) //停止线程 fun stopWorld()}
- 定义编码任务线程
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 //编码码率
- 封装
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混合器合并视频和音频为一路流
更多相关文章
- android实现emoji输入
- Android数据加密之Base64编码算法
- Android屏幕直播方案
- Android数据加密之Base64编码算法
- [Android(安卓)调试/测试] Android(安卓)LOGCAT输出编码,eclipse
- Android中MediaMuxer跟MediaCodec用例
- Android上的Native以及JNI开发(1)
- android amr编解码
- [Android(安卓)调试/测试] Android(安卓)LOGCAT输出编码,eclipse