EasyPusher安卓直播推流到EasyDarwin开源流媒体服务器工程简析
EasyPusher主要有三部分组件组成:采集,编码,叠加,上传.在这个基础上同时支持本地存储\后台预览的功能.主要业务模块与相关类之间的关系如图所示:
创建
首先,创建摄像头.这部分代码在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回调,这里进行硬编码的初始化.
@Overridepublic 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();}
在视频数据回调的时候,将视频帧塞入硬编码器进行编码.
@Overridepublic 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格式的数据.并根据情况进行存储和推送.
@Overridepublic 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) {// spssync = (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池
@Overridepublic void run(){do {// 从队列中取出yuvTimedBuffer 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
- iOS https://itunes.apple.com/us/app/easypusher/id1211967057
获取更多信息
邮件:support@easydarwin.org
WEB:www.EasyDarwin.org
QQ交流群:587254841
Copyright © EasyDarwin.org 2012-2017
EasyPusher安卓直播推流到EasyDarwin开源流媒体服务器工程简析相关推荐
- EasyDarwin开源流媒体服务器
主要功能特点 基于Golang开发维护: 支持Windows.Linux.macOS平台: 支持RTSP推流分发(推模式转发): 支持RTSP拉流分发(拉模式转发): 服务端录像 参考:EasyDar ...
- EasyDarwin开源流媒体服务器Golang版本:拉转推功能之拉流实现方法
EasyDarwin开源流媒体服务器(www.easydarwin.org),拉转推是一个很有意义的功能,它可将一个独立的RTSP数据源"拉"到服务器,再通过转发协议转发给多个客户 ...
- 多媒体视频开发_(6) EasyDarwin开源流媒体服务器程序搭建
EasyDarwin开源流媒体服务器程序搭建 https://winqi.cn/120.html
- EasyDarwin开源流媒体服务器性能优化之Work-stealing优化方案
本文转自EasyDarwin开源团队成员Alex的博客:http://blog.csdn.net/cai6811376/article/details/52400226 EasyDarwin团队的Ba ...
- 基于EasyDarwin开源流媒体服务器框架实现EasyNVR H5无插件直播流媒体服务器方案
背景分析 在之前的一篇博客<web无插件播放RTSP摄像机方案,拒绝插件,拥抱H5!>中,描述了实现一套H5无插件直播方案的各个组件的参考建议,又在博客<EasyNVR H5流媒体服 ...
- EasyDarwin开源流媒体服务器如何实现按需推送直播的
--本文转自EasyDarwin开源团队成员邵帅的博客:http://blog.csdn.net/ss00_2012/article/details/51441753 我们使用EasyDarwin的推 ...
- EasyDarwin开源流媒体服务器进行RTSP转发过程中将sdp由文件存储改成内存索引
-本篇由团队成员Fantasy供稿! 原始版本 在Darwin Streaming Server版本中,推送端DoAnnounce的时候后服务器会根据easydarwin.xml中配置的movies_ ...
- EasyDarwin开源流媒体服务器性能瓶颈分析及优化方案设计
EasyDarwin现有架构介绍 EasyDarwin的现有架构对网络事件的处理是这样的,每一个Socket连接在EasyDarwin内部的对应存在形式就是一个Session,不论是RTSP服务对应的 ...
- EasyDarwin开源流媒体服务器内存管理优化
-本文由EasyDarwin开源团队成员Fantasy贡献 前言 最近在linux上跑EasyDarwin发现一个很奇怪的问题,当有RTSPSession连接上来的时候,发现进程的虚拟内存映射一下就多 ...
最新文章
- 编写程序,在文件file1.dat中存入字符串“good morning”,然后将file1.dat中的内容输出到屏幕上,并复制到文件file2.dat中
- LeetCode Pascal's Triangle
- java中debug使用
- Android版CCLabelTTF在setstring时出现黑块
- .NET平台功能最强大,性能最佳的JSON库
- Android开发经典笔试面试题汇总(持续更新中)
- 来一份全面的面试宝典练练手,面试真题解析
- 解决鼠标滚动的时候多次执行函数
- jQuery----各版本
- “苹果税”猛于虎惹众怒,库克:我们是不会让步的
- 分解模式 - 按业务领域分解模式划分微服务
- cdn贝免费套餐_CDN贝网站seo
- wxPython中按钮、文本控件的简单运用
- 红帽linux卸载软件命令,好记性不如烂笔头- linux 下rpm软件的安装和卸载 rpm --force -ivh ......
- HTML如何返回上一页
- 如何自动生成表关联关系关系图 或 实体关系模型图------EER图
- 实用前端标注图片剪裁工具-AILabel.js
- mirror命令详解
- 为什么吃鸡显示连接不到服务器,为什么吃鸡进游戏显示连接不上 | 手游网游页游攻略大全...
- Vue项目原本原本http请求变成了https