WinAv - aopacloud/aopa-rtc GitHub Wiki

Realize audio and video interaction

This article describes how to integrate the Opa real-time interactive SDK and implement a simple real-time interactive app from 0 through a small amount of code, which is suitable for interactive live broadcast and video call scenarios.

First, you need to understand the following basic concepts about real-time audio and video interaction:

  • Opa real-time interaction SDK: an SDK developed by Opa to help developers realize real-time audio and video interaction in the app.
  • Channel: the channel used to transmit data. Users in the same channel can interact in real time.
  • Anchor: You can * * publish * * audio and video in the channel, and also * * subscribe to * * audio and video published by other anchors.
  • Audience: You can * * subscribe to * * audio and video in the channel, and do not have * * publish * * audio and video permissions.

The following figure shows the basic workflow of realizing audio and video interaction in the app:

Realize audio and video interaction

  1. All users call the 'joinChannel' method to join the channel and set user roles as needed:
    • Interactive live broadcast: if the user needs to stream in the channel, set it as the anchor; If the user only needs stream collection, it is set as audience.
    • Video call: use all user roles as anchors.
  2. After joining the channel, users in different roles have different behaviors:
    • All users can receive audio and video streams in the channel by default.
    • The anchor can publish audio and video streams in the channel.
    • If viewers need to send streams, they can call the 'setClientRole' method in the channel to modify the user role so that it has the permission to send streams.

Precondition​

Before implementing the function, please prepare the development environment according to the following requirements:

  • Windows 7 or above devices.

  • Microsoft Visual Studio 2017 or above.

  • C++11 or above.

  • If you use C # development, you also need [. NET Desktop Development Component]( https://learn.microsoft.com/en-us/dotnet/framework/install/guide-for-developers ).

  • A computer that can access the Internet. If your network environment has deployed a firewall, refer to [Coping with firewall restrictions]( https://doc.shengwang.cn/doc/rtc/windows/basic-features/firewall )In order to use the Opa service normally.

  • A valid Opa account and Opa project. Please refer to [Opening Service] to obtain the following information from the Opa console:

    • App ID: A string randomly generated by Opa to identify your project.
    • Temporary Token: Token is also called dynamic key, which authenticates the user when the client joins the channel. The temporary token is valid for 24 hours.
  • C++ Create project​


Take Windows 11 as an example, refer to the following operations to build an app in Visual Studio 2019 to realize real-time audio and video interaction. If you have your own project, you can skip this step.

Create Windows Project

  1. In Visual Studio, select * * File * > * New * > * Project * * to create a new project. In the pop-up window, select * * MFC Application * * as the project template, click * * Next * * to set the project name to * * AopaQuickStart * * and set the project storage location, and then click * * Create * *.

  2. In the pop-up MFC application window, set * * application type * * to * * dialog based * *, and * * use MFC * * to * * use MFC in shared DLLs * *. Enter the * * generated class * *, set the * * generated class * * to * * Dlg * *, set the * * base class * * to * * CDialog * *, and finally click * * Finish *

Integration SDK​

  1. Get the latest Windows SDK from [Download], unzip and open it.
  2. Open the downloaded SDK file and copy the 'sdk' folder to your project path. Ensure that the 'sdk' folder and your 'sln' file are in the same directory

Configuration item attribute ​

In the * * Solution Explorer * * window, right-click the project name and click * * Properties * * to configure the following:

  • Enter the * * C/C++>General>Attach Include Directory * * menu, and enter '$(SolutionDir) sdk high_level_api include'.

  • Enter the * * Linker>General>Additional Library Directory * * menu, and enter '$(SolutionDir) sdk x86_64'.

information

If you are an x86 Windows operating system, please enter '$(SolutionDir) sdk x86'.

  • Enter the * * Linker>Input>Additional Dependencies * * menu, and enter '$(SolutionDir) sdk x86_64 aopa_rtc_sdk. dll. lib'.

information

If you are an x86 Windows operating system, please enter '$(SolutionDir) sdk  x86  aopa_rtc_sdk. dll. lib'.
  • Enter the * * Advanced * * menu, and set * * Copy content to OutDir * * and * * Copy C++runtime to output directory * * to * * Yes * * in * * Advanced Properties * *.

  • Enter the * * Generate Event>Post Generate Event>Command Line * * menu, and enter 'copy $(SolutionDir) sdk x86_64 *. dll $(SolutionDir) $(Platform) $(Configuration)'.

Click * * Apply * * after completing the above configuration.

Create user interface​

According to the needs of real-time audio and video interaction scenarios, you need to add the following UI to your application:

  • View box showing local video
  • View box showing remote video
  • Input box for entering channel name
  • Join and leave channel buttons

You can refer to the following steps to create a user interface.

To create a user interface

  1. Switch the item to resource view in the right menu bar, and then open the " Dialog file, your interface is shown below:: ui
  2. Add a view box showing remote video. In * * View>Toolbox * *, select Add * * Picture Control * * Control, and in * * Properties>Miscellaneous * *, set the ID of the control to * * IDC _STATIC _REMOTE * *, as shown in the following figure:: remote users
  3. Add a view box to display local videos. In * * View>Toolbox * *, select Add * * Picture Control * * Control, and in * * Properties>Miscellaneous * *, set the ID of the control to * * IDC _STATIC _LOCAL * *.
  4. Add an input box for entering the channel name. In * * View>Toolbox * *, select Add * * Static Text * * control, and change * * Description Text * * to * * Channel Name * * in * * Property * *. Then add a * * Edit Control * * control as the input box. In * * Properties>Miscellaneous * *, set the ID of the control to * * IDC _EDIT _CHANNEL * *.
  5. Add add and leave channel buttons. In * * View>Toolbox * *, select and add two * * Button * * controls. In * * Properties>Miscellaneous * *, set the control IDs as * * ID _BTN _JOIN * * and * * ID _BTN _LEAVE * *, and set the * * description text * * as * * Add * * and * * Leave * * respectively. The user interface is shown below: ui

Implementation process​

This section describes how to implement a real-time audio and video interactive application. You can first copy the complete sample code into your project to quickly experience the basic functions of real-time audio and video interaction, and then follow the implementation steps to understand the core API calls.

The following figure shows the basic process of using OPA RTC SDK to achieve audio and video interaction.

Implementation process

The following shows the complete sample code to realize the basic process of real-time interaction:

take care

After copying the complete sample code to your project, you need to replace the 'APP_ID' and 'token' in the code with the App ID and temporary token you obtained on the Opa console. AopaQuickStartDlg.h

C++

#pragma once  
#include <string>  
#include <IBBRtcEngine.h>  
  
using namespace aopa;  
using namespace aopa::rtc;  
using namespace aopa::media;  
using namespace aopa::media::base;  

class CAopaQuickStartRtcEngineEventHandler : public IRtcEngineEventHandler {
public:
    void SetMsgReceiver(HWND hWnd) {
        m_hMsgHanlder = hWnd;
    }

    virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
        }
    }
    virtual void onUserJoined(uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_JOINED), uid, 0);
        }
    }

    virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_OFFLINE), uid, 0);
        }
    }

private:
    HWND m_hMsgHanlder;
};
class CAopaQuickStartDlg : public CDialog {
public:
    CAopaQuickStartDlg(CWnd* pParent = nullptr);
    virtual ~CAopaQuickStartDlg();

#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_AOPAQUICKSTART_DIALOG };
#endif
    afx_msg void OnBnClickedBtnJoin();
    afx_msg void OnBnClickedBtnLeave();
    afx_msg LRESULT OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnEIDUserJoined(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnEIDUserOffline(WPARAM wParam, LPARAM lParam);

protected:
    HICON m_hIcon;
    CStatic m_staRemote;
    CStatic m_staLocal;
    CEdit m_edtChannelName;

    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()

    std::string cs2utf8(CString str);

private:
    IRtcEngine* m_rtcEngine = nullptr;
    CAopaQuickStartRtcEngineEventHandler m_eventHandler;
bool m_initialize = false;
bool m_remoteRender = false;
};

AopaQuickStartDlg.cpp

C++

//
#include "pch.h"
#include "framework.h"
#include "AopaQuickStart.h"
#include "AopaQuickStartDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
class CAboutDlg : public CDialogEx {
public:
    CAboutDlg();
#ifdef AFX_DESIGN_TIME
    enum {
        IDD = IDD_ABOUTBOX
    }
    ;
#endif
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
protected:
    DECLARE_MESSAGE_MAP()
}
;
CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) {
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX) {
    CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
CAopaQuickStartDlg::CAopaQuickStartDlg(CWnd* pParent
        /*=nullptr*/
                                        )
    : CDialog(IDD_AOPAQUICKSTART_DIALOG, pParent) {
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
CAopaQuickStartDlg::~CAopaQuickStartDlg() {
    CDialog::~CDialog();
    if(m_rtcEngine) {
        m_rtcEngine->release(true);
        m_rtcEngine = NULL;
    }
}
void CAopaQuickStartDlg::DoDataExchange(CDataExchange* pDX) {
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_EDIT_CHANNEL, m_edtChannelName);
    DDX_Control(pDX, IDC_STATIC_REMOTE, m_staRemote);
    DDX_Control(pDX, IDC_STATIC_LOCAL, m_staLocal);
}
BEGIN_MESSAGE_MAP(CAopaQuickStartDlg, CDialog)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(ID_BTN_JOIN, &CAopaQuickStartDlg::OnBnClickedBtnJoin)
    ON_BN_CLICKED(ID_BTN_LEAVE, &CAopaQuickStartDlg::OnBnClickedBtnLeave)
    ON_MESSAGE(WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), CAopaQuickStartDlg::OnEIDJoinChannelSuccess)
    ON_MESSAGE(WM_MSGID(EID_USER_JOINED), &CAopaQuickStartDlg::OnEIDUserJoined)
    ON_MESSAGE(WM_MSGID(EID_USER_OFFLINE), &CAopaQuickStartDlg::OnEIDUserOffline)
END_MESSAGE_MAP()
#define "<APP_ID>"
#define "<token>"
BOOL CAopaQuickStartDlg::OnInitDialog() {
    CDialog::OnInitDialog();
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);
    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != nullptr) {
        BOOL bNameValid;
        CString strAboutMenu;
        bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
        ASSERT(bNameValid);
        if (!strAboutMenu.IsEmpty()) {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }
    SetIcon(m_hIcon, TRUE);
    SetIcon(m_hIcon, FALSE);
    m_eventHandler.SetMsgReceiver(m_hWnd);
    m_rtcEngine = createAopaRtcEngine();
    RtcEngineContext context;
    context.appId = APP_ID;
    context.eventHandler = &m_eventHandler;
    int ret = m_rtcEngine->initialize(context);
    if (ret == 0) {
        m_initialize = true;
    } else {
        m_initialize = false;
    }
    m_rtcEngine->enableVideo();
    return TRUE;
}
void CAopaQuickStartDlg::OnSysCommand(UINT nID, LPARAM lParam) {
    if ((nID & 0xFFF0) == IDM_ABOUTBOX) {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    } else {
        CDialog::OnSysCommand(nID, lParam);
    }
}
void CAopaQuickStartDlg::OnPaint() {
    if (IsIconic()) {
        CPaintDC dc(this);
        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()),0);
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;
        dc.DrawIcon(x, y, m_hIcon);
    } else {
        CDialog::OnPaint();
    }
}
HCURSOR CAopaQuickStartDlg::OnQueryDragIcon() {
    return static_cast<HCURSOR>(m_hIcon);
}
std::string CAopaQuickStartDlg::cs2utf8(CString str) {
    char szBuf[2 * MAX_PATH] = {
        0
    }
    ;
    WideCharToMultiByte(CP_UTF8, 0, str.GetBuffer(0), str.GetLength(), szBuf, 2 *MAX_PATH, NULL, NULL);
    return szBuf;
}
void CAopaQuickStartDlg::OnBnClickedBtnJoin() {
    CString strChannelName;
    m_edtChannelName.GetWindowText(strChannelName);
    if (strChannelName.IsEmpty()) {
        AfxMessageBox(_T("Fill channel name first"));
        return;
    }
    ChannelMediaOptions option;
    option.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
    option.clientRoleType = CLIENT_ROLE_BROADCASTER;
    option.publishMicrophoneTrack = true;
    option.publishCameraTrack = true;
    option.autoSubscribeAudio = true;
    option.autoSubscribeVideo = true;
    int ret = m_rtcEngine->joinChannel(token, cs2utf8(strChannelName).c_str(), 0,option);
    VideoCanvas canvas;
    canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
    canvas.uid = 0;
    canvas.view = m_staLocal.GetSafeHwnd();
    m_rtcEngine->setupLocalVideo(canvas);
    m_rtcEngine->startPreview();
}
void CAopaQuickStartDlg::OnBnClickedBtnLeave() {
    m_rtcEngine->stopPreview();
    m_rtcEngine->leaveChannel();
    VideoCanvas canvas;
    canvas.uid = 0;
    m_rtcEngine->setupLocalVideo(canvas);
    m_remoteRender = false;
}
LRESULT CAopaQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
    uid_t localUid = wParam;
    return 0;
}
LRESULT CAopaQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
    uid_t remoteUid = wParam;
    if (m_remoteRender) {
        return 0;
    }
    VideoCanvas canvas;
    canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
    canvas.uid = remoteUid;
    canvas.view = m_staRemote.GetSafeHwnd();
    m_rtcEngine->setupRemoteVideo(canvas);
    m_remoteRender = true;
    return 0;
}
LRESULT CAopaQuickStartDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) {
    uid_t remoteUid = wParam;
    if (!m_remoteRender) {
        return 0;
    }
    VideoCanvas canvas;
    canvas.uid = remoteUid;
    m_rtcEngine->setupRemoteVideo(canvas);
    m_remoteRender = false;
    return 0;
}

Refer to the following steps to achieve real-time interaction in your application:

Create and initialize IRtcEngine​

Before calling other Opa APIs, you need to call 'createAopaRtcEngine' to create an 'IRtcEngine' object, then call 'initialize' and pass in the App ID to initialize 'IRtcEngine'. C++

m_rtcEngine = createAopaRtcEngine();  
  
RtcEngineContext context;  
  
context.appId = APP_ID;  
  
context.eventHandler = &m_eventHandler;  
  
int ret = m_rtcEngine->initialize(context);  
if (ret == 0) {  
    m_initialize = true;} else {  
    m_initialize = false;}  

Enable video module​](about:blank#%E5%90%AF%E7%94%A8%E8%A7%86%E9%A2%91%E6%A8%A1%E5%9D%97 "标题的直接链接")

  1. Call the 'enableVideo' method to enable the video module.
  2. Call 'setupLocalVideo' to initialize the local view and set the local video display properties.
  3. Call the 'startPreview' method to open the local video preview.

C++

m_rtcEngine->enableVideo();  
...  
  
VideoCanvas canvas;  
canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;  
canvas.uid = 0;  
canvas.view = m_staLocal.GetSafeHwnd();  
m_rtcEngine->setupLocalVideo(canvas);  
m_rtcEngine->startPreview();  

Join the channel and publish the audio and video stream​

Call the 'joinChannel' method, fill in the temporary token you obtained on the console, and add the channel name filled in when you obtained the token to the channel, and set the user role. C++

void CAopaQuickStartDlg::OnBnClickedBtnJoin() {
    CString strChannelName;
    m_edtChannelName.GetWindowText(strChannelName);
    if (strChannelName.IsEmpty()) {
        AfxMessageBox(_T("Fill channel name first"));
        return;
    }

    ChannelMediaOptions option;
    option.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
    option.clientRoleType = CLIENT_ROLE_BROADCASTER;
    option.publishMicrophoneTrack = true;
    option.publishCameraTrack = true;
    option.autoSubscribeAudio = true;
    option.autoSubscribeVideo = true;
    m_rtcEngine->joinChannel(token, cs2utf8(strChannelName).c_str(), 0, option);
}

Set remote view​

When a remote user joins a channel, obtain the remote user's uid from the callback of the remote user joining the channel, and then call 'setupRemoteVideo' to set and render the remote view.

C++

void CAopaQuickStartDlg::OnBnClickedBtnJoin() {
    CString strChannelName;
    m_edtChannelName.GetWindowText(strChannelName);
    if (strChannelName.IsEmpty()) {
        AfxMessageBox(_T("Fill channel name first"));
        return;
    }

    ChannelMediaOptions option;
    option.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
    option.clientRoleType = CLIENT_ROLE_BROADCASTER;
    option.publishMicrophoneTrack = true;
    option.publishCameraTrack = true;
    option.autoSubscribeAudio = true;
    option.autoSubscribeVideo = true;
    m_rtcEngine->joinChannel(token, cs2utf8(strChannelName).c_str(), 0, option);
}

Implement common callback​

You can implement other functions during initialization as required, such as callback for registered users to join or leave the channel.

C++

class CAopaQuickStartRtcEngineEventHandler : public IRtcEngineEventHandler {
public:
    virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
        }
    }

    virtual void onUserJoined(uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_JOINED), uid, 0);
        }
    }
    virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_OFFLINE), uid, 0);
        }
    }

private:
    HWND m_hMsgHanlder;
};

Leave channel ​

Call 'leaveChannel' to leave the current channel according to the needs of the scene, such as when the application is closed or switched to the background.

C++

class CAopaQuickStartRtcEngineEventHandler : public IRtcEngineEventHandler {
public:
    virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
        }
    }

    virtual void onUserJoined(uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_JOINED), uid, 0);
        }
    }

    virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_OFFLINE), uid, 0);
        }
    }

private:
    HWND m_hMsgHanlder;
};

If you no longer need interaction, call the 'release' method to release engine resources.

C++

CDialog::~CDialog() {
    if(m_rtcEngine){
       m_rtcEngine->release(true);
       m_rtcEngine = NULL;
    }
}

Test application​

Follow these steps to test your video call project:

  1. Fill the App ID and temporary Token you obtained from the Opa console into the 'APP_ID' and 'APP_TOKEN' of the 'Form1. cs' file respectively.
  2. Click the * * Start * * button in Visual Studio to run your project, and you will see yourself in the local view.
  3. Enter the channel name you obtained on the Opa console in the input box, and click the * * Join * * button to join the channel.
  4. Invite a friend to join the channel using the same App ID, channel name, and token on another device. When your friends join the channel, you can hear and see each other. 图片 Next Steps ​

  • In the test or production environment, in order to ensure communication security, OPA recommends using a token server to generate a token. See [Using Token Authentication] for details
  • If you want to achieve fast live scenes, you can change the delay level of the audience end to low delay ('AUDIENCE_LATENCY_LEVEL_LOW_LATENCY') on the basis of interactive live broadcast. For details, please refer to [Achieve Fast Live Broadcasting]

Related information​

Sample project​

Opa has provided an open source audio and video interaction example project for your reference. You can go to download or view the source code.

C++

Next chapter:basic function

⚠️ **GitHub.com Fallback** ⚠️