开始编写基于zego的直播模块

This commit is contained in:
董宏宇
2021-02-03 16:33:05 +08:00
parent 9009c9b75a
commit cb4b3e4bb5
20 changed files with 707 additions and 105 deletions

1
.idea/gradle.xml generated
View File

@@ -25,7 +25,6 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

View File

@@ -42,6 +42,8 @@ dependencies {
implementation rootProject.ext.dependencies.androidxconstraintlayout
implementation rootProject.ext.dependencies.rxjava
implementation rootProject.ext.dependencies.rxandroid
// 从车机获取视频流
implementation 'com.zhidao.carmanager:common:1.0.23@aar'
if (Boolean.valueOf(RELEASE)) {
implementation "com.mogo.cloud:tanlu:${MOGO_TANLU_VERSION}"
@@ -49,6 +51,7 @@ dependencies {
} else {
implementation project(":modules:mogo-tanlu")
implementation project(":modules:mogo-realtime")
implementation project(":foudations:mogo-live")
}
annotationProcessor 'com.elegant.spi:compiler:1.0.3' //编译时库

View File

@@ -2,6 +2,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.cloud">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<application
android:name="com.mogo.cloud.MoGoApplication"
android:allowBackup="true"
@@ -17,29 +32,18 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".PassPortActivity"
android:label="鉴权测试">
</activity>
android:name=".PushActivity"
android:label="推送直播" />
<activity
android:name="com.mogo.cloud.network.NetworkActivity"
android:label="网络测试">
</activity>
android:label="网络测试" />
<activity
android:name=".RealTimeActivity"
android:label="实时数据测试">
</activity>
android:label="实时数据测试" />
<activity
android:name=".RoadConditionActivity"
android:label="路况服务">
</activity>
android:label="路况服务" />
</application>

View File

@@ -0,0 +1,148 @@
package com.mogo.cloud;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.appcompat.app.AppCompatActivity;
import com.mogo.cloud.util.YuvToolUtils;
import com.zhidao.manager.camera.FrameBufferCallBack;
import com.zhidao.manager.camera.ZDCameraManager;
import com.zhidao.manager.camera.ZDCameraParams;
public abstract class BaseLiveActivity extends AppCompatActivity {
private String TAG = "TestCarRecorderLiveActivity";
private String yuvSavePath = "/sdcard/Movies/TestYuvNV12.yuv";
public static int cameraId = 0; // 要打开的摄像头的ID
public int videoWidth = 1280;
public int videoHeight = 720;
// 开始直播
protected ToggleButton btnLive;
// 保存文件到本地
private ToggleButton btnSaveFile;
// 相机数据预览
protected SurfaceView surfaceView;
private ZDCameraManager zdCameraManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_push_video);
surfaceView = findViewById(R.id.surfaceView);
btnLive = findViewById(R.id.btnLive);
btnLive.setOnCheckedChangeListener((buttonView, isChecked) -> {
Toast.makeText(getApplicationContext(), buttonView.getText(), Toast.LENGTH_SHORT).show();
toggleLive(isChecked);
});
btnSaveFile = findViewById(R.id.btnSaveFile);
btnSaveFile.setOnCheckedChangeListener((btnSaveFile, isChecked) -> {
Toast.makeText(getApplicationContext(), btnSaveFile.getText(), Toast.LENGTH_SHORT).show();
YuvToolUtils.isSaveFile = isChecked;
if (isChecked) {
YuvToolUtils.connectYUVFile(yuvSavePath);
}
});
initCamer();
}
/**
* 初始化相机
*/
private void initCamer() {
zdCameraManager = ZDCameraManager.getInstance();
zdCameraManager.init(getApplicationContext());
ZDCameraParams camParam = zdCameraManager.getCameraParam();
camParam.setStoragePath("/sdcard/DCIM/");
camParam.setVideoWidth(0, videoWidth);
camParam.setVideoHeight(0, videoHeight);
zdCameraManager.setCameraParam(camParam);
// 这里是获取 YUV-NV12 格式的视频流
ZDCameraManager.getInstance().setFrontBufferCallBack(new FrameBufferCallBack() {
@Override
public void onFrame(byte[] bytes, int i) {
//Log.d(TAG, "duanmu OnFrame ,bytes:" + bytes + " i:" + i);
// 回碉给业务侧进行处理
// 这里对所有传入的YUV数据进行对应类型的转码为I420
byte[] yuv420p = YuvToolUtils.convertData(bytes, videoWidth, videoHeight, 4);
onVideoFrame(yuv420p, i);
// 保存yuv文件
if (YuvToolUtils.isSaveFile) {
try {
// 往队列里面添加数据
YuvToolUtils.queueYUV.put(yuv420p);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 这里是纯预览
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i(TAG, "addSurfaceCallBack id");
zdCameraManager.startPreview(holder, cameraId, videoWidth, videoHeight);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
zdCameraManager.stopPreview(cameraId);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (zdCameraManager != null) {
zdCameraManager.stopPreview(cameraId);
}
}
/**
* F 车机从相机获取的视频数据回调
* <p>
* TODO 一般在这里调用直播SDK的接口进行直播操作一定要先卸载 ADAS com.zhidao.autopilot
*
* @param yuv420p YUV数据 I420
* @param bytesLength 数据长度
*/
public abstract void onVideoFrame(byte[] yuv420p, int bytesLength);
/**
* 开关直播状态
*
* @param isLive true-开启直播false-关闭直播
*/
public abstract void toggleLive(boolean isLive);
/**
* 是否是静音模式
*
* @param isMute true-静音false-非静音
*/
public abstract void toggleMute(boolean isMute);
}

View File

@@ -18,6 +18,7 @@ public class MainActivity extends AppCompatActivity {
private Button btnJumpNetWorkPort;
private Button btnJumpRealTime;
private Button btnJumpRoadCondition;
private Button btnJumpPushLive;
private TextView tvSn;
private TextView tvToken;
@@ -57,6 +58,12 @@ public class MainActivity extends AppCompatActivity {
startActivity(intent);
});
btnJumpPushLive = findViewById(R.id.btnJumpPushLive);
btnJumpPushLive.setOnClickListener(v -> {
Intent intent = new Intent(MainActivity.this, PushActivity.class);
startActivity(intent);
});
MoGoAiCloudClient.getInstance().addTokenCallbacks(new IMoGoTokenCallback() {
@Override
public void onTokenGot(String token, String sn) {

View File

@@ -0,0 +1,120 @@
package com.mogo.cloud;
import android.media.AudioFormat;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import com.mogo.cloud.live.listener.ILiveProgressListener;
import com.mogo.cloud.live.manager.MGLivePushConfig;
import com.mogo.cloud.live.manager.ZeGoLiveManager;
import com.mogo.cloud.live.utils.ByteUtils;
import com.mogo.cloud.util.Devices;
/**
* 推流页面
*/
public class PushActivity extends BaseLiveActivity {
public static final String TAG = "PushActivity";
private boolean isLoginSuccess = false;
private boolean isLive = false;
private boolean isOnStart = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MGLivePushConfig mLivePushConfig = new MGLivePushConfig();
mLivePushConfig.setWidth(1280);
mLivePushConfig.setHeight(720);
mLivePushConfig.setVideoBitrate(1500);
mLivePushConfig.setVideoFPS(15);
mLivePushConfig.setAudioChannels(2);
mLivePushConfig.setAudioSampleRate(44100);
mLivePushConfig.setAudioFormat(AudioFormat.ENCODING_PCM_16BIT);
mLivePushConfig.setMute(true);
ZeGoLiveManager.getInstance().init(this.getApplication(), mLivePushConfig);
ZeGoLiveManager.getInstance().setLiveProgressListener(new ILiveProgressListener() {
@Override
public void onStart() {
Log.i(TAG, "onStart");
isOnStart = true;
}
@Override
public void onStop() {
Log.i(TAG, "onStop");
isOnStart = false;
}
@Override
public void onConnecting() {
Log.i(TAG, "onConnecting");
}
@Override
public void onConnected(String roomId) {
Log.i(TAG, "onConnected");
isLoginSuccess = true;
}
@Override
public void onDisConnect() {
Log.i(TAG, "onDisConnect");
isLoginSuccess = false;
}
@Override
public void onDebugError(int errorCode, String funcName, String errorInfo) {
Log.i(TAG, "errorCode : " + errorCode + " , funcName : " + funcName + " , errorInfo : " + errorInfo);
}
});
}
@Override
public void onVideoFrame(byte[] bytes, int bytesLength) {
if (!isLoginSuccess) {
// Log.i(TAG, "还未进房成功");
return;
}
if (!isOnStart) {
// Log.i(TAG, "还未执行onStart回调");
return;
}
if (!isLive) {
return;
}
Log.i(TAG, "onVideoFrame byte length: " + bytesLength);
ZeGoLiveManager.getInstance().startPublishingStream(ByteUtils.getBuffer(bytes, bytesLength), bytesLength, SystemClock.elapsedRealtime());
}
@Override
public void toggleLive(boolean isLive) {
this.isLive = isLive;
if (!isLoginSuccess) {
Log.i(TAG, "toggleLive isLive : " + isLive);
btnLive.setChecked(!isLive);
return;
}
Log.i(TAG, "toggleLive : " + isLive);
if (isLive) {
ZeGoLiveManager.getInstance().startPush(Devices.getSn());
} else {
ZeGoLiveManager.getInstance().stopPublish();
}
}
@Override
public void toggleMute(boolean isMute) {
}
@Override
protected void onDestroy() {
super.onDestroy();
ZeGoLiveManager.getInstance().onDestroyPublish();
}
}

View File

@@ -0,0 +1,33 @@
package com.mogo.cloud.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Devices {
private static final String PROPERTIES = "android.os.SystemProperties";
private static final String GSM_SERIAL = "gsm.serial";
private static final String GET = "get";
public static String getSn(){
return getSystemProperties(GSM_SERIAL);
}
public static String getSystemProperties(String name ) {
String value = "";
try {
Class< ? > c = Class.forName( PROPERTIES );
Method get = c.getMethod( GET, String.class );
value = (String) get.invoke( c, name );
} catch ( ClassNotFoundException var3 ) {
var3.printStackTrace();
} catch ( NoSuchMethodException var4 ) {
var4.printStackTrace();
} catch ( InvocationTargetException var5 ) {
var5.printStackTrace();
} catch ( IllegalAccessException var6 ) {
var6.printStackTrace();
}
return value;
}
}

View File

@@ -0,0 +1,197 @@
package com.mogo.cloud.util;
import android.util.Log;
import com.zhidao.libyuv.Key;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.concurrent.LinkedBlockingQueue;
/**
* YUV与RGB转换工具类
*/
public class YuvToolUtils {
private static final String TAG = "YuvToolUtils";
private static final int DST_WIDTH = 1280;
private static final int DST_HEIGHT = 720;
private static byte[] dst = null;
private static byte[] tmp = null;
/**
* @param data origin yuv data
* @param width data width
* @param height data height
* @param type 1:YV12 2:NV21 3:I420 4 nv12
*/
public static byte[] convertData(byte[] data, int width, int height, int type) {
if (dst == null) {
dst = new byte[DST_WIDTH * DST_HEIGHT * 3 / 2];
}
if (tmp == null) {
tmp = new byte[848 * 480 * 3 / 2];
}
if (type == 4) {
if (DST_WIDTH == width && DST_HEIGHT == height) {
com.zhidao.libyuv.YuvUtils.NV12ToI420(data, dst, width, height);
return dst;
} else {
com.zhidao.libyuv.YuvUtils.NV12ToI420(data, tmp, width, height);
com.zhidao.libyuv.YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
return dst;
}
} else if (type == 3) {
if (DST_WIDTH == width && DST_HEIGHT == height) {
return data;
} else {
com.zhidao.libyuv.YuvUtils.I420Scale(data, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
return dst;
}
} else if (type == 2) {
if (DST_WIDTH == width && DST_HEIGHT == height) {
com.zhidao.libyuv.YuvUtils.NV21ToI420(data, dst, width, height, false);
return dst;
} else {
com.zhidao.libyuv.YuvUtils.NV21ToI420(data, tmp, width, height, false);
com.zhidao.libyuv.YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
return dst;
}
} else if (type == 1) {
if (DST_WIDTH == width && DST_HEIGHT == height) {
swapYV12toI420(data, dst, width, height);
return dst;
} else {
swapYV12toI420(data, tmp, width, height);
com.zhidao.libyuv.YuvUtils.I420Scale(tmp, width, height, dst, DST_WIDTH, DST_HEIGHT, Key.SCALE_MODE_NONE, false);
return dst;
}
}
return new byte[0];
}
public static synchronized void release() {
dst = null;
tmp = null;
Log.d(TAG, "release 释放临时帧");
}
/**
* 将 YV12 格式转换为 I420
*
* @param yv12bytes 原始格式数据
* @param i420bytes 转出格式数据
* @param width 宽度
* @param height 高度
*/
public static void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height) {
System.arraycopy(yv12bytes, 0, i420bytes, 0, width * height);
int srcPos = width * height + width * height / 4;
// 这里利用的是 YV12 和 I420 存储特性 ,将 U 和 V 数据进行调换
System.arraycopy(yv12bytes, srcPos, i420bytes, width * height, width * height / 4);
System.arraycopy(yv12bytes, width * height, i420bytes, srcPos, width * height / 4);
}
public static ArrayList<byte[]> splitYUVI420(byte[] i420bytes, int width, int height) {
ArrayList<byte[]> yuvList = new ArrayList<>();
byte[] yData = new byte[width * height];
byte[] uData = new byte[width * height / 4];
byte[] vData = new byte[width * height / 4];
System.arraycopy(i420bytes, 0, yData, 0, width * height);
System.arraycopy(i420bytes, width * height, uData, 0, width * height / 4);
System.arraycopy(i420bytes, width * height + width * height / 4, vData, 0, width * height / 4);
yuvList.add(yData);
yuvList.add(uData);
yuvList.add(vData);
return yuvList;
}
public static LinkedBlockingQueue<byte[]> queueYUV = new LinkedBlockingQueue<>();
public static boolean isSaveFile = true;
public static void connectYUVFile(String filePath) {
Thread writeThread = new Thread(new Runnable() {
@Override
public void run() {
try {
File file = new File(filePath);
if (!file.exists()) {
boolean isOk = file.createNewFile();
if (isOk) {
Log.d(TAG, filePath + " 文件创建成功");
} else {
Log.w(TAG, filePath + " 文件已经存在");
}
}
FileOutputStream out = new FileOutputStream(filePath);
// 子线程中死循环
while (isSaveFile) {
// 拿数据
byte[] data = queueYUV.take();
// 往本地文件写入
out.write(data);
// 刷新
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
writeThread.start();
}
/**
* 写入文件
*
* @param path 文件路径
* @param data YUV 数据
* @return 是否写入成功
*/
public static boolean writeFile(String path, byte[] data) {
FileOutputStream out = null;
try {
File file = new File(path);
File parent = file.getParentFile();
if (parent != null && !parent.exists())
parent.mkdirs();
if (!file.exists()) {
boolean isOk = file.createNewFile();
if (isOk) {
out = new FileOutputStream(path);
out.write(data);
FileDescriptor fd = out.getFD();
fd.sync();
}
return true;
} else {
Log.d(TAG, path + " 文件已经存在");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
}

View File

@@ -65,6 +65,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="路况服务测试" />
<Button
android:id="@+id/btnJumpPushLive"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="直播SDK推流测试" />
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- <com.baidu.rtc.RTCVideoView-->
<!-- android:id="@+id/rtcView"-->
<!-- android:layout_width="860px"-->
<!-- android:layout_height="540px"-->
<!-- android:layout_below="@+id/surfaceView" />-->
<!-- <com.baidu.rtc.RTCVideoView-->
<!-- android:id="@+id/rtcRemoteView"-->
<!-- android:layout_width="900px"-->
<!-- android:layout_height="540px"-->
<!-- android:layout_alignParentRight="true"-->
<!-- android:layout_below="@+id/surfaceView" />-->
<LinearLayout
android:id="@+id/flTestPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<ToggleButton
android:id="@+id/btnLive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:textOff="@string/start"
android:textOn="@string/stop" />
<ToggleButton
android:id="@+id/btnSaveFile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:textOff="开始录制"
android:textOn="停止录制" />
</LinearLayout>
</RelativeLayout>

View File

@@ -1,3 +1,8 @@
<resources>
<string name="app_name">MoGoAiCloudSdk</string>
<string name="start">开始直播</string>
<string name="stop">停止直播</string>
</resources>

View File

@@ -35,8 +35,9 @@ dependencies {
implementation rootProject.ext.dependencies.androidxappcompat
implementation rootProject.ext.dependencies.live_sdk_zego
implementation 'com.zhidao.ptech:connsvr-protoco:0.1.37'
implementation 'com.google.protobuf:protobuf-java:3.10.0'
api 'com.zhidao.libyuv:libyuv:1.0.1.0'
implementation 'com.zhidao.ptech:connsvr-protoco:0.1.23'
implementation 'com.google.protobuf:protobuf-java:3.5.1'
if (Boolean.valueOf(RELEASE)) {
implementation "com.mogo.cloud:network:${MOGO_NETWORK_VERSION}"

View File

@@ -1,5 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.cloud.live">
/
<application>
<!--直播推流服务-->
<service android:name=".server.PushService" />
</application>
</manifest>

View File

@@ -1,8 +0,0 @@
package com.mogo.cloud.live;
/**
* YUV数据回调
*/
public interface IYUVDataCallback {
void onFrame(byte[] data);
}

View File

@@ -1,8 +1,8 @@
package com.mogo.cloud.live;
package com.mogo.cloud.live.listener;
/**
* 直播状态回调用
*/
public interface ILiveStatusCallback {
public interface ILiveStatusListener {
void onChange(String camId, int status);
}

View File

@@ -0,0 +1,8 @@
package com.mogo.cloud.live.listener;
/**
* YUV数据回调
*/
public interface IYUVDataListener {
void onFrame(byte[] data);
}

View File

@@ -1,37 +1,42 @@
package com.mogo.cloud.live.utils;
package com.mogo.cloud.live.manager;
import com.mogo.cloud.live.IYUVDataCallback;
import com.mogo.cloud.live.listener.IYUVDataListener;
import com.zhidao.libyuv.Key;
import com.zhidao.libyuv.YuvUtils;
import java.util.HashSet;
import java.util.Set;
public class FrameManagerUtils {
/**
* 视频帧管理
*/
public class CameraFrameManager {
private static final int DST_WIDTH = 1280;
private static final int DST_HEIGHT = 720;
byte[] dst = null;
byte[] tmp = null;
private final Set<IYUVDataCallback> globalDataListener = new HashSet<>();
private final Set<IYUVDataListener> globalDataListener = new HashSet<>();
public static FrameManagerUtils getInstance() {
public static CameraFrameManager getInstance() {
return SingletonHolder.INSTANCE;
}
private static final class SingletonHolder {
private static final FrameManagerUtils INSTANCE = new FrameManagerUtils();
private static final CameraFrameManager INSTANCE = new CameraFrameManager();
}
public void notifyYUVData(byte[] data, int width, int height, int type) {
dispatchData(handleData(data, width, height, type));
}
/*
* @param data origin yuv data
* @param width data width
/**
* @param data origin yuv data
* @param width data width
* @param height data height
* @param type 1:YV12 2:NV21 3:I420 4 nv12
* @param type 1:YV12 2:NV21 3:I420 4 nv12
*/
private byte[] handleData(byte[] data, int width, int height, int type) {
if (dst == null) {
@@ -80,13 +85,13 @@ public class FrameManagerUtils {
return new byte[0];
}
public synchronized void addYuvDataCallback(IYUVDataCallback callback) {
public synchronized void addYuvDataCallback(IYUVDataListener callback) {
if (callback != null) {
globalDataListener.add(callback);
}
}
public synchronized void rmYuvDataCallback(IYUVDataCallback callback) {
public synchronized void rmYuvDataCallback(IYUVDataListener callback) {
if (callback != null) {
globalDataListener.remove(callback);
}
@@ -99,7 +104,7 @@ public class FrameManagerUtils {
private void dispatchData(byte[] data) {
if (globalDataListener != null && globalDataListener.size() > 0) {
for (IYUVDataCallback callback : globalDataListener) {
for (IYUVDataListener callback : globalDataListener) {
if (callback != null) {
callback.onFrame(data);
}

View File

@@ -14,7 +14,6 @@ import java.util.ArrayList;
import im.zego.zegoexpress.ZegoExpressEngine;
import im.zego.zegoexpress.callback.IZegoCustomVideoCaptureHandler;
import im.zego.zegoexpress.callback.IZegoDestroyCompletionCallback;
import im.zego.zegoexpress.callback.IZegoEventHandler;
import im.zego.zegoexpress.constants.ZegoEngineState;
import im.zego.zegoexpress.constants.ZegoNetworkMode;
@@ -63,15 +62,15 @@ public class ZeGoLiveManager {
/**
* 直播会议ID使用推流端的车机信息建立房间ID这里采用格式为ROOM_ID_车机SN编号
*/
private static final String ROOM_ID_PREFIX = "ROOM_ID_";
public static final String ROOM_ID_PREFIX = "ROOM_ID_";
/**
* 直播用户名称的
*/
private static final String NAME_PREFIX = "MoGoCar_";
public static final String NAME_PREFIX = "MoGoCar_";
/**
* 直播流ID发起推送的直播流ID
*/
private static final String STREAM_ID_PREFIX = "STREAM_ID_"; //+ "_" + System.currentTimeMillis()
public static final String STREAM_ID_PREFIX = "STREAM_ID_"; //+ "_" + System.currentTimeMillis()
/**
* 定义 即构SDK 引擎对象
*/
@@ -160,6 +159,8 @@ public class ZeGoLiveManager {
customVideoCaptureConfig = new ZegoCustomVideoCaptureConfig();
// 设置自定义视频采集视频帧数据类型
customVideoCaptureConfig.bufferType = ZegoVideoBufferType.RAW_DATA;
// true 表示静音(关闭)
mExpressEngine.muteMicrophone(true);
// 开始或停止自定义视频采集,支持设置其他通道的推流
mExpressEngine.enableCustomVideoCapture(true, customVideoCaptureConfig, ZegoPublishChannel.MAIN);
// 设置自定义视频采集回调
@@ -327,7 +328,7 @@ public class ZeGoLiveManager {
* @param userId 当前用户ID
* @param roomId 要进入的房间ID
*/
private void loginRoom(String userId, String roomId) {
public void loginRoom(String userId, String roomId) {
currentRoomId = ROOM_ID_PREFIX + roomId;
ZegoUser zegoUser = new ZegoUser(userId, NAME_PREFIX + userId);
mExpressEngine.loginRoom(currentRoomId, zegoUser);
@@ -368,6 +369,7 @@ public class ZeGoLiveManager {
*/
public void stopLive(String streamId) {
mExpressEngine.stopPlayingStream(streamId);
stopPreview();
}
/**
@@ -418,7 +420,6 @@ public class ZeGoLiveManager {
* 停止推送
*/
public void stopPublish() {
stopPreview();
stopPublishingStream();
}
@@ -434,6 +435,7 @@ public class ZeGoLiveManager {
* 停止观看直播
*/
public void onDestroyLive() {
stopPreview();
logOutRoom();
destroyEngine();
}

View File

@@ -1,25 +1,27 @@
package com.mogo.cloud.live.push;
package com.mogo.cloud.live.server;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioFormat;
import android.os.IBinder;
import android.text.TextUtils;
import android.os.SystemClock;
import android.util.Log;
import com.mogo.cloud.live.IYUVDataCallback;
import com.mogo.cloud.live.listener.IYUVDataListener;
import com.mogo.cloud.live.manager.CameraFrameManager;
import com.mogo.cloud.live.manager.MGLivePushConfig;
import com.mogo.cloud.live.manager.ZeGoLiveManager;
import com.mogo.cloud.live.utils.ByteUtils;
public class PushService extends Service implements IYUVDataCallback {
public class PushService extends Service implements IYUVDataListener {
public static final String ACTION_START_RTMP_PUSH = "action_start_rtmp_push";
public static final String ACTION_STOP_RTMP_PUSH = "action_stop_rtmp_push";
private static final String TAG = "PushService";
private static final String RTMP_URL = "rtmp_url";
private static final String DEVICES_ID = "devices_id";
private static final String CAM_ID = "cam_id";
private static final int WIDTH = 1280;
@@ -29,12 +31,24 @@ public class PushService extends Service implements IYUVDataCallback {
private MGLivePushConfig mLivePushConfig;
private ZeGoLiveManager mLivePusher;
private static volatile String mRtmpUrl;
/**
* 当前设备ID作为推流端userID==roomId==streamID
*/
private volatile String mDevicesId;
public static void startService(Context context, String action, String rtmpurl, String camId) {
/**
* 启动服务
*
* @param context 上下文
* @param action 动作标志
* @param devicesId 设备ID
* @param camId 摄像头
*/
public static void startService(Context context, String action,
String devicesId, String camId) {
Intent intent = new Intent(context, PushService.class);
intent.setAction(action);
intent.putExtra(RTMP_URL, rtmpurl);
intent.putExtra(DEVICES_ID, devicesId);
intent.putExtra(CAM_ID, camId);
context.startService(intent);
}
@@ -44,8 +58,8 @@ public class PushService extends Service implements IYUVDataCallback {
super.onCreate();
Log.d(TAG, "初始化推流服务……");
mLivePushConfig = new MGLivePushConfig();
mLivePushConfig.setWidth(1280);
mLivePushConfig.setHeight(720);
mLivePushConfig.setWidth(WIDTH);
mLivePushConfig.setHeight(HEIGHT);
mLivePushConfig.setVideoBitrate(1500);
mLivePushConfig.setVideoFPS(15);
mLivePushConfig.setAudioChannels(2);
@@ -54,7 +68,7 @@ public class PushService extends Service implements IYUVDataCallback {
mLivePushConfig.setMute(true);
mLivePusher = ZeGoLiveManager.getInstance();
mLivePusher.init(getApplication(),mLivePushConfig);
mLivePusher.init(getApplication(), mLivePushConfig);
}
@Override
@@ -62,13 +76,13 @@ public class PushService extends Service implements IYUVDataCallback {
if (intent != null) {
if (ACTION_START_RTMP_PUSH.equals(intent.getAction())) {
try {
String url = intent.getStringExtra(RTMP_URL);
startRtmpPush(url);
String devicesId = intent.getStringExtra(DEVICES_ID);
startPush(devicesId);
} catch (Exception e) {
e.printStackTrace();
}
} else if (ACTION_STOP_RTMP_PUSH.equals(intent.getAction())) {
stopRtmpPush();
stopPush();
}
}
return super.onStartCommand(intent, flags, startId);
@@ -79,54 +93,39 @@ public class PushService extends Service implements IYUVDataCallback {
return null;
}
private void startRtmpPush(String url) {
if (mRtmpUrl != null && mRtmpUrl.equals(url)
/**
* 启动发布直播视频流
*
* @param devicesId 设备ID
*/
private void startPush(String devicesId) {
if (mDevicesId != null && mDevicesId.equals(devicesId)
&& mLivePusher != null && mLivePusher.isPushing()) {
return;
}
mRtmpUrl = url;
String rtmpUrl = null;
if (!TextUtils.isEmpty(mRtmpUrl)) {
String url1[] = mRtmpUrl.split("###");
if (url1.length > 0) {
rtmpUrl = url1[0];
}
}
if (TextUtils.isEmpty(rtmpUrl) || (!rtmpUrl.trim().toLowerCase().startsWith("rtmp://"))) {
//TODO rtmp url 地址有误
Log.e(TAG, "rtmp url 地址有误");
return;
}
//int customModeType = 0;
//customModeType |= TXLiveConstants.CUSTOM_MODE_VIDEO_CAPTURE;
//mLivePusher.setVideoQuality(TXLiveConstants.VIDEO_QUALITY_HIGH_DEFINITION, true, true);
//mLivePushConfig.setVideoResolution(TXLiveConstants.VIDEO_RESOLUTION_TYPE_1280_720);
//mLivePushConfig.setAutoAdjustBitrate(false);
//mLivePushConfig.setHomeOrientation(TXLiveConstants.VIDEO_ANGLE_HOME_LEFT);
//mLivePushConfig.setVideoBitrate(900);
//mLivePushConfig.setVideoEncodeGop(1);
//mLivePushConfig.setVideoFPS(15);
//mLivePushConfig.setCustomModeType(customModeType);
FrameManagerUtils.getInstance().addYuvDataCallback(this);
mLivePusher.startPusher(rtmpUrl.trim());
Log.d(TAG, "startRtmpPush :" + rtmpUrl);
mDevicesId = devicesId;
// 注册视频YUV回调监听
CameraFrameManager.getInstance().addYuvDataCallback(this);
// 登录房间
mLivePusher.loginRoom(mDevicesId, mDevicesId);
// 开始发布
mLivePusher.startPush(mDevicesId);
Log.d(TAG, "startPush :mRoomId=" + mDevicesId + " mDevicesId=" + mDevicesId);
}
private void stopRtmpPush() {
/**
* 结束发布直播视频流
*/
private void stopPush() {
try {
mRtmpUrl = "";
mDevicesId = "";
// 是否处于发布状态
if (mLivePusher.isPushing()) {
mLivePusher.stopPusher();
// 停止发布
mLivePusher.stopPublish();
}
FrameManagerUtils.getInstance().rmYuvDataCallback(this);
// 移除视频回碉监听
CameraFrameManager.getInstance().rmYuvDataCallback(this);
} catch (Exception e) {
e.printStackTrace();
}
@@ -135,8 +134,10 @@ public class PushService extends Service implements IYUVDataCallback {
@Override
public void onFrame(byte[] data) {
if (mLivePusher != null && mLivePusher.isPushing() && data != null) {
int ret = mLivePusher.sendCustomVideoData(data, 3, WIDTH, HEIGHT);
if (BuildConfig.DEBUG) Log.d(TAG, "onFrame push ret is:" + ret);
// 将YUV数据发布到即构
ZeGoLiveManager.getInstance().startPublishingStream(
ByteUtils.getBuffer(data, data.length), data.length,
SystemClock.elapsedRealtime());
}
}
}

View File

@@ -0,0 +1,17 @@
package com.mogo.cloud.live.utils;
import java.nio.ByteBuffer;
public class ByteUtils {
private static ByteBuffer byteBuffer;
public static ByteBuffer getBuffer(byte[] bytes, int length) {
if (byteBuffer == null) {
byteBuffer = ByteBuffer.allocateDirect(length);
}
byteBuffer.put(bytes);
byteBuffer.flip();
return byteBuffer;
}
}