Android开发视频通话怎么实现?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发视频通话怎么实现?相关的知识,希望对你有一定的参考价值。

/**
* android视频聊天
* 1、初始化SDK 2、连接服务器、 3、用户登录;4、进入房间;5、打开本地视频;6、请求对方视频
*/
public class VideoChatActivity extends Activity implements AnyChatBaseEvent

private AnyChatCoreSDK anychat; // 核心SDK
private SurfaceView remoteSurfaceView; // 对方视频
private SurfaceView localSurfaceView; // 本地视频
private ConfigEntity configEntity;
private boolean bSelfVideoOpened = false; // 本地视频是否已打开
private boolean bOtherVideoOpened = false; // 对方视频是否已打开
private TimerTask mTimerTask; // 定时器
private Timer mTimer = new Timer(true);
private Handler handler; // 用Handler来不间断刷新即时视频
private List<String> userlist = new ArrayList<String>();//保存在线用户列表
private int userid; // 用户ID
@Override
public void onCreate(Bundle savedInstanceState)

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_chat);
remoteSurfaceView = (SurfaceView) findViewById(R.id.surface_remote);
localSurfaceView = (SurfaceView) findViewById(R.id.surface_local);
configEntity = ConfigService.LoadConfig(this);//加载视频通话设置
loginSystem();// 初始化SDK 连接服务器
mTimerTask = new TimerTask()
public void run()
Message mesasge = new Message();
handler.sendMessage(mesasge);

;
mTimer.schedule(mTimerTask, 1000, 100);
handler = new Handler()
@Override
public void handleMessage(Message msg)
VideoChat();// 不间断显示即时视频通话画面
super.handleMessage(msg);

;

// 初始化SDK 连接服务器
private void loginSystem()
if (anychat == null)
anychat = new AnyChatCoreSDK();
anychat.SetBaseEvent(this); // 设置基本事件回调函数
if (configEntity.useARMv6Lib != 0) // 使用ARMv6指令集
anychat.SetSDKOptionInt(AnyChatDefine.
BRAC_SO_CORESDK_USEARMV6LIB, 1);
anychat.InitSDK(android.os.Build.VERSION.SDK_INT, 0); // 初始化SDK

anychat.Connect("demo.anychat.cn", 8906);// 连接服务器

// 显示即时视频通话画面
public void VideoChat()
if (!bOtherVideoOpened)
if (anychat.GetCameraState(userid) == 2
&& anychat.GetUserVideoWidth(userid) != 0)
SurfaceHolder holder = remoteSurfaceView.getHolder();
holder.setFormat(PixelFormat.RGB_565);
holder.setFixedSize(anychat.GetUserVideoWidth(userid),
anychat.GetUserVideoHeight(userid));
Surface s = holder.getSurface(); // 获得视频画面
anychat.SetVideoPos(userid, s, 0, 0, 0, 0); // 调用API显示视频画面
bOtherVideoOpened = true;

if (!bSelfVideoOpened)
if (anychat.GetCameraState(-1) == 2
&& anychat.GetUserVideoWidth(-1) != 0)
SurfaceHolder holder = localSurfaceView.getHolder();
holder.setFormat(PixelFormat.RGB_565);
holder.setFixedSize(anychat.GetUserVideoWidth(-1),
anychat.GetUserVideoHeight(-1));
Surface s = holder.getSurface();
anychat.SetVideoPos(-1, s, 0, 0, 0, 0);
bSelfVideoOpened = true;



public void OnAnyChatConnectMessage(boolean bSuccess)
if (!bSuccess)
Toast.makeText(VideoChatActivity.this, "连接服务器失败,自动重连,请稍后...", Toast.LENGTH_SHORT).show();

anychat.Login("android", ""); // 服务器连接成功 用户登录

public void OnAnyChatLoginMessage(int dwUserId, int dwErrorCode)
if (dwErrorCode == 0)
Toast.makeText(this, "登录成功!", Toast.LENGTH_SHORT).show();
anychat.EnterRoom(1, ""); // 用户登录成功 进入房间
ApplyVideoConfig();
else
Toast.makeText(this, "登录失败,错误代码:" + dwErrorCode, Toast.LENGTH_SHORT).show();


public void OnAnyChatEnterRoomMessage(int dwRoomId, int dwErrorCode)
if (dwErrorCode == 0) // 进入房间成功 打开本地音视频
Toast.makeText(this, "进入房间成功", Toast.LENGTH_SHORT).show();
anychat.UserCameraControl(-1, 1); // 打开本地视频
anychat.UserSpeakControl(-1, 1); // 打开本地音频
else
Toast.makeText(this, "进入房间失败,错误代码:" + dwErrorCode, Toast.LENGTH_SHORT).show();


public void OnAnyChatOnlineUserMessage(int dwUserNum, int dwRoomId)
if (dwRoomId == 1)
int user[] = anychat.GetOnlineUser();
if (user.length != 0)
for (int i = 0; i < user.length; i++)
userlist.add(user[i]+"");
.
String temp =userlist.get(0);
userid = Integer.parseInt(temp);
anychat.UserCameraControl(userid, 1);// 请求用户视频
anychat.UserSpeakControl(userid, 1); // 请求用户音频

else
Toast.makeText(VideoChatActivity.this, "当前没有在线用户", Toast.LENGTH_SHORT).show();



public void OnAnyChatUserAtRoomMessage(int dwUserId, boolean bEnter)
if (bEnter) //新用户进入房间
userlist.add(dwUserId+"");

else //用户离开房间
if (dwUserId == userid)

Toast.makeText(VideoChatActivity.this, "视频用户已下线", Toast.LENGTH_SHORT).show();
anychat.UserCameraControl(userid, 0);// 关闭用户视频
anychat.UserSpeakControl(userid, 0); // 关闭用户音频
userlist.remove(userid+""); //移除该用户
if (userlist.size() != 0)

String temp =userlist.get(0);
userid = Integer.parseInt(temp);
anychat.UserCameraControl(userid, 1);// 请求其他用户视频
anychat.UserSpeakControl(userid, 1); // 请求其他用户音频


141. else
userlist.remove(dwUserId+""); //移除该用户



public void OnAnyChatLinkCloseMessage(int dwErrorCode)
Toast.makeText(VideoChatActivity.this, "连接关闭,error:" + dwErrorCode, Toast.LENGTH_SHORT).show();

@Override
protected void onDestroy() //程序退出
anychat.LeaveRoom(-1); //离开房间
anychat.Logout(); //注销登录
anychat.Release(); //释放资源
mTimer.cancel();
super.onDestroy();

// 根据配置文件配置视频参数
private void ApplyVideoConfig()
if (configEntity.configMode == 1) // 自定义视频参数配置

// 设置本地视频编码的码率(如果码率为0,则表示使用质量优先模式)
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_BITRATECTRL,configEntity.videoBitrate);
if (configEntity.videoBitrate == 0)

// 设置本地视频编码的质量
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_QUALITYCTRL,configEntity.videoQuality);

// 设置本地视频编码的帧率
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_FPSCTRL,configEntity.videoFps);
// 设置本地视频编码的关键帧间隔
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_GOPCTRL,configEntity.videoFps * 4);
// 设置本地视频采集分辨率
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_WIDTHCTRL,configEntity.resolution_width);
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_HEIGHTCTRL,configEntity.resolution_height);
// 设置视频编码预设参数(值越大,编码质量越高,占用CPU资源也会越高)
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_PRESETCTRL,configEntity.videoPreset);

// 让视频参数生效
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_APPLYPARAM,configEntity.configMode);
// P2P设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_NETWORK_P2PPOLITIC,configEntity.enableP2P);
// 本地视频Overlay模式设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_OVERLAY,configEntity.videoOverlay);
// 回音消除设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_AUDIO_ECHOCTRL,configEntity.enableAEC);
// 平台硬件编码设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_CORESDK_USEHWCODEC,configEntity.useHWCodec);
// 视频旋转模式设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_ROTATECTRL,configEntity.videorotatemode);
// 视频平滑播放模式设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_STREAM_SMOOTHPLAYMODE,configEntity.smoothPlayMode);
// 视频采集驱动设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_CAPDRIVER,configEntity.videoCapDriver);
// 本地视频采集偏色修正设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_LOCALVIDEO_FIXCOLORDEVIA,configEntity.fixcolordeviation);
// 视频显示驱动设置
anychat.SetSDKOptionInt(AnyChatDefine.BRAC_SO_VIDEOSHOW_DRIVERCTRL,configEntity.videoShowDriver);

参考技术A Android开发视频通话实现方式可以直接接入zego sdk,快速实现实时视频通话功能,设备终端底层语音视频接口集成,可广泛适用于各个行业。

Android技术分享| 视频通话开发流程

多人呼叫

多人呼叫与点对点呼叫区别在于多人呼叫是一次呼叫1个以上的人,中途也可以再呼叫邀请别人加入通话。 整个呼叫的流程跟点对点呼叫类似,但也有些区别,需要添加额外的 API 逻辑来实现功能。下面我们分主叫被叫两种角色来分析。

主叫

发起呼叫

创建多个LocalInvitation 对象

val callArray = arrayOf("1234","5678","8888")
callArray.forEach 
  val localInvitation = rtmCallManager.createLocalInvitation(it)
  localInvitation.setContent("自定义的消息体")

//通常我们都会选择发送json格式字符串,例如
"mediaType":0 //0视频 1 音频
"isMeeting":0 //0 多人模式 1 p2p
"rtcChannelId":"10000"//⚠️这个参数尤为重要,这个参数通常都由主叫生成,在呼叫的时候带给被叫。该参数的作用是告诉对方我们这次呼叫将进入哪个 RTC 的频道,两个人进入同一个 RTC 频道时,音视频才会通。
"callUsers":callArray
 //发送呼叫
  rtmCallManager.sendLocalInvitation(localInvitation, null)

多人呼叫跟和点对点呼叫一样,先创建 LocalInvitation,并且设置好必要的参数,只不过多人是需要创建多个,在这里我在自定义消息体里多加了一个 callUsers,将所有呼叫的人userId都带过去。这个可以让收到呼叫的人知道有哪几个人参与本次通话,并且可以根据这个数据提前加载好占位的View 并展示Loading动画,接通后并且通过接收该视频第一针回调后取消Loading动画。

这里需要注意的是要将创建好的所有 LocalInvitation放集合中保存,后面取消呼叫,中途邀请人都需要用。

加入RTM频道

这一步尤为重要,先说下加入 RTM 频道的作用。

主叫在发起多人呼叫后,紧接着应该加入频道,并且收到呼叫的人应该也第一时间加入RTM频道。之所以要这么做,是因为我们可以通过RTM 频道几个相当实用的回调,实现人员的添加移除管理。

//频道内总人数更新
void onMemberCountUpdated(int var1);

//有人加入频道
void onMemberJoined(RtmChannelMember var1);

//有人离开频道
void onMemberLeft(RtmChannelMember var1);

onMemberLeft:前面我们已经知道,被叫收到呼叫,不论同意与否,都会会第一时间加入频道。如果被叫拒绝的话,不仅需要调用 rejectRemoteInvitation 还需要调用RTM channel的 Leave方法。调了 Leave ,所有加入 RTM 频道的成员都能知道这个用户离开了,不在需要额外的消息通知。

onMemberJoined:人员加入频道的回调,这个适用于如果中途邀请了别人加入通话,频道内的其他人同样会收到该用户加入 RTM 频道的回调,这时候就判断该用户是否有本地画面,如果没有可以在该回调添加这个人的视图。

onMemberCountUpdated:当RTM频道仅剩1个人的时候,这个时候就可以看作本次通话已经结束,执行相应的逻辑即可。

挂断
  1. 如果还有未接听的人员,主叫挂断需遍历 LocalInvitation 集合,再调用 cancelLocalInvitation 取消每一个呼叫对象。已接听的记得在LocalInvitation 集合中移除。如果业务逻辑是不区分主叫被叫,那么就不需要调用cancelLocalInvitation,即使发起人挂断,其他人还在的话,仍可以选择同意或拒绝加入通话。
  2. 调用 RTM 频道的 Leave ,其他人会收到 onMemberLeft,移除对应的视图。
异常处理
  1. 有人不在线

    可以过滤不在线的用户,通过 queryPeersOnlineStatus查询对方状态。

  2. 接听超时

    被叫迟迟不接听,会回调 onLocalInvitationFailure方法,在这里将其移除。

  3. 有人异常断线

    被叫接听后异常断线,前面可知,收到呼叫就会加入 RTM 频道,如果异常断线后,其他人会收到 onMemberLeft回调,可在这里将其移除。

被叫

收到呼叫

在收到呼叫回调 onRemoteInvitationReceived后,首先要做的是 加入 RTM 频道,这样可以方便其他人处理逻辑,其次,主叫在创建 LocalInvitation对象的时候,加入了callUsers 字端,里面包含本次呼叫的所有人员信息。所以我们可以在此解析,并且保存下来,用于后面页面展示等其他业务逻辑。

同意

调用rtmCallManager.acceptRemoteInvitation(it, remoteInvitation)同意后,直接遍历 callUsers 在页面上展示每个人的Loading视图,再加入 RTC频道,等收到其他人加入 RTC后取消Loading即可。

拒绝

调用rtmCallManager.refuseRemoteInvitation(it, remoteInvitation)的同时,也需要离开加入的 RTM 频道

挂断
  1. 未接听的情况下,先拒绝 refuseRemoteInvitation 本次呼叫邀请。
  2. 多人呼叫挂断无需再发送信令通知其他人,只需要离开之前加入的 RTM 频道,其他人即可 通过 onMemberLeft知道这个人挂断离开。
其他人离开

任何人离开都会先退出 RTM 频道,所以在 onMemberLeft里移除对应人员即可。

异常处理

其他用户一直未接听:

在 RTM SDK中,一个呼叫邀请的有效时间是60秒,所以,在展示其他人视图的时候,为其设置一个倒计时。期间,如果他 加入了RTC频道(意味着他同意了呼叫),就取消倒计时,隐藏Loading,展示他的视频即可。

如果倒计时结束还未收到他加入RTC的回调,直接移除。

总结

以上就是多人呼叫的大概流程,其中需要注意的地方就是,收到呼叫或者发起呼叫都要先加入 RTM 频道,

RTM频道的ID由主叫创建,被叫在 RemoteInvitation 中解析。挂断需离开RTM频道。

基于上述流程,开发者可以根据该流程并参考DEMO快速实现多人音视频呼叫通话。

以上是关于Android开发视频通话怎么实现?的主要内容,如果未能解决你的问题,请参考以下文章

Android技术分享| 视频通话开发流程

Android技术分享| 视频通话开发流程

Android技术分享| 安卓3行代码,实现整套音视频通话功能

Android端WebRTC音视频通话录音-获取音频输出数据

关于基于Linphone的视频通话Android端开发过程中遇到的问题

基于Linphone开发Android音视频通话