package com.runt.live.media
|
|
import android.media.AudioFormat
|
import android.media.MediaCodec
|
import android.media.MediaCodecInfo
|
import android.media.MediaCodecList
|
import android.media.MediaFormat
|
import android.util.Log
|
import com.runt.live.cpp.LiveMiniView
|
import com.runt.live.cpp.LiveMiniView.SAMPLE_RATE
|
import java.nio.ByteBuffer
|
import java.util.Date
|
|
|
/**
|
* @author Runt(qingingrunt2010@qq.com)
|
* @purpose
|
* @date 11/28/24
|
*/
|
class EncodeHelper {
|
val TAG = "EncodeHelper";
|
var mediaCodec264 : MediaCodec;
|
var mediaCodecAAC : MediaCodec;
|
var spsPpsBytes:ByteArray? = null;
|
var audioSeqBytes:ByteArray? = null;
|
var startTime = 0L;
|
|
constructor(){
|
mediaCodec264 = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
|
mediaCodecAAC = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
|
initEncoderFLV(LiveMiniView.FRAME_WIDTH , LiveMiniView.FRAME_HEIGHT,4000*1024, LiveMiniView.FRAME_PS)
|
initEncoderAAC(SAMPLE_RATE,2)
|
native_init_encoder();
|
|
}
|
|
fun initEncoderFLV(width:Int,height:Int,bitrate:Int,fps:Int){
|
startTime = Date().time;
|
var format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,width,height)
|
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
|
format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
|
format.setInteger(MediaFormat.KEY_FRAME_RATE, fps);
|
format.setInteger(MediaFormat.KEY_QUALITY, 100); // 设置质量为最高,范围通常是 0 到 100
|
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2); // 每 2 秒一个关键帧
|
format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
|
format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel51);
|
format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
|
//format.setInteger(MediaFormat.KEY_PROFILE, -1);
|
//format.setInteger(MediaFormat.KEY_LEVEL, -1);
|
mediaCodec264.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
mediaCodec264.start();
|
}
|
|
fun initEncoderAAC(sampleRate:Int,channelCount:Int){
|
val format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate , channelCount) // 例如,44100 Hz采样率,双声道
|
format.setInteger(MediaFormat.KEY_BIT_RATE , 128*1024) // 设置比特率为128000比特/秒
|
format.setInteger(MediaFormat.KEY_AAC_PROFILE , MediaCodecInfo.CodecProfileLevel.AACObjectLC) // 设置AAC profile为LC
|
format.setInteger(MediaFormat.KEY_CHANNEL_MASK , AudioFormat.CHANNEL_IN_STEREO) // 设置声道掩码
|
mediaCodecAAC.configure(format , null , null , MediaCodec.CONFIGURE_FLAG_ENCODE)
|
mediaCodecAAC.start()
|
}
|
|
private fun selectEncoder(mimeType : String) : String? {
|
//OMX.MTK.VIDEO.ENCODER.AVC 联发科
|
//c2.mtk.avc.encoder.secure 联发科
|
//c2.mtk.avc.encoder 联发科
|
//高通
|
//OMX.qcom.video.encoder.avc 高通
|
//c2.qti.avc.encoder
|
//谷歌自带的解码
|
//c2.android.avc.encoder
|
//OMX.google.h264.encoder 软解码
|
val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
|
for (codecInfo in codecList.codecInfos) {
|
if (! codecInfo.isEncoder) continue
|
|
for (type in codecInfo.supportedTypes) {
|
if (type.equals(mimeType , ignoreCase = true)) { // 优先选择非 Google 的编码器
|
//println("codecInfo name:${codecInfo.name}")
|
val capabilities = codecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_AVC)
|
if(!capabilities.colorFormats.contains(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar)){
|
continue
|
}
|
//c2.qti.avc.encoder这个编码画面清晰,但是丢帧
|
if(codecInfo.name.equals("c2.qti.avc.encoder")){
|
return codecInfo.name;
|
}
|
//编码模糊
|
if(codecInfo.name.equals("c2.mtk.avc.encoder")){
|
val caps = codecInfo.getCapabilitiesForType(mimeType)
|
for (colorFormat in caps.colorFormats) {
|
Log.d("ColorFormat" , "Supported: $colorFormat")
|
}
|
return codecInfo.name;
|
}
|
}
|
}
|
}
|
return null // 没找到合适的编码器
|
}
|
|
fun encodeYUV(yuvData:ByteArray){
|
//Log.i(TAG , "encode: pusher yuvData:${yuvData!!.size}")
|
synchronized(mediaCodec264){
|
var time = Date().time
|
var inputBufferIndex = mediaCodec264.dequeueInputBuffer(10000)
|
if(inputBufferIndex >=0){
|
var inputBuffer = mediaCodec264.getInputBuffer(inputBufferIndex)
|
if (inputBuffer != null) {
|
inputBuffer.clear();
|
inputBuffer.put(yuvData);
|
}
|
mediaCodec264.queueInputBuffer(inputBufferIndex, 0, yuvData!!.size, time - startTime, 0);
|
}
|
|
// 获取编码后的 H.264 数据
|
val bufferInfo = MediaCodec.BufferInfo()
|
var outputBufferIndex = mediaCodec264.dequeueOutputBuffer(bufferInfo , 10000)
|
|
//Log.i(TAG , "encode: pusher outputBufferIndex:${outputBufferIndex}")
|
/*if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
val mediaFormat : MediaFormat = mediaCodec264.getOutputFormat()
|
var spsBuffer = mediaFormat.getByteBuffer("csd-0")
|
var ppsBuffer = mediaFormat.getByteBuffer("csd-1")
|
val spsBytes = ByteArray(spsBuffer!!.remaining())
|
val ppsBytes = ByteArray(ppsBuffer!!.remaining())
|
spsBuffer.get(spsBytes, 0, spsBytes.size)
|
ppsBuffer.get(ppsBytes, 0, ppsBytes.size)
|
spsPpsData = ByteArray(spsBytes.size+ppsBytes.size)
|
spsBuffer.clear()
|
ppsBuffer.clear()
|
for( i in 0..(spsBytes.size+ppsBytes.size - 1)){
|
if(i < spsBytes.size){
|
spsPpsData!!.set(i,spsBytes[i])
|
}else{
|
spsPpsData!!.set(i,ppsBytes[i-spsBytes.size])
|
}
|
}
|
Log.i(TAG , "initEncoderFLV: spspps: ${spsBytes.size} ${ppsBytes.size}")
|
}*/
|
while (outputBufferIndex >= 0) {
|
val outputBuffer = mediaCodec264.getOutputBuffer(outputBufferIndex)
|
if (outputBuffer != null && bufferInfo.size > 0) {
|
//Log.i(TAG , "encode: pusher bufferInfo.flags:${bufferInfo.flags}")
|
// 通过 JNI 将 H.264 数据发送到 C++
|
if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
|
spsPpsBytes = ByteArray(bufferInfo.size)
|
outputBuffer.get(spsPpsBytes)
|
//Log.i(TAG , "encode: pusher BUFFER_FLAG_CODEC_CONFIG:${bufferInfo.size}")
|
} else { // 处理数据
|
val h264Data = ByteArray(bufferInfo.size)
|
outputBuffer.get(h264Data)
|
native_sent_h264(h264Data , time - startTime)
|
//Log.i(TAG , "encode: pusher h264Data:${h264Data.size}")
|
}
|
|
}
|
mediaCodec264.releaseOutputBuffer(outputBufferIndex , false)
|
outputBufferIndex = mediaCodec264.dequeueOutputBuffer(bufferInfo , 10000)
|
//Log.i(TAG , "encode: pusher 发给x264解码 耗时 ${Date().time - time} flags:${bufferInfo.flags}")
|
}
|
}
|
}
|
|
|
fun encodePCM(pcmData:ByteArray){ // TODO: Imp pcm encoder
|
synchronized(mediaCodecAAC){
|
var time = Date().time
|
val inputBufferIndex : Int = mediaCodecAAC.dequeueInputBuffer(10000) // 获取输入缓冲区索引,-1表示无限期等待
|
if (inputBufferIndex >= 0) {
|
var inputBuffer = mediaCodecAAC.getInputBuffer(inputBufferIndex)
|
if (inputBuffer != null) {
|
inputBuffer.clear();// 清除并准备
|
inputBuffer.put(pcmData);//写入数据
|
}
|
// 从音频源读取数据到inputBuffer中(这里需要你自己实现从麦克风等获取音频数据的逻辑)
|
mediaCodecAAC.queueInputBuffer(inputBufferIndex, 0, pcmData!!.size, time - startTime, 0); // 将输入缓冲区的数据提交给编码器
|
}
|
|
val bufferInfo = MediaCodec.BufferInfo()
|
var outputBufferIndex = mediaCodecAAC.dequeueOutputBuffer(bufferInfo , 10000)
|
/*if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
val mediaFormat : MediaFormat = mediaCodecAAC.getOutputFormat()
|
var seqBuffer = mediaFormat.getByteBuffer("csd-0")
|
val seqBytes = ByteArray(seqBuffer!!.remaining())
|
seqBuffer.get(seqBytes, 0, seqBytes.size)
|
seqBuffer.clear()
|
Log.i(TAG , "initEncoderFLV: spspps: ${spsBytes.size} ${ppsBytes.size}")
|
}*/
|
//Log.i(TAG , "encodePCM: pusher outputBufferIndex:${outputBufferIndex} ${bufferInfo.flags}")
|
if (outputBufferIndex >= 0) {
|
val outputBuffer : ByteBuffer = mediaCodecAAC.getOutputBuffer(outputBufferIndex) !!
|
if(bufferInfo.flags != 0){
|
audioSeqBytes = ByteArray(bufferInfo.size )
|
outputBuffer[audioSeqBytes]
|
}else{
|
/*val aacData = ByteArray(bufferInfo.size + 7) // ADTS 头 + AAC 数据
|
addADTSToPacket(aacData , bufferInfo.size + 7)
|
outputBuffer[aacData , 7 , bufferInfo.size] // 复制 AAC 数据*/
|
val aacData = ByteArray(bufferInfo.size )
|
outputBuffer[aacData]
|
// 这里可以通过 JNI 传给 C++
|
native_send_aac(aacData,time - startTime)
|
}
|
outputBuffer.clear()
|
mediaCodecAAC.releaseOutputBuffer(outputBufferIndex , false)
|
}
|
}
|
}
|
|
external fun native_init_encoder()
|
|
external fun native_sent_h264(byteArray : ByteArray,time:Long);
|
|
external fun native_send_aac(byteArray : ByteArray,time : Long)
|
|
|
fun getSpsPpsData():ByteArray{
|
Log.i(TAG , "getSpsPpsData: ${spsPpsBytes !!.size}")
|
return spsPpsBytes!!;
|
}
|
|
fun getAudioSeqData():ByteArray{
|
audioSeqBytes?.let {
|
return it;
|
};
|
return ByteArray(0);
|
}
|
|
private fun addADTSToPacket(packet : ByteArray , packetLen : Int) {
|
val profile = 2 // AAC LC
|
val freqIdx = 4 // 44100Hz
|
val chanCfg = 2 // 2 channels
|
|
packet[0] = 0xFF.toByte()
|
packet[1] = 0xF1.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()
|
}
|
|
companion object {
|
init {
|
System.loadLibrary("live-tool")
|
}
|
}
|
}
|