MediaCodec 写编码 网上有一堆解释 各种api 我这边应为是基础篇 所以教你能用
应为我这边主题是写直播 那么对于mediacodec 首先关心输入输出
输入surface , data
输出data
开始写代码 :
1.new 一个mediacodec
2.config 首先你要知道你现在输入的是什么格式的数据
比如surface
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
如果是data数据 有很多图片格式 我这边选择nv21格式
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
然后根据格式查看是否支持这种格式
public static MediaCodecInfo getEncodeMediaCodecInfo(String codecType,int colorSpace){ //获取编解码器列表 int numCodecs = MediaCodecList.getCodecCount(); MediaCodecInfo codecInfo = null; for(int i = 0; i < numCodecs && codecInfo == null ; i++){ MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); if(!info.isEncoder()){//筛选编码器 continue; } String[] types = info.getSupportedTypes(); boolean found = false; for(int j=0; j<types.length && !found; j++){ if(types[j].equals(codecType)&&isSupportColorSpace(info,codecType,colorSpace)){ found = true; break; } } if(!found){ continue; } codecInfo = info; } return codecInfo; }
public static boolean isSupportColorSpace(MediaCodecInfo codecInfo,String codecType,int colorSpaceType){ //检查所支持的colorspace int colorFormat = 0; MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(codecType); for(int i = 0; i < capabilities.colorFormats.length && colorFormat == 0 ; i++){ int format = capabilities.colorFormats[i]; if(format==colorSpaceType){ colorFormat = format; break; } } if(colorFormat!=0){ return true; } else{ return false; } }
以上是获取支持你的输入输出格式的编解码器 然后根据返回的编码器 config
private void choesEncodeMediaCode(int intputType,String outputType){ mediaCodecInfo=MediaCodecUtil.getEncodeMediaCodecInfo(outputType,intputType); if(mediaCodecInfo==null){ return ; } try { mediaCodec= MediaCodec.createByCodecName(mediaCodecInfo.getName()); } catch (IOException e) { e.printStackTrace(); } MediaFormat vformat = MediaFormat.createVideoFormat(outputType, mWidth, mHeight); vformat.setInteger(MediaFormat.KEY_COLOR_FORMAT, intputType); vformat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0); vformat.setInteger(MediaFormat.KEY_BIT_RATE, MediaCodecUtil.getBitrateSize(mWidth,mHeight,vbitrateType)); vformat.setInteger(MediaFormat.KEY_FRAME_RATE, FPS); vformat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, VGOP); //vformat.setByteBuffer("csd-0" , ByteBuffer.wrap(sps)); //vformat.setByteBuffer("csd-1", ByteBuffer.wrap(pps)); mediaCodec.configure(vformat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); if(intputType== MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) { mSurface = mediaCodec.createInputSurface(); } if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ AsynchronousProcessingUsingBuffers(); } }
到这里我们开始讲重点:
a。比特流 这个东西影响传输图片质量
quality.getNumber()*width*height
这是我的公式 图片长宽*质量系数 系数高中低 我这边是 5,3,1
b。fps是帧率
c。VGOP 是关键帧间隔 这个东西很尴尬 受数据来的时间影响 受编码时间影响 受图像信息影响一般不准
如果你是surface为输入源可能准点(基础篇不深入 下面有进一步讲的)
d。AsynchronousProcessingUsingBuffers()这个东西是异步回调等等和同步一起讲 有版本要求
3. config讲完成后我们只要meidacodec。start()一下就可以开启编码了
然后我们将具体怎么编码 编码大同小异 分同步编码和异步编码 版本不同函数名不同但是功能是一样的
先拿到数据
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); int inputBufferId = mediaCodec.dequeueInputBuffer(timeoutUs); if (inputBufferId >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferId]; encodecInput(inputBuffer,inputBufferId); }
private void encodecInput(ByteBuffer inputBuffer,int inputBufferId){ byte[] input= null; input = YUVQueue.poll(); if(input==null){ return; } inputBuffer.clear(); inputBuffer.put(input); long pts = computePresentationTime(generateIndex); mediaCodec.queueInputBuffer(inputBufferId, 0, input.length, pts, 0); generateIndex++; }
YUVQueue这个是图片数据就是输入源 是个阻塞队列
inputbuffer是内部输入源的一个应用地址
然后把YUVQueue的数据塞到inputBuffer里就是给MeidaCodec喂数据了 更具以上代码不难理解
pts就是关键帧时间
* @param presentationTimeUs The presentation timestamp in microseconds for this * buffer. This is normally the media time at which this * buffer should be presented (rendered). When using an output * surface, this will be propagated as the {@link * SurfaceTexture#getTimestamp timestamp} for the frame (after * conversion to nanoseconds).
详细解释
4. 喂数据结束就是 那数据了
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo,timeoutUs); ByteBuffer outputBuffer=null; if(outputBufferId>=0){ outputBuffer = outputBuffers[outputBufferId]; } encodecOutput(outputBufferId,outputBuffer,null,bufferInfo,mediaCodec);
private void encodecOutput(int encoderStatus,ByteBuffer outputBuffer,MediaFormat bufferFormat,MediaCodec.BufferInfo bufferInfo,MediaCodec mediaCodec){ if (encoderStatus >= 0) { byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); if (outputBuffer == null) { throw new RuntimeException("encoderOutputBuffer " + outputBuffer + " was null"); } // if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { //bufferInfo.size = 0; } else{ //use outData } mediaCodec.releaseOutputBuffer(encoderStatus,false); } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { //拿到输出缓冲区,用于取到编码后的数据 //outputBuffers = mediaCodec.getOutputBuffers(); } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // Subsequent data will conform to new format. //MediaFormat format = codec.getOutputFormat(); } else if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER){ }else{ } }
因为有异步同步的区别的 但是大同小异对我们来说就是放进去数据拿出来编码后的数据
到这里很重要了 看明白了你的编解码就ok了
先看encodecStatus 小于0就是各种乱七八糟状态 大于等于0就是具体编出了数据我现在是h264的数据
我先简单介绍下h264格式:
大家都知道视频就是播放连续的帧 每帧就是一张图片 但是一张张图片存太大就有了各种各样格式 说简单点就是压缩格 式
h264格式就是sps spp 关键帧 压缩帧 关键帧 压缩帧。。。关键帧 压缩帧 这种类型组成的
压缩帧就是应为和关键帧相差不大所以弄了个压缩帧
以上所有的sps spp 关键帧什么的开头都是 00 00 00 01 开始所以你会发现每次编码返回的头4位都是00 00 00 01 或者00 00 01 三位也是对的
接下来的一位表示这段信息具体是个什么鬼 比如sps 00 00 00 01 67 , spp 00 00 00 01 68 关键帧00 00 00 01 65
sps spp可以理解为头文件告诉你这个视频数据的宽高什么什么的信息
下面回归正题你拿到了h264编码出来的数据后你可以做你想做的任何事 比如我现在做直播我需要视频流 视频是支持任何一点开始播放的 所以我会存下sps spp 在每个关键帧前面插入这个信息 传给rtp协议封装 这样视频流就有了
如果你还有声音 代码类似 比如aac的adts格式也有这么个头标签 ff f0 开始类似于00 00 00 01的东西
上代码
https://github.com/wen14148/MediaCodecEngine