You need to enable JavaScript to run this app.
导航

场景搭建(Android)

最近更新时间2023.12.13 16:13:03

首次发布时间2022.11.15 16:06:43

SDK集成

为了保证最佳体验效果,本场景需要集成火山引擎的 RTC SDK 以及 HIFIVE 音乐开放平台的SDK,您需要在 RTC、HIFIVE的控制台开通服务,相应开通指南如下:

  1. RTC SDK总体接入流程,详细步骤请参看 RTC服务开通指南

alt

  1. HIFIVE 音乐开放平台 SDK 接入流程,详细步骤请参看 HIFIVE 集成文档

整体实现流程

整体业务流程图

alt

数据流向图

alt

核心功能实现

当前仅提供了你已通过其他渠道获取音乐文件时,实现 KTV 场景的参考实现。RTC 也提供从曲库接入到 KTV 场景的完整能力,如果希望获取参考文档,请咨询技术支持。

创建/加入房间

时序图

alt

示例代码

/**
 * 加入RTC房间并初始化参数 
 * @param token 加入 RTC 房间的 token
 * @param roomId 加入 RTC 房间的 id
 * @param userId 加入 RTC 房间的 用户id
 * @param isHost true 是业务上主播角色;false 是业务上观众角色
 */
public void joinRTCRoom(String token, String roomId, String userId, boolean isHost) {
    // 初始化 RTCVideo 对象
    mRTCVideo = RTCVideo.createRTCVideo(Utilities.getApplicationContext(), appId, mIRTCVideoEventHandler, null, null);

    // 初始化 RTCRoom 对象
    mRTCRoom = mRTCVideo.createRTCRoom(roomId);
    mRTCRoom.setRTCRoomEventHandler(mIRTCRoomEventHandler);

    // 设置音频场景类型 Music
    mRTCVideo.setAudioScenario(AudioScenarioType.AUDIO_SCENARIO_MUSIC);
    
    // 设置音频双声道音乐音质
    mRTCVideo.setAudioProfile(AudioProfileType.AUDIO_PROFILE_HD);

    // 设置主播为可见,观众为隐身
    mRTCRoom.setUserVisibility(isHost);

    // 加入房间时主播需要开启麦克风,观众需要关闭麦克风
    if (isHost) {
        mRTCVideo.startAudioCapture();
    } else {
        mRTCVideo.stopAudioCapture();
    }

    // 设置音频路由模式
    mRTCVideo.setDefaultAudioRoute(AudioRoute.AUDIO_ROUTE_SPEAKERPHONE);

    // 开启发言者音量监听
    AudioPropertiesConfig audioPropertiesConfig = new AudioPropertiesConfig(300);
    mRTCVideo.enableAudioPropertiesReport(audioPropertiesConfig);

    // 加入房间,开始连麦,需要申请AppId和Token
    UserInfo userInfo = new UserInfo(userId, null);
    RTCRoomConfig roomConfig = new RTCRoomConfig(ChannelProfile.CHANNEL_PROFILE_KTV,
            true, true, true);
    mRTCRoom.joinRoom(token, userInfo, roomConfig);
}
private final IRTCVideoEventHandler mIRTCVideoEventHandler = new IRTCVideoEventHandler() {
    /**
     * 本地用户音量回调
     */
    @Override
    public void onLocalAudioPropertiesReport(LocalAudioPropertiesInfo[] audioPropertiesInfos) {
        super.onLocalAudioPropertiesReport(audioPropertiesInfos);
    }
    /**
     * 远端用户音量回调
     */
    @Override
    public void onRemoteAudioPropertiesReport(RemoteAudioPropertiesInfo[] audioPropertiesInfos, int totalRemoteVolume) {
        super.onRemoteAudioPropertiesReport(audioPropertiesInfos, totalRemoteVolume);
    }
};
private final IRTCRoomEventHandler mIRTCRoomEventHandler = new IRTCRoomEventHandler() {
    /**
     * 收到 RTC 加入房间结果
     */
    @Override
    public void onRoomStateChanged(String roomId, String uid, int state, String extraInfo) {
    }
}

双人合唱

时序图

alt

示例代码

  • 状态变化
/**
 * 演唱者角色变化(上下麦)
 * @param isSinger: 是否是演唱者
 */
private void switchRoleBecomeSinger(boolean isSinger) {
    // 设置用户可见性
    mRTCRoom.setUserVisibility(isSinger);
    // 开启/关闭 本地音频采集
    if (isSinger) {
        mRTCVideo.startAudioCapture();
    } else {
        mRTCVideo.stopAudioCapture();
    }
}

/**
 * 主唱、副唱和观众更新音频流订阅状态
 */
private void updateAudioSubscribe() {
    if (!mIsChorusing) {
        return;
    }
    // 在合唱中
    // 如果自己是主唱,取消订阅副唱音频
    if (mRole == LEAD_SINGER) {
        mRTCRoom.unsubscribeStream(succentorUid, MediaStreamType.RTC_MEDIA_STREAM_TYPE_AUDIO);
    } 
    // 如果自己是观众,取消订阅主唱音频
    else if (mRole == AUDIENCE) {
        mRTCRoom.unsubscribeStream(leadSingerUid, MediaStreamType.RTC_MEDIA_STREAM_TYPE_AUDIO);
    }
}
  • 主唱
/**
 * 开始演唱。在歌词/歌曲下载完成,收到开始演唱广播后执行。
 * @param filePath 歌曲的文件路径
 */
public void startStartSing(String filePath) {
    // 设置歌曲本地播放和推送到远端
    IAudioMixingManager audioMixingManager = mRTCVideo.getAudioMixingManager();
    AudioMixingConfig audioMixingConfig = new AudioMixingConfig(AUDIO_MIXING_TYPE_PLAYOUT_AND_PUBLISH, 1);
    // 开始播放歌曲
    audioMixingManager.startAudioMixing(audioMixingId, filePath, audioMixingConfig);
    audioMixingManager.setAudioMixingProgressInterval(audioMixingId, 100);
}

/**
 * 收到本地歌曲播放进度回调
 * @param mixId 混音ID
 * @param progress 歌曲播放进度,单位为毫秒
 */
@Override
public void onAudioMixingPlayingProgress(int mixId, long progress) {
    super.onAudioMixingPlayingProgress(mixId, progress);
    // 发送音频流同步信息
    String progressStr = String.valueOf(progress);

    StreamSycnInfoConfig streamSycnInfoConfig = new StreamSycnInfoConfig(
            StreamIndex.STREAM_INDEX_MAIN,
            3,
            StreamSycnInfoConfig.SyncInfoStreamType.SYNC_INFO_STREAM_TYPE_AUDIO
    );
    mRTCVideo.sendStreamSyncInfo(progressStr.getBytes(StandardCharsets.UTF_8), streamSycnInfoConfig);
        
    // 刷新本地歌词进度
    syncLocalLyricsProgress(progress);
}
 
/**
 * 音乐文件播放状态改变回调
 */
@Override
public void onAudioMixingStateChanged(int mixId, AudioMixingState state, AudioMixingError error) {
    super.onAudioMixingStateChanged(mixId, state, error);
    if (state == AudioMixingState.AUDIO_MIXING_STATE_FINISHED) {
        // 歌曲播放结束
    }
}
  • 副唱
/**
 * 音频数据回调观察者:因为RTC引擎底层使用弱引用持有观察者需要应用层自己管理观察者的生命周期
 */
private IAudioFrameObserver mAudioFrameObserver;
/**
 * 副唱转发主唱音频
 */
public void startSuccentorAudioMixing() {
    // 开启PCM混音
    IAudioMixingManager audioMixingManager = mRTCVideo.getAudioMixingManager();
    audioMixingManager.enableAudioMixingFrame(audioMixingId, AUDIO_MIXING_TYPE_PLAYOUT_AND_PUBLISH);
    // 监听远端用户音频数据
    mAudioFrameObserver = new IAudioFrameObserver() {
        @Override
        public void onRemoteUserAudioFrame(RemoteStreamKey streamInfo, IAudioFrame audioFrame) {
            // 副唱转推房间内主唱的音频
            if (audioFrame.data_size() > 0) {
                AudioFrame frame = new AudioFrame();
                int size = audioFrame.data_size();
                frame.buffer = new byte[size];
                ByteBuffer originalBuffer = audioFrame.getDataBuffer();
                originalBuffer.get(frame.buffer);
                frame.channel = audioFrame.channel();
                frame.sampleRate = audioFrame.sample_rate();
                frame.samples = size / frame.channel.value() / 2;
                audioMixingManager.pushAudioMixingFrame(audioMixingId, frame);
            }
        }
    };
    mRTCVideo.enableAudioFrameCallback(AUDIO_FRAME_CALLBACK_REMOTE_USER, new AudioFormat(AudioSampleRate.AUDIO_SAMPLE_RATE_AUTO, AudioChannel.AUDIO_CHANNEL_AUTO));
    mRTCVideo.registerAudioFrameObserver(mAudioFrameObserver);
}

/**
 * 副唱停止合唱
 */
public void stopSuccentorAudioMixing() {
    // 关闭监听远端用户音频数据
    mRTCVideo.disableAudioFrameCallback(AUDIO_FRAME_CALLBACK_REMOTE_USER);
    // 关闭 PCM 混音
    IAudioMixingManager audioMixingManager = mRTCVideo.getAudioMixingManager();
    audioMixingManager.disableAudioMixingFrame(audioMixingId);
}

/**
 * 收到音频同步信息
 * @param streamKey 远端流信息
 * @param streamType 媒体流类型
 * @param data 消息内容
 */
@Override
public void onStreamSyncInfoReceived(RemoteStreamKey streamKey, StreamSycnInfoConfig.SyncInfoStreamType streamType, ByteBuffer data) {
    super.onStreamSyncInfoReceived(streamKey, streamType, data);
    // 转发主唱的音频流同步信息
    Charset charset = StandardCharsets.UTF_8;
    CharsetDecoder decoder = charset.newDecoder();
    CharBuffer charBuffer = null;
    try {
        charBuffer = decoder.decode(data);
    } catch (CharacterCodingException e) {

    }
    String progressStr = charBuffer != null ? charBuffer.toString() : null;
    if (progressStr != null) {
        StreamSycnInfoConfig config = new StreamSycnInfoConfig(StreamIndex.STREAM_INDEX_MAIN, 0, StreamSycnInfoConfig.SyncInfoStreamType.SYNC_INFO_STREAM_TYPE_AUDIO);
        mRTCEngine.sendStreamSyncInfo(progressStr.getBytes(StandardCharsets.UTF_8), config);
    }
    
    // 刷新本地歌词进度
    syncLocalLyricsProgress(progressStr);
}
  • 观众
/**
 * 收到音频同步信息
 * @param streamKey 远端流信息
 * @param streamType 媒体流类型
 * @param data 消息内容
 */
@Override
public void onStreamSyncInfoReceived(RemoteStreamKey streamKey, StreamSycnInfoConfig.SyncInfoStreamType streamType, ByteBuffer data) {
    super.onStreamSyncInfoReceived(streamKey, streamType, data);
    Charset charset = StandardCharsets.UTF_8;
    CharsetDecoder decoder = charset.newDecoder();
    CharBuffer charBuffer = null;
    try {
        charBuffer = decoder.decode(data);
    } catch (CharacterCodingException e) {

    }
    String progressStr = charBuffer != null ? charBuffer.toString() : null;
    
    // 刷新本地歌词进度
    syncLocalLyricsProgress(progressStr);
}

歌曲控制台

alt

alt

示例代码

/**
 * 原唱/伴奏切换,需要音乐素材支持右声道伴奏,左声道原唱
 * @param isAccompaniment 是否为伴奏,YES 为伴奏,NO 为原唱
 */
public void switchAccompaniment(boolean isAccompaniment) {
    IAudioMixingManager audioMixingManager = mRTCVideo.getAudioMixingManager();
    AudioMixingDualMonoMode audioMixingDualMonoMode = isAccompaniment ? AUDIO_MIXING_DUAL_MONO_MODE_R : AUDIO_MIXING_DUAL_MONO_MODE_L;
    audioMixingManager.setAudioMixingDualMonoMode(audioMixingId, audioMixingDualMonoMode);
}

/**
 * 暂停混音播放
 */
public void pauseSinging() {
    IAudioMixingManager audioMixingManager = mRTCVideo.getAudioMixingManager();
    audioMixingManager.pauseAudioMixing(audioMixingId);
}

/**
 * 恢复混音播放
 */
public void resumeSinging() {
    IAudioMixingManager audioMixingManager = mRTCVideo.getAudioMixingManager();
    audioMixingManager.resumeAudioMixing(audioMixingId);
}

/**
 * 开启/关闭耳返
 * @param isEnable YES 为开启耳返,NO 为关闭耳返
 */
public void enableEarMonitor(boolean isEnable) {
    mRTCVideo.setEarMonitorMode(isEnable ? EarMonitorMode.EAR_MONITOR_MODE_ON : EarMonitorMode.EAR_MONITOR_MODE_OFF);
}

/**
 * 设置耳返的音量
 * @param volume 耳返音量
 */
public void setEarMonitorVolume(int volume) {
    mRTCVideo.setEarMonitorVolume(volume);
}

/**
 * 调节本地播放和远端混音的音量大小
 * @param volume 混音音量
 */
public void setMusicVolume(int volume) {
    IAudioMixingManager audioMixingManager = mRTCVideo.getAudioMixingManager();
    audioMixingManager.setAudioMixingVolume(audioMixingId, volume, AUDIO_MIXING_TYPE_PLAYOUT_AND_PUBLISH);
}

/**
 * 调节麦克风采集音量
 * @param volume 麦克风采集音量
 */
public void setRecordingVolume(int volume) {
    mRTCVideo.setCaptureVolume(StreamIndex.STREAM_INDEX_MAIN, volume);
}
    
/**
 * 设置混响特效类型
 * @param reverbType 特效类型
 */
public void setVoiceReverbType(VoiceReverbType reverbType) {
    mRTCVideo.setVoiceReverbType(reverbType);
}

核心功能对应 API 与回调

API

功能点API
创建 ByteRTCVideo 实例createRTCVideo
设置音频场景类型setAudioScenario:
设置音质档位setAudioProfile:
开启本地音频采集startAudioCapture
开启内部视频采集startVideoCapture
创建 RTC 房间createRTCRoom
设置用户可见性setUserVisibility
加入RTC 房间joinRoom
开始播放音频文件startAudioMixing
设置混音时音频文件播放进度回调的间隔setAudioMixingProgressInterval
发送音频流同步信息sendStreamSyncInfo
订阅房间内指定的通过摄像头/麦克风采集的媒体流subscribeStream
取消订阅房间内指定的通过摄像头/麦克风采集的媒体流unSubscribeStream
启动 PCM 音频数据混音enableAudioMixingFrame
注册音频数据回调观察者。registerAudioFrameObserver
开启音频回调enableAudioFrameCallback
推送 PCM 音频帧数据用于混音pushAudioMixingFrame
关闭 PCM 混音disableAudioMixingFrame
启用音频信息提示。enableAudioPropertiesReport
设置当前音频文件的声道模式setAudioMixingDualMonoMode
暂停播放音频文件pauseAudioMixing
恢复播放音频文件resumeAudioMixing
打开/关闭耳返功能setEarMonitorMode
设置耳返音量setEarMonitorVolume
调节混音的音量大小,包括音频文件混音和 PCM 混音setAudioMixingVolume
调节音频采集音量setCaptureVolume
设置混响特效类型setVoiceReverbType
离开 RTC 房间leaveRoom
销毁 RTC 房间引擎destroy
关闭内部音频采集stopAudioCapture
销毁 RTCVideo 对象destroyRTCVideo

回调

功能点回调
本地用户加入 RTC 回调onRoomStateChanged
音频流同步信息回调onStreamSyncInfoReceived
返回远端单个用户的音频数据onRemoteUserAudioFrame
本地音频的相关信息回调onLocalAudioPropertiesReport
订阅的远端用户的音频信息回调onRemoteAudioPropertiesReport
音频混音文件播放状态改变时回调onAudioMixingStateChanged
音频播放路由变化回调onAudioRouteChanged
混音音频文件播放进度回调onAudioMixingPlayingProgress
远端视频流的状态改变回调onRemoteVideoStateChanged