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回调,这里进行硬编码的初始化.
@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


获取更多信息
QQ交流群:587254841
Copyright © EasyDarwin.org 2012-2017

EasyPusher安卓直播推流到EasyDarwin开源流媒体服务器工程简析的更多相关文章
- ffmpeg强制使用TCP方式推流到EasyDarwin开源流媒体服务器进行直播
我们的EasyDarwin目前部署在阿里云的服务器上面,运行的效果是非常好的,而且无论是以TCP方式.还是UDP的方式推送,都可以非常好地进行直播转发: 但并不是所有的用户服务器都是阿里云的形式,有很 ...
- EasyDarwin开源流媒体服务器实现RTSP直播同步输出MP4、RTMP、HLS的方案思路
背景 近期跟开源团队商量,想在EasyDarwin上继续做一些功能扩展,目前EasyDarwin开源流媒体服务器只能够实现高效的RTSP推流直播转发/分发功能,输入与输出都是RTSP/RTP流,不能够 ...
- 解决用EasyDarwin开源流媒体服务器做HLS直播时Flash Player卡住的问题
最近在开发EasyDarwin开源流媒体服务器HLS直播的时候发现一个现象:在PC上用flash player播放HLS和在ios上面播放HLS时,效果明显不同,在ios上播放非常稳定,而在flash ...
- EasyDarwin开源流媒体服务器Golang版本:服务端录像功能发布
EasyDarwin开源流媒体服务器(www.easydarwin.org)现在使用Go版本实现了.最新的代码提交,已经支持了推流(或者拉流)的同时进行本地存储. 本地存储的原理,是在推流的同时启动f ...
- NodeJS版本EasyDarwin开源流媒体服务器开发心得
title: Node版本EasyDarwin开发心得 date: 2018-03-27 22:46:15 tags: 年后着手Node版本EasyDarwin的开发工作,截止到今天2018年03月2 ...
- EasyDarwin开源流媒体服务器Golang版本:拉转推功能之拉流实现方法
EasyDarwin开源流媒体服务器(www.easydarwin.org),拉转推是一个很有意义的功能,它可将一个独立的RTSP数据源"拉"到服务器,再通过转发协议转发给多个客户 ...
- EasyDarwin开源流媒体服务器性能优化之Work-stealing优化方案
本文转自EasyDarwin开源团队成员Alex的博客:http://blog.csdn.net/cai6811376/article/details/52400226 EasyDarwin团队的Ba ...
- EasyDarwin开源流媒体服务器将select改为epoll的方法
本文来自EasyDarwin团队Fantasy(fantasy(at)easydarwin.org) 一. EasyDarwin网络模型介绍 EventContext负责监听所有网络读写事件,Even ...
- EasyDarwin开源流媒体服务器提供的TS切片/HLS直播打包库
EasyHLS Github:https://github.com/EasyDarwin/EasyHLS EasyHLS是什么? EasyHLS是EasyDarwin开源流媒体社区开发的一款HLS打 ...
随机推荐
- MR案例:CombineFileInputFormat
CombineFileInputFormat是一个抽象类.Hadoop提供了两个实现类CombineTextInputFormat和CombineSequenceFileInputFormat. 此案 ...
- linux修改单个进程的系统时间
简介 如下是 libfaketime 的一个简单实例. 在工作中常常需要测试修改时间,如果环境不允许调整时间,就要想办法调整单个进程的时间了. 编译安装 git clone https://githu ...
- python下载网页上公开数据集
URL很简单,数据集分散开在一个URL页面上,单个用手下载很慢,这样可以用python辅助下载: 问题:很多国外的数据集,收到网络波动的影响很大,最好可以添加一个如果失败就继续请求的逻辑,这里还没有实 ...
- Css常用属性单位
长度单位:px-像素 颜色单位: ①十六进制:#FFFFFF: ②颜色名称:red: ③RGB颜色代码:RGB(0-255,0-255,0-255): ...
- 解题报告:hdu1159 common consequence LCS裸题
2017-09-02 17:07:42 writer:pprp 通过这个题温习了一下刚学的LCS 代码如下: /* @theme:hdu1159 @writer:pprp @begin:17:01 @ ...
- 使用Sandcastle 基于代码注释生成接口文档
一. 工具下载: 1. Sandcastle:Sandcastle是微软官方的文档生成工具,下载地址:http://www.codeplex.com/Sandcastle 2. SHFBGuidedI ...
- 解决Eclipse Maven插件的最佳方案
最近在尝试使用GAE,要求项目必须使用Maven,于是下载了Maven3.1.1配置了环境变量.但是在为Eclipse EE安装Maven插件的时候遇到了问题. 官网的建议是: 在Eclipse EE ...
- linux环境变量 export命令详解
由host $ export DVSDK="${HOME}/ti-dvsdk_dm368-evm_xx_xx_xx_xx"引发的问题 1.${HOME}:首先, HOME 是个变量 ...
- 【Linux】使用awk批量杀进程
pkill 杀一个用户的所有进程 pkill -u bingo 批量杀进程 使用ps -ef|grep aaa 查出相关进程 使用grep -v grep 过滤掉grep本身产生的进程 使用awk 打 ...
- binding与属性
Text="{Binding Path=SearchKeyWord, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" 将“源”显 ...