1 対 1 の音声通話およびビデオ通話の使用シナリオ
優れたユーザー エクスペリエンスを確保するには、1 対 1 の音声通話とビデオ通話が安定しており、クリアでスムーズである必要があります。一般的な使用シナリオは次のとおりです。
- ソーシャル アプリケーション: ソーシャル アプリケーションは一般的な使用シナリオであり、ユーザーは音声通話やビデオ通話を通じて対面でコミュニケーションできます。
- オンライン教育:教師と生徒は音声通話やビデオ通話を通じてリアルタイムで対話できるため、授業の効率が向上します。
- リモート アシスタンス: 緊急コマンド プロジェクトなどの一部の作業シナリオでは、音声およびビデオ通話機能、テクニカル サポート、メンテナンス サービスなどを通じてリモート アシスタンスを提供する必要があります。
- ビデオ会議: 1 対 1 の音声通話とビデオ通話はビデオ会議の非常に重要な部分であり、2 人の参加者間のコミュニケーションに使用されます。もちろん、それらを組み合わせて出力することもできます。
- 音声通話:運転中などに音声通話を使用します。現時点では音声通話が良い選択です。
1 対 1 の音声およびビデオ通話の技術ソリューション
WebRTC スキーム
Android プラットフォームで 1 対 1 の音声通話とビデオ通話を実装するには、リアルタイムの音声通話とビデオ通話の機能を提供する WebRTC を使用できます。その方法についての簡単なステップバイステップのガイドは次のとおりです。
- 環境のセットアップ: まず、開発環境に Android Studio をインストールし、必要な SDK を構成する必要があります。
- 依存関係を追加する: プロジェクトに WebRTC ライブラリを追加する必要があります。次の依存関係を build.gradle ファイルに追加します。
- オーディオとビデオのキャプチャを実現する: オーディオとビデオのキャプチャを実現する必要があります。Java では、AudioRecord クラスと VideoCapturer クラスを使用して実現できます。
- PeerConnection の作成: PeerConnection オブジェクトを作成します。これはオーディオとビデオのコーデックとネットワーク送信に使用されます。
- ローカルのオーディオおよびビデオ ストリームを表示する: MediaStream.VideoTrack および MediaStream.AudioTrack を使用して、キャプチャされたオーディオおよびビデオ ストリームを PeerConnection に追加し、VideoRenderer および AudioRenderer を通じて表示します。
- オファーを作成して送信する: オーディオおよびビデオ チャネル情報と、受け入れてもよい接続パラメーターを含むオファーを作成して送信します。
- オファーを受信して解析します。もう一方の端では、オファーを受信した後、オーディオとビデオのチャネル情報と接続パラメータを解析し、応答を作成して返します。
- 回答の受信: 回答を受信した後、ローカルでオーディオおよびビデオのチャネル情報と接続パラメータを解析し、対応するチャネルを作成して開始します。
RTMPスキーム
RTMP は TCP ベースのストリーミング メディア プロトコルで、主にライブ ビデオに使用されます。リアルタイムで音声とビデオを送信する機能を提供し、1 対 1 または 1 対多のシナリオで使用できます。RTMP はイントラネットまたは公衆ネットワーク環境でも使用できます。欠点は、RTMP サーバーが必要なことです。個別に展開され、データは RTMP サーバーを介して転送されます。低遅延の RTMP プレーヤーを使用すると、ミリ秒単位で簡単に対話を行うことができます。
Daniel Live SDK のデモを例に挙げると、RTMP によってプッシュされるコードは次のとおりです。
class ButtonPushStartListener implements OnClickListener
{
public void onClick(View v)
{
if (isPushingRtmp)
{
stopPush();
btnPushStartStop.setText("推送RTMP");
isPushingRtmp = false;
return;
}
Log.i(PUSH_TAG, "onClick start push rtmp..");
if (libPublisher == null)
return;
InitPusherAndSetConfig();
Log.i(PUSH_TAG, "videoWidth: "+ pushVideoWidth + " videoHeight: " + pushVideoHeight + " pushType:" + pushType);
if ( libPublisher.SmartPublisherSetURL(publisherHandle, publishURL) != 0 )
{
Log.e(PUSH_TAG, "Failed to set rtmp pusher URL..");
}
int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);
if (startRet != 0) {
isPushingRtmp = false;
Log.e(TAG, "Failed to start push stream..");
return;
}
CheckInitAudioRecorder();
btnPushStartStop.setText("停止推送 ");
isPushingRtmp = true;
};
RTMP プッシュを停止します。
//停止rtmp推送
private void stopPush() {
if(!isPushingRtmp)
{
return;
}
if ( !isRTSPPublisherRunning) {
if (audioRecord_ != null) {
Log.i(TAG, "stopPush, call audioRecord_.StopRecording..");
audioRecord_.Stop();
if (audioRecordCallback_ != null) {
audioRecord_.RemoveCallback(audioRecordCallback_);
audioRecordCallback_ = null;
}
audioRecord_ = null;
}
}
if (libPublisher != null) {
libPublisher.SmartPublisherStopPublisher(publisherHandle);
}
if (!isRTSPPublisherRunning) {
if (publisherHandle != 0) {
if (libPublisher != null) {
libPublisher.SmartPublisherClose(publisherHandle);
publisherHandle = 0;
}
}
}
}
RTMP再生:
btnPlaybackStartStopPlayback.setOnClickListener(new Button.OnClickListener()
{
// @Override
public void onClick(View v) {
if(isPlaybackViewStarted)
{
btnPlaybackStartStopPlayback.setText("开始播放 ");
if ( playerHandle != 0 )
{
libPlayer.SmartPlayerStopPlay(playerHandle);
libPlayer.SmartPlayerClose(playerHandle);
playerHandle = 0;
}
isPlaybackViewStarted = false;
}
else
{
Log.i(PLAY_TAG, "Start playback stream++");
playerHandle = libPlayer.SmartPlayerOpen(curContext);
if(playerHandle == 0)
{
Log.e(PLAY_TAG, "sur faceHandle with nil..");
return;
}
libPlayer.SetSmartPlayerEventCallbackV2(playerHandle,
new EventHandePlayerV2());
libPlayer.SmartPlayerSetSur face(playerHandle, playerSur faceView); //if set the second param with null, it means it will playback audio only..
libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1);
libPlayer.SmartPlayerSetExternalAudioOutput(playerHandle, new PlayerExternalPcmOutput());
libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1);
libPlayer.SmartPlayerSetBuffer(playerHandle, playbackBuffer);
libPlayer.SmartPlayerSetFastStartup(playerHandle, isPlaybackFastStartup?1:0);
if ( isPlaybackMute )
{
libPlayer.SmartPlayerSetMute(playerHandle, isPlaybackMute?1:0);
}
if (isPlaybackHardwareDecoder) {
int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle,1);
int isSupportH264HwDecoder = libPlayer
.SetSmartPlayerVideoHWDecoder(playerHandle,1);
Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);
}
libPlayer.SmartPlayerSetAudioVolume(playerHandle, curAudioVolume);
libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl);
int iPlaybackRet = libPlayer.SmartPlayerStartPlay(playerHandle);
if( iPlaybackRet != 0 )
{
libPlayer.SmartPlayerClose(playerHandle);
playerHandle = 0;
Log.e(PLAY_TAG, "StartPlayback strem failed..");
return;
}
btnPlaybackStartStopPlayback.setText("停止播放 ");
btnPlaybackPopInputUrl.setEnabled(false);
btnPlaybackHardwareDecoder.setEnabled(false);
btnPlaybackSetPlayBuffer.setEnabled(false);
btnPlaybackFastStartup.setEnabled(false);
isPlaybackViewStarted = true;
Log.i(PLAY_TAG, "Start playback stream--");
}
}
});
軽量RTSPサービス+RTSP再生ソリューション
純粋なイントラネット環境では、2 台の端末が軽量 RTSP サービスを同時に開き、エコー キャンセルなどを通じて相互に送信された RTSP URL をプルして、インテリジェントな 1 対 1 の音声とビデオの対話を実現できます。シーンは使用でき、測定された遅延はミリ秒単位であり、インタラクティブなエクスペリエンスには影響せず、効果は非常に良好です。
対応するコードは次のとおりです。
//Author: daniusdk.com
//启动/停止RTSP服务
class ButtonRtspServiceListener implements OnClickListener {
public void onClick(View v) {
if (isRTSPServiceRunning) {
stopRtspService();
btnRtspService.setText("启动RTSP服务");
btnRtspPublisher.setEnabled(false);
isRTSPServiceRunning = false;
return;
}
Log.i(TAG, "onClick start rtsp service..");
rtsp_handle_ = libPublisher.OpenRtspServer(0);
if (rtsp_handle_ == 0) {
Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性");
} else {
int port = 8554;
if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {
libPublisher.CloseRtspServer(rtsp_handle_);
rtsp_handle_ = 0;
Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");
}
//String user_name = "admin";
//String password = "12345";
//libPublisher.SetRtspServerUserNamePassword(rtsp_handle_, user_name, password);
if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {
Log.i(TAG, "启动rtsp server 成功!");
} else {
libPublisher.CloseRtspServer(rtsp_handle_);
rtsp_handle_ = 0;
Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");
}
btnRtspService.setText("停止RTSP服务");
btnRtspPublisher.setEnabled(true);
isRTSPServiceRunning = true;
}
}
}
RTSP ストリームをパブリッシュします。
//发布/停止RTSP流
class ButtonRtspPublisherListener implements OnClickListener {
public void onClick(View v) {
if (isRTSPPublisherRunning) {
stopRtspPublisher();
if (!isPushingRtmp) {
ConfigControlEnable(true);
}
btnRtspPublisher.setText("发布RTSP流");
btnGetRtspSessionNumbers.setEnabled(false);
btnRtspService.setEnabled(true);
isRTSPPublisherRunning = false;
return;
}
Log.i(TAG, "onClick start rtsp publisher..");
if (!isPushingRtmp) {
InitPusherAndSetConfig();
}
if (publisherHandle == 0) {
Log.e(TAG, "Start rtsp publisher, publisherHandle is null..");
return;
}
String rtsp_stream_name = "stream1";
libPublisher.SetRtspStreamName(publisherHandle, rtsp_stream_name);
libPublisher.ClearRtspStreamServer(publisherHandle);
libPublisher.AddRtspStreamServer(publisherHandle, rtsp_handle_, 0);
if (libPublisher.StartRtspStream(publisherHandle, 0) != 0) {
Log.e(TAG, "调用发布rtsp流接口失败!");
return;
}
if (!isPushingRtmp) {
if (pushType == 0 || pushType == 1) {
CheckInitAudioRecorder(); //enable pure video publisher..
}
ConfigControlEnable(false);
}
startLayerPostThread();
btnRtspPublisher.setText("停止RTSP流");
btnGetRtspSessionNumbers.setEnabled(true);
btnRtspService.setEnabled(false);
isRTSPPublisherRunning = true;
}
}
RTSP ストリーミング セッション接続の数を取得します。
//当前RTSP会话数弹出框
private void PopRtspSessionNumberDialog(int session_numbers) {
final EditText inputUrlTxt = new EditText(this);
inputUrlTxt.setFocusable(true);
inputUrlTxt.setEnabled(false);
String session_numbers_tag = "RTSP服务当前客户会话数: " + session_numbers;
inputUrlTxt.setText(session_numbers_tag);
AlertDialog.Builder builderUrl = new AlertDialog.Builder(this);
builderUrl
.setTitle("内置RTSP服务")
.setView(inputUrlTxt).setNegativeButton("确定", null);
builderUrl.show();
}
//获取RTSP会话数
class ButtonGetRtspSessionNumbersListener implements OnClickListener {
public void onClick(View v) {
if (libPublisher != null && rtsp_handle_ != 0) {
int session_numbers = libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_);
Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);
PopRtspSessionNumberDialog(session_numbers);
}
}
}
RTSP の再生については詳細は説明しません。RTMP の再生と同じですが、URL の種類が異なります。RTMP または RTSP のどちらを使用する場合でも、エコー キャンセルを有効にする必要があることに注意してください。
技術概要
Android プラットフォームでの 1 対 1 のインタラクション。純粋なイントラネット環境では、別個のストリーミング メディア サーバーを展開せずに、軽量 RTSP サービスを使用するのが非常に便利です。パブリック ネットワーク サービスに拡張する必要がある場合は、検討することをお勧めします。 RTMP: 適切な開発機能がある場合は、実際のシナリオに応じて WebRTC も検討できます。