EasyPusher安卓直播推流到EasyDarwin开源流媒体服务器工程简析
2017-07-09 16:27
441 查看
EasyPusher主要有三部分组件组成:采集,编码,叠加,上传.在这个基础上同时支持本地存储\后台预览的功能.主要业务模块与相关类之间的关系如图所示:
Created with Raphaël 2.1.0StreamActivityStreamActivity摄像头线程摄像头线程BackgroundCameraServiceBackgroundCameraService编码线程编码线程PusherPusherMuxerMuxer音频线程音频线程音频编码线程音频编码线程TxtOverlayTxtOverlay显示在主界面后台预览提供摄像头数据发送编码后的数据本地存储创建\销毁音频(依附于视频线程)发送编码后的数据本地存储提供水印叠加
创建成功后,再开启摄像头预览.预览的同时做一些初始化工作,初始化编码库和字幕叠加库
视频数据通过onPreviewFrame回调上来,如果需要的话,我们在这里对它做水印叠加:
硬编码的初始化
在startPreview时,调用硬编码的onVideoStart回调,这里进行硬编码的初始化.
在这里调用startMediaCodec来创建MediaCodec:
在视频数据回调的时候,将视频帧塞入硬编码器进行编码.
同时,编码器线程会持续取走编码后的264格式的数据.并根据情况进行存储和推送.
在stopPreview时,编码器进行反初始化:
首先进行初始化,创建编码器,同时开启编码线程:
同硬编码一致,视频数据通过onVideo回调给编码器,这里将视频数据附加上时间戳,并缓存到队列里.
这里用到了双缓冲逻辑.yuv_caches是一个空闲的yuv buffer池,如果yuv buffer池里面没有buffer,则创建新的buffer,拷贝数据,然后放到队列里.
否则在yuv buffer池里取出buffer,拷贝数据,再放到队列里.
接下来,编码线程从队列取出yuv数据,进行编码.编码结束后将yuv数据回收到yuv buffer池
在stopPreview时,对软编码器进行反初始化:
通过持续的编码\推送数据,就可实现视频传输的功能.
EasyPusher现在已在GitHub上开源.地址:https://github.com/EasyDarwin/EasyPusher_Android
通过EasyPusher我们就可以避免接触到稍显复杂的RTSP/RTP/RTCP推送流程,只需要调用EasyPusher的几个API接口,就能轻松、稳定地把流媒体音视频数据推送给RTSP流媒体服务器进行转发和分发,EasyPusher经过长时间的企业用户检验,稳定性非常高;
![](http://www.easydarwin.org/skin/bs/images/app/EasyPusher_AN.png)
iOS https://itunes.apple.com/us/app/easypusher/id1211967057
![](http://www.easydarwin.org/skin/bs/images/app/EasyPusher_iOS.png)
WEB:www.EasyDarwin.org
QQ交流群:587254841
Copyright © EasyDarwin.org 2012-2017
Created with Raphaël 2.1.0StreamActivityStreamActivity摄像头线程摄像头线程BackgroundCameraServiceBackgroundCameraService编码线程编码线程PusherPusherMuxerMuxer音频线程音频线程音频编码线程音频编码线程TxtOverlayTxtOverlay显示在主界面后台预览提供摄像头数据发送编码后的数据本地存储创建\销毁音频(依附于视频线程)发送编码后的数据本地存储提供水印叠加
创建
首先,创建摄像头.这部分代码在MediaStream.java文件,接口为:createCamera:public void createCamera() { mCamera = Camera.open(mCameraId); ... // 这里设置摄像头参数,设置分辨率,显示方向等 }
创建成功后,再开启摄像头预览.预览的同时做一些初始化工作,初始化编码库和字幕叠加库
/** * 开启预览 */ public synchronized void startPreview() { if (mCamera != null) { mSWCodec = PreferenceManager.getDefaultSharedPreferences(mApplicationContext).getBoolean("key-sw-codec", false); if (mSWCodec) { // 初始化软编码库 mMuxer = null; mVC = new SWConsumer(mApplicationContext, mEasyPusher); } else { // 初始化硬编码库 ... // 创建Muxer. mVC = new HWConsumer(mApplicationContext, mEasyPusher); } ... // 设置视频帧回调,设置显示的holder,开启预览 ... // 创建叠加库并初始化 } // 同时启动音频线程 audioStream = new AudioStream(mEasyPusher); audioStream.startRecord(); }
视频数据通过onPreviewFrame回调上来,如果需要的话,我们在这里对它做水印叠加:
@Override public void onPreviewFrame(byte[] data, Camera camera) { // if (PreferenceManager.getDefaultSharedPreferences(mApplicationContext).getBoolean("key_enable_video_overlay", false)) { // 叠加字幕 String txt = String.format("drawtext=fontfile=" + mApplicationContext.getFileStreamPath("SIMYOU.ttf") + ": text='%s%s':x=(w-text_w)/2:y=H-60 :fontcolor=white :box=1:boxcolor=0x00000000@0.3", "EasyPusher", new SimpleDateFormat("yyyy-MM-ddHHmmss").format(new Date())); txt = "EasyPusher " + new SimpleDateFormat("yy-MM-dd HH:mm:ss SSS").format(new Date()); overlay.overlay(data, txt); } // 将数据塞给给编码器 mVC.onVideo(data, previewFormat); mCamera.addCallbackBuffer(data); }
编码与推送
EasyPusher支持硬编码和软编码.硬编码用MediaCodec来实现的,软编码用X264编码库实现的.分别对应类HWConsumer和SWConsumer.硬编码的初始化
在startPreview时,调用硬编码的onVideoStart回调,这里进行硬编码的初始化.
@Override public void onVideoStart(int width, int height) throws IOException { ... startMediaCodec(); ... start(); mVideoStarted = true; }
在这里调用startMediaCodec来创建MediaCodec:
/** * 初始化编码器 */ private void startMediaCodec() throws IOException { ... mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName()); MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight); ... mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mMediaCodec.start(); }
在视频数据回调的时候,将视频帧塞入硬编码器进行编码.
@Override public int onVideo(byte[] data, int format) { if (!mVideoStarted)return 0; // 视频数据预处理.包括format转换\旋转等 if (format == ImageFormat.YV12 ) { JNIUtil.yV12ToYUV420P(data, mWidth, mHeight); }else{ JNIUtil.nV21To420SP(data, mWidth, mHeight); } int bufferIndex = mMediaCodec.dequeueInputBuffer(0); ... // 视频数据塞入编码器. // 下面代码用来控制帧率 if (time > 0) Thread.sleep(time / 2); return 0; }
同时,编码器线程会持续取走编码后的264格式的数据.并根据情况进行存储和推送.
@Override public void run(){ do { outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10000); ... // 一些返回值判断语句 ByteBuffer outputBuffer; .. // outputBuffer为从编码器取出的编码后的数据 outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); .. // muxer录像的处理逻辑 // 下面是获取sps pps数据. boolean sync = false; if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps sync = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; if (!sync) { byte[] temp = new byte[bufferInfo.size]; outputBuffer.get(temp); mPpsSps = temp; mMediaCodec.releaseOutputBuffer(outputBufferIndex, false); continue; } else { mPpsSps = new byte[0]; } } // 如果是关键帧,那么把sps pps拷贝到关键帧前面. if (sync) { System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length); outputBuffer.get(h264, mPpsSps.length, bufferInfo.size); mPusher.push(h264, 0, mPpsSps.length + bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 1); }else{ outputBuffer.get(h264, 0, bufferInfo.size); mPusher.push(h264, 0, bufferInfo.size, bufferInfo.presentationTimeUs / 1000, 1); if (BuildConfig.DEBUG) Log.i(TAG, String.format("push video stamp:%d", bufferInfo.presentationTimeUs / 1000)); } // 释放buffer. mMediaCodec.releaseOutputBuffer(outputBufferIndex, false); } while (mVideoStarted); }
停止
停止预览时,关闭摄像头,同时进行反初始化音频线程\编码器\muxer\TxtOverlay:public synchronized void stopPreview() { if (mCamera != null) { mCamera.stopPreview(); mCamera.setPreviewCallbackWithBuffer(null); } if (audioStream != null) { audioStream.stop(); audioStream = null; } if (mVC != null) mVC.onVideoStop(); if (overlay != null) overlay.release(); if (mMuxer != null) { mMuxer.release(); mMuxer = null; } }
在stopPreview时,编码器进行反初始化:
/** * 停止编码并释放编码资源占用 */ private void stopMediaCodec() { mMediaCodec.stop(); mMediaCodec.release(); }
软编码的处理
软编码是通过x264进行编码的.首先进行初始化,创建编码器,同时开启编码线程:
@Override public void onVideoStart(int width, int height) { this.mWidth = width; this.mHeight = height; x264 = new X264Encoder(); int bitrate = (int) (mWidth*mHeight*20*2*0.07f); x264.create(width, height, 20, bitrate/500); mVideoStarted = true; start(); }
同硬编码一致,视频数据通过onVideo回调给编码器,这里将视频数据附加上时间戳,并缓存到队列里.
这里用到了双缓冲逻辑.yuv_caches是一个空闲的yuv buffer池,如果yuv buffer池里面没有buffer,则创建新的buffer,拷贝数据,然后放到队列里.
否则在yuv buffer池里取出buffer,拷贝数据,再放到队列里.
@Override public int onVideo(byte[] data, int format) { try { .. // 帧率控制相关 // 这里用到了双缓冲逻辑.yuv_caches是一个空闲的yuv buffer池,如果yuv buffer池里面没有buffer,则创建新的buffer,拷贝数据,在放到队列里. // 否则在yuv buffer池里取出buffer,拷贝数据,再放到队列里. byte[] buffer = yuv_caches.poll(); if (buffer == null || buffer.length != data.length) { buffer = new byte[data.length]; } // 拷贝数据,放到yuv队列中 System.arraycopy(data, 0, buffer, 0, data.length); yuvs.offer(new TimedBuffer(buffer)); }catch (InterruptedException ex){ ex.printStackTrace(); } return 0; }
接下来,编码线程从队列取出yuv数据,进行编码.编码结束后将yuv数据回收到yuv buffer池
@Override public void run(){ do { // 从队列中取出yuv TimedBuffer tb = yuvs.take(); // 编码 r = x264.encode(data, 0, h264, 0, outLen, keyFrm); // 将yuv数据放到缓冲池 yuv_caches.offer(data); // 推送 mPusher.push(h264, 0, outLen[0], tb.time, 1); }while (mVideoStarted); }
在stopPreview时,对软编码器进行反初始化:
@Override public void onVideoStop() { ...// 停止编码线程 if (x264 != null) { // 关闭编码器 x264.close(); } x264 = null; }
通过持续的编码\推送数据,就可实现视频传输的功能.
EasyPusher现在已在GitHub上开源.地址:https://github.com/EasyDarwin/EasyPusher_Android
通过EasyPusher我们就可以避免接触到稍显复杂的RTSP/RTP/RTCP推送流程,只需要调用EasyPusher的几个API接口,就能轻松、稳定地把流媒体音视频数据推送给RTSP流媒体服务器进行转发和分发,EasyPusher经过长时间的企业用户检验,稳定性非常高;
下载地址
Android https://fir.im/EasyPusher![](http://www.easydarwin.org/skin/bs/images/app/EasyPusher_AN.png)
iOS https://itunes.apple.com/us/app/easypusher/id1211967057
![](http://www.easydarwin.org/skin/bs/images/app/EasyPusher_iOS.png)
获取更多信息
邮件:support@easydarwin.orgWEB:www.EasyDarwin.org
QQ交流群:587254841
Copyright © EasyDarwin.org 2012-2017
![](http://www.easydarwin.org/skin/easydarwin/images/wx_qrcode.jpg)
相关文章推荐
- EasyPusher安卓直播推流到EasyDarwin开源流媒体服务器工程简析
- EasyDarwin开源手机直播方案:EasyPusher手机直播推送,EasyDarwin流媒体服务器,EasyPlayer手机播放器
- EasyDarwin开源手机直播方案:EasyPusher手机直播推送,EasyDarwin流媒体服务器,EasyPlayer手机播放器
- EasyDarwin开源流媒体服务器提供的RTMP直播推送库
- 解决用EasyDarwin开源流媒体服务器做HLS直播时Flash Player卡住的问题
- EasyDarwin开源流媒体服务器如何实现按需推送直播的
- 解决用EasyDarwin开源流媒体服务器做HLS直播时Flash Player卡住的问题
- EasyDarwin开源手机直播方案:EasyPusher手机直播推送,EasyDarwin流媒体server,EasyPlayer手机播放器
- EasyDarwin开源流媒体服务器提供的RTMP直播推送库
- EasyDarwin开源流媒体服务器如何实现按需推送直播的
- EasyDarwin开源流媒体服务器中一种实现对作用域内new对象自动释放的方法(值得借鉴)
- EasyDarwin开源流媒体服务器支持basic基本认证和digest摘要自定义认证
- EasyDarwin开源流媒体服务器性能优化之Work-stealing优化方案
- 开源流媒体服务器EasyDarwin支持epoll网络模型,大大提升流媒体服务器网络并发性能
- 2015年度新增开源软件排名TOP 100,EasyDarwin开源流媒体服务器排名第17
- EasyDarwin开源流媒体服务器性能瓶颈分析及优化方案设计
- EasyDarwin开源流媒体服务器支持basic基本认证和digest摘要自定义认证
- EasyDarwin开源流媒体服务器性能瓶颈分析及优化方案设计
- EasyDarwin开源流媒体服务器性能瓶颈分析及优化方案设计
- EasyDarwin开源流媒体服务器高性能设计之无锁队列