Android从0开始写直播 基础篇 第3章

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  



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值