EasyPusher安卓Android手机直播推送之MediaCodec 硬编码H264格式

最近在研究EasyDarwin的Push库EasyPusher,EasyPusher可以推送H264视频到EasyDarwin服务器,终端可以通过rtsp协议访问该实时流,达到手机直播的功能,延迟基本在2秒以内。
EasyDarwinQQ群:496258327
本文主要记录一下最近研究的关于Android手机如何获取实时画面,并将数据编码为H264的格式的视频流,编码使用的是Android自带的MediaCodec,也就是硬解。
本demo的下载地址:MediaCodecDemo

MediaCodec是Android在4.1中加入的新的API,目前也有很多文章介绍MediaCodec的用法,但是很多时候很多手机都失败,主要问题出现在调用dequeueOutputBuffer的时候总是返回-1,让你以为No buffer available !这里介绍一个开源项目libstreaming,我们借助此项目中封装的一个工具类EncoderDebugger,来初始化MediaCodec会很好的解决此问题,目前为止测试了几个手机都可以成功,包括小米华为Moto。
看一下怎么使用的

EncoderDebugger debugger = EncoderDebugger.debug(getApplicationContext(), width, height);
MediaCodec mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName());

嗯,就这样。当然了,后面还是要根据需要对mMediaCodec设置其他参数的,看一下本demo中设置参数的过程吧

private void initMediaCodec() {int dgree = getDgree();framerate = 15;bitrate = 2 * width * height * framerate / 20;EncoderDebugger debugger = EncoderDebugger.debug(getApplicationContext(), width, height);mConvertor = debugger.getNV21Convertor();try {mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName());MediaFormat mediaFormat;if (dgree == 0) {//dree==0的时候,需要将画面旋转90度,所以这里编码的时候需要将宽和高颠倒,//否则编码后的会面会出现四重画面并且花屏mediaFormat = MediaFormat.createVideoFormat("video/avc", height, width);} else {mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);}mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,debugger.getEncoderColorFormat());mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);mMediaCodec.start();} catch (IOException e) {e.printStackTrace();}
}

编码之前先看一下要编码的数据怎么获取吧,这个当然是来自Camera。
首先是创建SurfaceView用于预览视频画面,并设置回调,来监控生命周期。

surfaceView = (SurfaceView) findViewById(R.id.sv_surfaceview);
surfaceView.getHolder().addCallback(this);
surfaceView.getHolder().setFixedSize(getResources().getDisplayMetrics().widthPixels,
getResources().getDisplayMetrics().heightPixels);

然后是创建Camera的方法:

private boolean ctreateCamera(SurfaceHolder surfaceHolder) {try {//mCameraId=Camera.CameraInfo.CAMERA_FACING_BACKmCamera = Camera.open(mCameraId);Camera.Parameters parameters = mCamera.getParameters();Camera.CameraInfo camInfo = new Camera.CameraInfo();Camera.getCameraInfo(mCameraId, camInfo);int cameraRotationOffset = camInfo.orientation;//设置预览格式NV21,他属于YUV420SPparameters.setPreviewFormat(ImageFormat.NV21);parameters.setPreviewSize(width, height);mCamera.setParameters(parameters);mCamera.autoFocus(null);//计算preview画面需要旋转的角度。目前木有做横竖屏切换的时候无缝旋转画面,后面再搞。int  displayRotation = (cameraRotationOffset - getDgree() + 360) % 360;mCamera.setDisplayOrientation(displayRotation);mCamera.setPreviewDisplay(surfaceHolder);return true;} catch (Exception e) {destroyCamera();e.printStackTrace();return false;}
}private int getDgree() {int rotation = getWindowManager().getDefaultDisplay().getRotation();int degrees = 0;switch (rotation) {case Surface.ROTATION_0:degrees = 0;break; // Natural orientationcase Surface.ROTATION_90:degrees = 90;break; // Landscape leftcase Surface.ROTATION_180:degrees = 180;break;// Upside downcase Surface.ROTATION_270:degrees = 270;break;// Landscape right}return degrees;
}

摄像头创建完毕,就是开启预览

/*** 开启预览*/
public synchronized void startPreview() {if (mCamera != null && !started) {mCamera.startPreview();int previewFormat = mCamera.getParameters().getPreviewFormat();Camera.Size previewSize = mCamera.getParameters().getPreviewSize();int size = previewSize.width * previewSize.height* ImageFormat.getBitsPerPixel(previewFormat)/ 8;mCamera.addCallbackBuffer(new byte[size]);mCamera.setPreviewCallbackWithBuffer(previewCallback);started = true;btnSwitch.setText("停止");}
}

上面就是设置了预览回调的方式,回调中将预览画面一帧一帧的返回给我们,给我们的数据就是NV21格式的,根据需要决定是否需要对数据进行旋转,旋转之后,就是转换,将NV21数据转为YUV420P格式的数据,然后就可以编码为H264数据了。

Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {//mSpsPps用来存储sps pps数据,后面遇到关键帧(I帧),必须将spspps数据加到I帧前面byte[] mSpsPps = new byte[0];@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {if (data == null) {return;}ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();byte[] dst = new byte[data.length];Camera.Size previewSize = mCamera.getParameters().getPreviewSize();if (getDgree() == 0) {//手机竖屏的时候要将获取的数据顺时针旋转90度,否则画面不是正着的,而是逆时针90度dst = Util.rotateNV21Degree90(data, previewSize.width, previewSize.height);} else {dst = data;}try {int bufferIndex = mMediaCodec.dequeueInputBuffer(5000000);if (bufferIndex >= 0) {inputBuffers[bufferIndex].clear();//将YUV420SP数据转换成YUV420P的格式,并将结果存入inputBuffers[bufferIndex]mConvertor.convert(dst, inputBuffers[bufferIndex]);mMediaCodec.queueInputBuffer(bufferIndex, 0,inputBuffers[bufferIndex].position(),System.nanoTime() / 1000, 0);MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);while (outputBufferIndex >= 0) {ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];byte[] outData = new byte[bufferInfo.size];//从buff中读取数据到outData中outputBuffer.get(outData);//记录pps和sps,pps和sps数据开头是0x00 0x00 0x00 0x01 0x67,// 0x67对应十进制103if (outData[0] == 0 && outData[1] == 0 && outData[2] == 0 && outData[3] == 1 && outData[4] == 103) {mSpsPps = outData;} else if (outData[0] == 0 && outData[1] == 0 && outData[2] == 0 && outData[3] == 1 && outData[4] == 101) {//关键帧开始规则是0x00 0x00 0x00 0x01 0x65,0x65对应十进制101//在关键帧前面加上pps和sps数据byte[] iframeData = new byte[mSpsPps.length + outData.length];System.arraycopy(mSpsPps, 0, iframeData, 0, mSpsPps.length);System.arraycopy(outData, 0, iframeData, mSpsPps.length, outData.length);outData = iframeData;}//至此,这一帧的数据已经经过MediaCodec编码完毕,这个outData就是我们需要的数据了,//因为EasyDarwin可以自动将H264打包为RTP,//所以EasyPusher只需要负责将outData推给EasyDarwin就OK了//保存H264数据到本地文件easy.h264Util.save(outData, 0, outData.length, path, true);mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);}} else {Log.e("easypusher", "No buffer available !");}} catch (Exception e) {StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw);e.printStackTrace(pw);String stack = sw.toString();Log.e("save_log", stack);e.printStackTrace();} finally {mCamera.addCallbackBuffer(dst);}}
};

保存之后的文件easy.h264我用VLC播放器打开,截屏如下:

OK,基本上完毕了,该注意的地方都写在代码中了
需要Demo的请到这里https://github.com/kidloserme/MediaCodecDemo

EasyPusher安卓Android手机直播推送之MediaCodec 硬编码H264格式相关推荐

  1. EasyPusher安卓Android手机直播推送之RTSP流媒体协议流程

    EasyPusher移动端推送同我们平时用的RTSP直播推送流程一样,都是采用标准RTSP/RTP推送流程:ANNOUNCE->SETUP->PLAY->RTP/RTCP->T ...

  2. EasyPusher实现安卓Android手机直播推送同步录像功能(源码解析)

    EasyPusher是一款非常棒的推送客户端.稳定.高效.低延迟,音视频同步等都特别好.装在安卓上可作为一款单兵设备来用.说到单兵,在项目中通常都需要边传边录的功能,因此后来EasyPusher也加入 ...

  3. 安卓Android手机直播推送同步录像功能设计与实现源码

    本文转自:http://blog.csdn.net/jyt0551/article/details/58714595 EasyPusher是一款非常棒的推送客户端.稳定.高效.低延迟,音视频同步等都特 ...

  4. android 通知 广告,解决三星/小米等Android手机通知栏推送广告的问题

    三星和小米都是使用安卓系统的手机,有时候突然在手机通知栏弹出很多的广告,而且广告是不定时的弹出,你把消息清理掉之后,过段时间还是会弹出广告. 而且下载的都是一些乱七八糟的东西,对手机没有什么用处,想完 ...

  5. EasyDarwin开源手机直播方案:EasyPusher手机直播推送,EasyDarwin流媒体服务器,EasyPlayer手机播放器...

    在不断进行EasyDarwin开源流媒体服务器的功能和性能完善的同时,我们也配套实现了目前在安防和移动互联网行业比较火热的移动端手机直播方案,主要就是我们的 EasyPusher直播推送项目 和 Ea ...

  6. Android 手机厂商推送服务调研

    由于谷歌服务在国内不能用,Android 的推送真是一大痛点,但也推动了国内一批做第三方服务的厂商.第三方推送做的比较好的有极光.个推.百度.友盟,经过比较之后,最后选择了极光推送.其实这些第三方推送 ...

  7. uniapp push 推送 个推 安卓Android添加Google 推送服务 FCM 离线推送 Dcloud

    项目甲方在国外需要用到google推送服务,看了文档中说明,如果安卓要实现离线推送,需要通过厂商来解决 在google开发者后台添加项目,获取Legancy server key 获取google-s ...

  8. 视频直播推流技术(MediaCodec硬编码+libRTMP,编码器),Demo - Android

    - aac audio_codec; h264,video_codec;25 framerate 25帧; - Camera-YUV帧序列-YUV帧预处理(镜像 缩放 旋转)-编码器-H264数据 从 ...

  9. Android MediaCodec硬编码H264文件(四)

    在 Android 4.1 版本提供了 MediaCodec 接口来访问设备的编解码器,不同于 FFmpeg 的软件编解码,它采用的是硬件编解码能力,因此在速度上会比软解更具有优势,但是由于 Andr ...

最新文章

  1. 自定义Counter使用
  2. 科研赢家比其他人多了哪一个特点?
  3. PHP 安装 扩展时 抛出 /usr/local/Cellar/php@7.1/7.1.25/pecl 异常解决
  4. kubernetes安装_kubernetes安装教程之三:安装kubeadm
  5. java对象实例_深入理解Java对象实例生成的例子!(转)
  6. .net平台的MongoDB使用
  7. Ros学习——roslaunch
  8. 打印pdf就一页_我就是死都不想在mac上装第三方pdf软件
  9. java单元测试面试,Java必备!JUnit面试题和答案汇总
  10. Mongodb常规操作【一】
  11. 维基百科文件解析成中文遇到的变量类型、编码问题
  12. 嵌入式Linux驱动学习之路(二)u-boot体验
  13. 生活中常用的汉字?有4600个。都有哪些呢?
  14. nProtect GameGuard 的破解
  15. 空降项目经理,该如何服众?
  16. 用户行为分析 无埋点代码
  17. 见过世面的程序员,到底有多厉害
  18. 齐纳二极管 稳压二极管 SOD123封装 正负区分
  19. Newtonsoft.Json Json.NET - Newtonsoft
  20. 【报告分享】2021年小红书kol营销白皮-千瓜数据(附下载)

热门文章

  1. 基于 mPaaS 框架 Portal-Bundle 接入方式下 Multidex 分包失效的解决方法
  2. 尚硅谷大数据技术之Kettle
  3. 解读蔚来Q3财报:亏损额度收窄 蔚来汽车幸运的软着陆?
  4. RIP路由项欺骗攻击实验
  5. 20135203齐岳 信息安全系统设计基础第四周学习总结
  6. Acrobat如何将PDF拆分为多个文档
  7. python列表sort倒序输出_Python 列表sort()添加key和reverse参数操作方法|python基础教程|python入门|python教程...
  8. eclipse 32位换成64位 maven tomcat svn 集成
  9. ReferenceError: primordials is not defined错误解决
  10. 康巴丝(compas)某Wi-Fi万年历无法自动校时的问题