您的位置:首页 > 移动开发 > Android开发

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

2018-03-15 14:21 162 查看
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, MediaCo
4000
decUtil.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  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息