EasyPusher主要有三部分组件组成:采集,编码,叠加,上传.在这个基础上同时支持本地存储\后台预览的功能.主要业务模块与相关类之间的关系如图所示:

Created with Raphaël 2.1.0 StreamActivity StreamActivity 摄像头线程 摄像头线程 BackgroundCameraService BackgroundCameraService 编码线程 编码线程 Pusher Pusher Muxer Muxer 音频线程 音频线程 音频编码线程 音频编码线程 TxtOverlay TxtOverlay 显示在主界面 后台预览 提供摄像头数据 发送编码后的数据 本地存储 创建\销毁音频(依附于视频线程) 发送编码后的数据 本地存储 提供水印叠加

创建

首先,创建摄像头.这部分代码在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开源流媒体服务器工程简析相关推荐

  1. EasyDarwin开源流媒体服务器

    主要功能特点 基于Golang开发维护: 支持Windows.Linux.macOS平台: 支持RTSP推流分发(推模式转发): 支持RTSP拉流分发(拉模式转发): 服务端录像 参考:EasyDar ...

  2. EasyDarwin开源流媒体服务器Golang版本:拉转推功能之拉流实现方法

    EasyDarwin开源流媒体服务器(www.easydarwin.org),拉转推是一个很有意义的功能,它可将一个独立的RTSP数据源"拉"到服务器,再通过转发协议转发给多个客户 ...

  3. 多媒体视频开发_(6) EasyDarwin开源流媒体服务器程序搭建

    EasyDarwin开源流媒体服务器程序搭建 https://winqi.cn/120.html

  4. EasyDarwin开源流媒体服务器性能优化之Work-stealing优化方案

    本文转自EasyDarwin开源团队成员Alex的博客:http://blog.csdn.net/cai6811376/article/details/52400226 EasyDarwin团队的Ba ...

  5. 基于EasyDarwin开源流媒体服务器框架实现EasyNVR H5无插件直播流媒体服务器方案

    背景分析 在之前的一篇博客<web无插件播放RTSP摄像机方案,拒绝插件,拥抱H5!>中,描述了实现一套H5无插件直播方案的各个组件的参考建议,又在博客<EasyNVR H5流媒体服 ...

  6. EasyDarwin开源流媒体服务器如何实现按需推送直播的

    --本文转自EasyDarwin开源团队成员邵帅的博客:http://blog.csdn.net/ss00_2012/article/details/51441753 我们使用EasyDarwin的推 ...

  7. EasyDarwin开源流媒体服务器进行RTSP转发过程中将sdp由文件存储改成内存索引

    -本篇由团队成员Fantasy供稿! 原始版本 在Darwin Streaming Server版本中,推送端DoAnnounce的时候后服务器会根据easydarwin.xml中配置的movies_ ...

  8. EasyDarwin开源流媒体服务器性能瓶颈分析及优化方案设计

    EasyDarwin现有架构介绍 EasyDarwin的现有架构对网络事件的处理是这样的,每一个Socket连接在EasyDarwin内部的对应存在形式就是一个Session,不论是RTSP服务对应的 ...

  9. EasyDarwin开源流媒体服务器内存管理优化

    -本文由EasyDarwin开源团队成员Fantasy贡献 前言 最近在linux上跑EasyDarwin发现一个很奇怪的问题,当有RTSPSession连接上来的时候,发现进程的虚拟内存映射一下就多 ...

最新文章

  1. 编写程序,在文件file1.dat中存入字符串“good morning”,然后将file1.dat中的内容输出到屏幕上,并复制到文件file2.dat中
  2. LeetCode Pascal's Triangle
  3. java中debug使用
  4. Android版CCLabelTTF在setstring时出现黑块
  5. .NET平台功能最强大,性能最佳的JSON库
  6. Android开发经典笔试面试题汇总(持续更新中)
  7. 来一份全面的面试宝典练练手,面试真题解析
  8. 解决鼠标滚动的时候多次执行函数
  9. jQuery----各版本
  10. “苹果税”猛于虎惹众怒,库克:我们是不会让步的
  11. 分解模式 - 按业务领域分解模式划分微服务
  12. cdn贝免费套餐_CDN贝网站seo
  13. wxPython中按钮、文本控件的简单运用
  14. 红帽linux卸载软件命令,好记性不如烂笔头- linux 下rpm软件的安装和卸载 rpm --force -ivh ......
  15. HTML如何返回上一页
  16. 如何自动生成表关联关系关系图 或 实体关系模型图------EER图
  17. 实用前端标注图片剪裁工具-AILabel.js
  18. mirror命令详解
  19. 为什么吃鸡显示连接不到服务器,为什么吃鸡进游戏显示连接不上 | 手游网游页游攻略大全...
  20. Vue项目原本原本http请求变成了https

热门文章

  1. window计算机日志分析详解,windows系统日志分析
  2. CF 853A Planning
  3. 前端面试——跨域问题
  4. 【JS 逆向百例】某空气质量监测平台无限 debugger 以及数据动态加密
  5. C#中DllImport使用法汇总
  6. QQ未接收文件短期找回
  7. mysql附录建表_用CREATE TABLE 语句创建数据表
  8. 字节数组 Byte[]
  9. windows卸载qt_在Qt中,如何使用QSettings创建/修改/删除Windows注册表项/值?
  10. 语音情绪辨识方法与流程