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;
}
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);
}
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);
到这里很重要了 看明白了你的编解码就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
应为我这边主题是写直播 那么对于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
相关文章推荐
- Android从0开始写直播 基础篇 第2章
- Android从0开始写直播 基础篇 第1章
- Android从0开始写直播 基础篇 第4章
- Android从0开始写直播 前言 第0章
- Android进阶二:序列化总结(基础篇)
- Android插件开发初探——基础篇
- EasyPlayer_Android RTSP安卓播放器直播画面卡在第一帧问题修复
- 12月26日,唐攀网络直播讲解《Android手机底层软硬件结合开发技术》
- Android安全输入设计与思考,android设计思考 为什么使用安全键盘? 安全的输入 各大公司的安全键盘设计 开始自定义安全键盘 安全键盘还需要注意的
- Android VideoView播放视频控制:开始、暂停、快进(3)
- Android 从0开始自定义控件之 View 的滑动(二)
- Android中直播视频技术探究之---基础核心类ByteBuffer解析
- 我和Android的开始
- 历史版本1:Android基础篇之在ListView中显示网络图片
- 实现简易的android 直播技术
- 晓东的android bluetooth记录的开始
- android学习之路----开始之前
- Android程序开始启动时出现白屏(大约1秒)
- Android开发-直播视讯(2)-ijkplayer-基础知识
- 开始学习Android