Android视频直播开发总结

最近需要实现一对多视频直播功能,团队没有搭建视频直播平台的能力,没办法只能借助第三方开放平台来实现。将整个开发历程记录下来,以便后续查看。

1 平台选择


对于视频直播一开始也不清楚,没做过类似功能,不过相关开放平台应该不少。某度搜索视频直播SDK,豁。。。百度、腾讯、环信、融云、七牛、阿里、网易、声网。。。一大堆,乍一看颇有百花齐放的赶脚。甭管大厂小厂、有名无名,只要是个开放平台都得提供视频直播功能,不然咋个讲故事,咋个融资呢。哎没办法,一个一个看一个一个比对呗。首先还是得从大厂下手,毕竟实力摆在那儿,看了下BAT的文档还是算了,实力雄厚但就是搞不出一款叫得上名的直播软件,果然还是有原因滴。再看看环信、融云、七牛,搞IM、储存的也来插一脚,也时醉了。最后左挑右选选中了声网,算得上基于自己技术优势搞出来的开放直播平台,网上相关文章不少评价也还不错,先试试看吧。。。

2 功能集成


娱乐一下,说个笑话:声网首页介绍说“4行代码 30分钟在应用内构建 视频通话语音通话互动直播”。

看了下声网视频互动直播SDK文档还算清晰明了,马上开车,哦打错了马上开干,毕竟千里之行、路在键盘上。。。

先贴一张官方文档的时序图,算是比较清晰的描述了App和Sdk的交互时序:


Android视频直播开发总结_第1张图片
image

2.1 DEMO


一个简单的视频直播Demo按以下几个步骤就可以实现了,可以找几个Android设备run一下看看效果,还是相当easy滴。

Step1 SDK集成

SDK还好支持maven依赖,在build.gradle的dependencies模块中加一行就行:

dependencies {
      ...   
      implementation 'io.agora.rtc:full-sdk:2.8.1'
}

Step2 直播引擎创建

SDK有个重要的类RtcEngine,负责直播功能管理,提供了上/下线、状态监听、音/视频设置等比较丰富的Api,碰到问题时,首先查这个类就对了。创建引擎时APP_ID参数为声网开发平台创建的应用id,应用创建文档

private RtcEngine mRtcEngine;

try {
    mRtcEngine = RtcEngine.create(context, LiveDefine.APP_ID, mRtcEventHandler);
    mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_COMMUNICATION);
    mRtcEngine.enableAudio(); // 开启音频功能
    mRtcEngine.enableVideo(); // 开启视频功能
} catch (Exception e) {
    e.printStackTrace();
}

Step3 直播View关联

角色有主播和观众区分,关联View时有些许区别。ANCHOR_UID为主播用户id,主播端关联View时为自己的用户id,观众端关联view时为观看的主播的用户id。用户系统需要应用自己管理,声网SDK不提供用户管理。

SurfaceView surface = RtcEngine.CreateRendererView(this);
// 主播端View关联
mRtcEngine.setClientRole(Constants.CLIENT_ROLE_BROADCASTER);
mRtcEngine.enableLocalAudio(true); // 主播端需要打开本地音频
mRtcEngine.setupLocalVideo(new VideoCanvas(surface, VideoCanvas.RENDER_MODE_HIDDEN, ANCHOR_UID)); // 主播端设置的是本地video,因为主播是将画面分享给远端的
mRtcEngine.startPreview();  // 主播需要开启视频预览

// 观众端View关联
mRtcEngine.setClientRole(Constants.CLIENT_ROLE_AUDIENCE);
mRtcEngine.enableLocalAudio(false); // 观众端不需要打开本地音频
mRtcEngine.setupRemoteVideo(new VideoCanvas(surface, VideoCanvas.RENDER_MODE_HIDDEN, ANCHOR_UID)); // 观众端设置的是远端即主播video,因为观众是看的主播画面

Step4 加入房间

加入房间时第一个参数token为当前登录账户对应的token,应用自己管理,测试时可从传空。第二个参数为频道id,也是由应用自己管理的。第三个参数为频道名称。最后一个参数为当前登录的账户id

mRtcEngine.joinChannel("", CHANNEL_ID, "CHANNEL_NAME", uid);

Step5 离开房间

// 主播端离开
mRtcEngine.setupLocalVideo(null);
mRtcEngine.stopPreview();
mRtcEngine.leaveChannel();

// 观众端离开
mRtcEngine.setupRemoteVideo(null);
mRtcEngine.leaveChannel();

2.2 消息功能


直播房间消息功能可以说是相对基础而简单的了,然而万万没想到啊,就是这么个本以为很简单的功能却花费了不少的时间,真是人生处处有惊喜呀。到底怎么回事呢,声网视频互动直播SDK根本就没有消息通道,对你没看错就是没有。一开始我以为是我没找到而已,直到我翻遍了SDK的所有类和官网文档,才确认真的是没有,这TA瞄的就很尴尬了。。。这可咋整!这不成鸡肋了么。

后来一想不对啊,这么基础的功能声网平台不可能没考虑到,又去细细翻了下官网文档,然后我在插件分类里面找到了个实时消息SDK,这是一个独立的工具类SDK,声网将实时消息功能解耦出来,可以给各个场景提供消息支持。当然这样设计是非常好的,独立通用不影响其他功能,但好歹你在视频互动直播SDK文档里面提一下呀,要不是碰到像我这样细心认真不认输长得帅的人o(* ̄︶ ̄*)o,可能就选择其他平台了。。。

好了废话不多说,SDK支持点对点的实时消息,也支持群聊实时消息,官方接入文档。群聊实时消息可参考如下步骤:

Step1 依赖配置

dependencies {
      ...   
      implementation 'io.agora.rtm:rtm-sdk:1.0.1'
}

Step2 消息引擎创建

// APP_ID同视频互动SDK保持一致即可
private RtmClient mRtmClient;
mRtmClient = RtmClient.createInstance(context, LiveDefine.APP_ID, listener);

Step3 房间消息初始化

创建一个消息频道前需要调一次登录操作,第一个参数为应用账户token,第二个参数为账户标识。

mRtmClient.login("", userId, new ResultCallback() {
    @Override
    public void onSuccess(Void aVoid) {
        Log.d(TAG, "rtmClient login success");
    }

    @Override
    public void onFailure(ErrorInfo errorInfo) {
        Log.d(TAG, "rtmClient login fail : " + errorInfo);
    }
});

创建消息频道,CHANNEL_ID是一个标识,可以和直播频道不一致,但是建议保持一致:

RtmChannel mRtmChannel;

RtmChannelListener rtmListener = new RtmChannelListener(){
    @Override
    public void onMessageReceived(RtmMessage var1, RtmChannelMember var2){
        // 收到消息,自己发送的消息也会有该方法回调,可以通过RtmChannelMember判断发送消息的人是不是自己,如果是不处理本次消息即可。
    }
    
    @Override
    public void onMemberJoined(RtmChannelMember var1){
        // 有用户加入,可用来做用户上线消息处理
    }

    @Override
    public void onMemberLeft(RtmChannelMember var1){
          // 有用户离开,可用来做用户离线消息处理
    }
};
mRtmChannel = mRtmClient.createChannel(CHANNEL_ID, rtmListener );;

Step4 发送消息

RtmMessage rtmMessage = mRtmClient.createMessage();
rtmMessage.setText(msg);
mRtmChannel.sendMessage(rtmMessage, callback);

Step5 退出消息频道

可在退出直播房间时,调用该方法。

mRtmChannel.release();

3 需求变更


程序猿注定和产品汪是相爱相杀的人生伴侣,当产品汪说要砍掉一个功能时,程序猿恨不得亲产品汪一脸;当产品汪说要增加一个功能时,程序猿无话可说,只是默默的买了一把狗头铡放在了桌子上。。。

无赖产品又提了一个需求:增加答题功能,主播通过语音输入题目(问答题,答案只有是和否),确认后将题目文本发送给所有房间内的观众,观众收到题目后App主动弹框给观众选择结果。

功能拆解:
1 只有主播有发布题目入口。
2 需要ASR(Automatic Speech Recognition-语音识别)功能,有online实时翻译和本地offline翻译两个方案。
3 ASR结果需要主播确认。
4 ASR结果主播确认后需要通知给所有非主播用户。
5 非主播用户收到题目信息时需要主动弹窗,给用户选择结果。

方案确定:
1 为了确保ASR的准确性选择了online实时翻译,通过比对最终选择搜狗知音开放平台。
2 题目信息也是文本类型,可以借用群聊实时消息通道,给题目信息前面加上特殊字符,非主播用户收到消息时判断是否是以特殊字符开始,如果是remove特殊字符并弹窗显示题目信息。特殊字符定义时可以考虑到扩展性,以后其它类似功能也可以通过该方案来实现。

3.1 在线语音翻译


首先也是需要注册账户并创建应用,详见搜狗知音文档中心,实现可参考如下步骤:

Step1 初始化

调用init方法初始化

// 以下信息从知音平台申请获得
private static final String BASE_URL = "api.zhiyin.sogou.com";
private static final String APP_ID = "";
private static final String APP_KEY = "";
private SogoSpeech mSogouSpeech;
private DefaultAudioSource mAudioSource;
private OnSogouAsrListener mListener;

public void init(Context context) {
    ZhiyinInitInfo.Builder builder = new ZhiyinInitInfo.Builder();
    ZhiyinInitInfo initInfo = builder.setBaseUrl(BASE_URL).setUuid(UUID).setAppid(APP_ID).setAppkey(APP_KEY).create();
    SogoSpeech.initZhiyinInfo(context, initInfo);

    SogoSpeechSettings settings = SogoSpeechSettings.shareInstance();
    settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_AUDIO_CODING_INT, 1);
    settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_VAD_ENABLE_BOOLEAN, false); 
    settings.setProperty(SpeechConstants.Parameter.ASR_ONLINE_VAD_LONGMODE_BOOLEAN, true); // 长时间ASR
    settings.setProperty(Parameter.ASR_ONLINE_LANGUAGE_STRING, ASRLanguageCode.CHINESE); // 也支持英文ASR ASRLanguageCode.ENGLIS

    mSogouSpeech = new SogoSpeech(context);
    mSogouSpeech.registerListener(mSpeechEventListener);

    mAudioSource = new DefaultAudioSource(new AudioRecordDataProviderFactory(context));
    mAudioSource.addAudioSourceListener(mAudioSourceListener);
}

private EventListener mSpeechEventListener = new EventListener() {
    @Override
    public void onEvent(String eventName, String param, byte[] data, int offset, int length, Object extra) {
        if (TextUtils.equals(SpeechConstants.Message.MSG_ASR_ONLINE_LAST_RESULT, eventName)) {
            if (null != mListener) {
                mListener.onSogouAsrResult(param);
            }
            stopTransform();
        }
    }

    @Override
    public void onError(String errorDomain, int errorCode, String errorDescription, Object extra) {
        // 9002 用户主动取消
        if (9002 != errorCode && null != mListener) {
            mListener.onSogouAsrResult("");
        }
        stopTransform();
    }
};

private IAudioSourceListener mAudioSourceListener = new IAudioSourceListener() {
    @Override
    public void onBegin(IAudioSource iAudioSource) {
        Log.d(TAG, "AudioSource onBegin");
        mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_START, "", null, 0, 0);
    }

    @Override
    public void onNewData(IAudioSource audioSource, Object dataArray, long packIndex, long sampleIndex, int flag) {
        final short[] data = (short[]) dataArray;
        mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_RECOGIZE, "", data, (int) packIndex, 0);
    }

    @Override
    public void onEnd(IAudioSource audioSource, int status, Exception e, long sampleCount) {
        Log.d(TAG, "AudioSource onEnd");
        mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_STOP, "", null, 0, 0);
    }
};

public interface OnSogouAsrListener {
    void onSogouAsrResult(String result);
}

Step2 开始语音识别

public void startTransform(OnSogouAsrListener listener) {
    mListener = listener;
    mSogouSpeech.send(SpeechConstants.Command.ASR_ONLINE_CREATE, null, null, 0, 0);
    new Thread(mAudioSource, "audioRecordSource").start();
}

Step3 停止语音识别

正常情况下不需要调用该方法,在EventListener 回调中已经调用过该方法了,为了确保状态正常也可以在退出房间时手动调用一次。

public void stopTransform() {
    mListener  = null;
    if (null != mAudioSource) {
        mAudioSource.stop();
    }
}

3.2 功能整合


  1. 主播点击发布题目时,UI效果类似微信语音输入,调用startTransform方法开始ASR识别。
  2. 松开手指收到ASR结果后,弹出确认对话框。
  3. 主播确认后,通过RtmChannel发送RtmMessage。
  4. 收到RtmChannel消息判断是否是答题消息,如果是弹出答题dialog。

注意:RtmChannel消息主播也能收到,主播需要屏蔽答题信息。

4 功能上线


俺么老百姓儿啊,今儿个真高兴,真呀真高兴,欧耶!!!

你可能感兴趣的:(Android视频直播开发总结)