视频代码如何获取(探索在Android设备上采集音视频并使用MediaCodec编码为H.264)

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

1.定义编码接口类ICodec:

interface ICodec { //入队 fun putBuf(data: ByteArray, offset: Int, size: Int) //处理数据 fun dealWith(data: ByteArray) //停止线程 fun stopWorld() }

2.定义编码任务线程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 //编码码率

3.封装MediaCodec音视频编码基础类BaseMediaCodec:

该类包含

  • 初始化对应mime类型的MediaCodec

/** * mime类型对应的格式 * video/avc: h.264 * video/hevc: h.265 * audio/mp4a-latm: aac */

  • 数据入队
  • 发送结束标记
  • 数据解码出队线程
  • CodecListener编码结束回传结果的接口
  • stopWorld释放接口
二、封装音频编码器

初始化:默认初始化audio/mp4a-latm编码器

createCodec("audio/mp4a-latm") val format = MediaFormat.createAudioFormat(mime, SAMPLE_RATE_IN_HZ, 1) 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代码:

步骤如下:示例代码链接

  • 定义混合器
  • 定义编码器
  • 读取yuv文件,开始编码
  • CodecListener回调中接收编码完成的数据,使用MediaMuxer混合器保存视频文件
  • 读取文件结束,编码结束

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; //视频编码为mp4 val 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进行音频录制并保存为音频流

使用AudioRecord采集pcm裸数据,然后送往MediaCodec编码器编码为aac

深刻注意点:初始化AudioRecord录制音频时使用的声道数必须跟初始化编码器MediaFormat时输入的声道数保持一致,否则会出现裸数据pcm播放正常,编码好的aac数据播放异常,叽里呱啦的,,,貌似加速,android设备就都写1吧,,,立体声基本上都不支持

AudioFormat.CHANNEL_IN_MONO: 单声道 对应声道数 1 AudioFormat.CHANNEL_IN_STEREO:立体声 对应声道数 2

流程基本与录制视频一致

  • 创建AudioRecord,用于采集音频信息
  • 创建AudioEncoder,用于编码采集到的PCM裸流
  • 创建MediaMuxer,用于存储编码好的AAC数据

//存储pcm裸数据 val file_pcm = File("$filesDir/test.pcm") //直接FileOutputStream写出,存储编码完成的aac数据 val file_mp3 = File("$filesDir/test.mp3") //使用混合器mediaMuxer存储编码好的aac数据 val file_mp32 = File("$filesDir/test2.mp3") binding.startRecord.setOnClickListener { file_pcm.deleteOnExit() file_mp3.deleteOnExit() file_mp32.deleteOnExit() thread { DLog.d("开始录制") isCapture = true val bufferSize = AudioRecord.getMinBufferSize( SAMPLE_RATE_IN_HZ, CHANNEL, AUDIO_FORMAT ) val audioRecord = AudioRecord( MediaRecorder.AudioSource.MIC, SAMPLE_RATE_IN_HZ, CHANNEL, AUDIO_FORMAT, bufferSize ) audioRecord.startRecording() val data = ByteArray(bufferSize) val fos = FileOutputStream(file_pcm) val fos_mp3 = FileOutputStream(file_mp3) //定义混合器:输出并保存编码后的aac为mp3 val mediaMuxer = MediaMuxer( file_mp32.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ) var muxerTrackIndex = -1 val audioEncoder = AudioEncoder() audioEncoder.setCodecListener(object : CodecListener { override fun formatUpdate(format: MediaFormat) { super.formatUpdate(format) } override fun bufferUpdate(data: ByteArray) { super.bufferUpdate(data) DLog.d("bufferUpdate ${data.size}") fos_mp3.write(data, 0, data.size) } override fun bufferOutputEnd() { super.bufferOutputEnd() audioEncoder.stopWorld() fos_mp3.flush() fos_mp3.close() } }) audioEncoder.start() while (isCapture) { val len = audioRecord.read(data, 0, data.size) audioEncoder.putBuf(data, 0, len) fos.write(data, 0, len); } fos.flush() fos.close() audioEncoder.putBufEnd() audioRecord.stop() DLog.d("录制结束") } }

您可以还会对下面的文章感兴趣

最新评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

使用微信扫描二维码后

点击右上角发送给好友