项目中利用AnyChat SDK实现将RGB数据作为视频源的实时推送功能

一、前期调研工作:

查看官网简介www.anychat.cn 可以知道AnyChat支持外部音视频功能,具体描述如下:

AnyChat Platform Core SDK V4.2版本增加了外部音视频数据输入功能,该功能主要满足一些特殊应用场合下的需求,通常普通用户不会使用到,使用该功能,可以由上层应用程序输入视频数据、音频数据到AnyChat,然后AnyChat再对这些数据进行编码、传输,即使用上层应用的数据做为数据源,而不使用AnyChat从本地声卡、摄像头采集的音视频数据。

通过外部音视频数据输入功能,可以让AnyChat客户端的音视频数据来源更加广泛,默认情况下,AnyChat都是对本地的声卡、摄像头进行采集,把采集后的音频、视频数据再进行编码、传输,而如果视频数据并不是从标准的音视频硬件设备采集,则默认的采集功能将不能满足要求。

下面是我利用其SDK将RGB数据传入AnyChat,并传输到另一方进行显示,只要在同一个房间的所有客户端均可以显示,类似电视直播、游戏直播的应用场景;

二、具体分析

在一些场景下,可以实现用户自动获取RGB数据作为视频源,进行传输发送给其他用户,远程和本地同时显示;可以利用AnyChat SDK提供的接口,进行数据的输入,并有AnyChat 内核负责编码传输和渲染工作,上层调用简单方便;

具体举例:      Client A(Get the Rgb data from BMP or Other)--->AnyChat SDK --->Client B(Show the Rgb data)

架构图

数据逻辑调用过程图

三、项目的实现

这里主要是windows平台MFC对话框程序,直接上代码:

BrRgbInputDlg.h

  1 // BrPlayerDlg.h : 头文件
  2 //
  3 #include "BRAnyChatSDKProc.h"
  4 #include "afxwin.h"
  5 #include "afxcmn.h"
  6
  7 #pragma comment(lib, "BRAnyChatCore.lib")
  8
  9 #pragma once
 10
 11 #define MAX_VIDEO_FRAME_SIZE 1024*1024   //视频缓冲区大小
 12 #define MAX_AUDIO_FRAME_SIZE 1280*8       //音频缓冲区大小
 13 #define MAX_AUDIO_COUNT      16          //最大音频数目
 14 #define RING_BUFF_50K        1024*50
 15
 16 typedef struct PlayStatue
 17 {
 18     int stop;
 19     int pause;
 20 }PlayStatue,*PlayStatue_S;
 21
 22 // CBrPlayerDlg 对话框
 23 class CBrPlayerDlg :  public CDialog,
 24                       public CBRAnyChatSDKProc
 25 {
 26 // 构造
 27 public:
 28     CBrPlayerDlg(CWnd* pParent = NULL);    // 标准构造函数
 29
 30 // 对话框数据
 31     enum { IDD = IDD_BRPLAYER_DIALOG };
 32
 33     protected:
 34     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
 35
 36     void AnyChatSDKinit();//初始化SDK
 37     void AnyChatSDKRelease();
 38     void InitVideoBuf();
 39     void ReleaseVideoBuf();
 40
 41     static int PlayRgb32Thread(LPARAM lpParam);  //输入rgb32数据线程,测试用
 42
 43     char           m_lpMediaFileName[256];//媒体文件url
 44     int            m_iMediaType;          //媒体类型
 45
 46     int               m_nPic_Width;
 47     int               m_nPic_Height;
 48     int               m_nPic_Fps;
 49
 50     CBrush         m_brush;
 51
 52     HANDLE         hReadDataThread;  //读取数据线程
 53
 54     DWORD          dwReadThreadID;
 55
 56     UCHAR*           m_lpMediaBuf;
 57
 58
 59     PlayStatue_S    m_sPlayStatue;  //播放状态(播放,停止,暂停)
 60
 61     int            m_img_format;//RGB24/RGB32
 62     int               m_dwRemoteUserId;
 63     int            m_dwLocalUserId;
 64     BOOL           m_bSuccessEnterRoom;
 65
 66     // 用户登陆消息
 67     virtual void OnAnyChatLoginMessage(DWORD dwUserId, DWORD dwErrorCode);
 68     // 连接服务器消息
 69     //virtual void OnAnyChatConnectMessage(BOOL bSuccess);
 70     //网络断开消息
 71     virtual void OnAnyChatLinkCloseMessage(DWORD dwErrorCode);
 72     // 房间在线用户消息
 73     virtual void OnAnyChatOnlineUserMessage(DWORD dwUserNum, DWORD dwRoomId);
 74     // 用户进入/退出房间消息
 75     virtual void OnAnyChatUserAtRoomMessage(DWORD dwUserId, BOOL bEnter);
 76
 77 // 实现
 78 protected:
 79     HICON m_hIcon;
 80
 81     // 生成的消息映射函数
 82     virtual BOOL OnInitDialog();
 83     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
 84     afx_msg void OnPaint();
 85     afx_msg HCURSOR OnQueryDragIcon();
 86     afx_msg void OnDestroy();
 87     DECLARE_MESSAGE_MAP()
 88 public:
 89     afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
 90     afx_msg void OnStnClickedStaticShow();
 91     afx_msg void OnBnClickedButtonOpenfile();
 92     afx_msg void OnBnClickedButtonPlay();
 93     afx_msg void OnLbnSelchangeListFile();
 94     afx_msg void OnBnClickedButtonStop();
 95     CListBox m_List_File;
 96     CStatic m_media_play;
 97     CButton m_play_ctrl;
 98     CButton m_stop_ctrl;
 99     CButton m_openfile_ctrl;
100     CEdit m_eidt_width;
101     CEdit m_edit_height;
102     CComboBox m_select_img_type;
103     afx_msg void OnCbnSelchangeCombo1();
104     CStatic m_display_remote_user;
105 };

BrRgbInputDlg.cpp

// BrPlayerDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "BrRgbInput.h"
#include "BrRgbInputDlg.h"
#include "afxdialogex.h"
#include <string.h>
#include <assert.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

enum image_type{
    IMG_FORMAT_RGB32,
    IMG_FORMAT_RGB24,
};

// 用于应用程序“关于”菜单项的 CAboutDlg 对话框

class CAboutDlg : public CDialogEx
{
public:
    CAboutDlg();

// 对话框数据
    enum { IDD = IDD_ABOUTBOX };

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

// 实现
protected:
    DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()

// CBrPlayerDlg 对话框

CBrPlayerDlg::CBrPlayerDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CBrPlayerDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CBrPlayerDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST_FILE, m_List_File);
    DDX_Control(pDX, IDC_STATIC_SHOW, m_media_play);
    DDX_Control(pDX, IDC_BUTTON_PLAY, m_play_ctrl);
    DDX_Control(pDX, IDC_BUTTON_STOP, m_stop_ctrl);
    DDX_Control(pDX, IDC_BUTTON_OPENFILE, m_openfile_ctrl);
    DDX_Control(pDX, IDC_EDIT1, m_eidt_width);
    DDX_Control(pDX, IDC_EDIT2, m_edit_height);
    DDX_Control(pDX, IDC_COMBO1, m_select_img_type);
    DDX_Control(pDX, IDC_STATIC_SHOW_REMOTE, m_display_remote_user);
}

BEGIN_MESSAGE_MAP(CBrPlayerDlg, CDialog)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_WM_CTLCOLOR()
    ON_STN_CLICKED(IDC_STATIC_SHOW, &CBrPlayerDlg::OnStnClickedStaticShow)
    ON_BN_CLICKED(IDC_BUTTON_OPENFILE, &CBrPlayerDlg::OnBnClickedButtonOpenfile)
    ON_BN_CLICKED(IDC_BUTTON_PLAY, &CBrPlayerDlg::OnBnClickedButtonPlay)
    ON_LBN_SELCHANGE(IDC_LIST_FILE, &CBrPlayerDlg::OnLbnSelchangeListFile)
    ON_BN_CLICKED(IDC_BUTTON_STOP, &CBrPlayerDlg::OnBnClickedButtonStop)
    ON_CBN_SELCHANGE(IDC_COMBO1, &CBrPlayerDlg::OnCbnSelchangeCombo1)
END_MESSAGE_MAP()

// CBrPlayerDlg 消息处理程序

BOOL CBrPlayerDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // 将“关于...”菜单项添加到系统菜单中。

    // IDM_ABOUTBOX 必须在系统命令范围内。
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);

    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        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);        // 设置小图标

    // TODO: 在此添加额外的初始化代码
    m_brush.CreateSolidBrush(RGB(0,0,0)); 

    m_lpMediaBuf=NULL;

    memset(m_lpMediaFileName,0,sizeof(m_lpMediaFileName));

    //初始化AnyChatSDK
    AnyChatSDKinit();

    m_play_ctrl.EnableWindow(FALSE);
    m_stop_ctrl.EnableWindow(FALSE);
    m_openfile_ctrl.EnableWindow(TRUE);
    m_List_File.SetCurSel(0);

    //初始化播放状态
    m_sPlayStatue = new PlayStatue;
    m_sPlayStatue->stop  = 0;
    m_sPlayStatue->pause = 0;

    m_eidt_width.SetWindowTextA("352");
    m_edit_height.SetWindowTextA("288");

    m_dwRemoteUserId = -1;
    m_dwLocalUserId  = -1;

    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

void CBrPlayerDlg::ReleaseVideoBuf()
{

    if (m_sPlayStatue)
    {
        delete[] m_sPlayStatue;
        m_sPlayStatue=NULL;
     }
}

void CBrPlayerDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialog::OnSysCommand(nID, lParam);
    }
}

void CBrPlayerDlg::OnDestroy()
{
    CDialog::OnDestroy();
    // 释放资源
    BRAC_Release();    

    if(m_lpMediaBuf)
    {
        free(m_lpMediaBuf);
        m_lpMediaBuf=NULL;
    }

    ReleaseVideoBuf();
}

void CBrPlayerDlg::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 CBrPlayerDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}

HBRUSH CBrPlayerDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    // TODO:  在此更改 DC 的任何特性
    if (pWnd->GetDlgCtrlID() ==IDC_STATIC_SHOW)
    {
        pDC-> SetBkColor(RGB(0,0,0));
        return   (HBRUSH)m_brush;
    }

    if (pWnd->GetDlgCtrlID() ==IDC_STATIC_SHOW_REMOTE)
    {
        pDC-> SetBkColor(RGB(0,0,0));
        return   (HBRUSH)m_brush;
    }
    // TODO:  如果默认的不是所需画笔,则返回另一个画笔
    return hbr;
}

void CBrPlayerDlg::OnStnClickedStaticShow()
{
    // TODO: 在此添加控件通知处理程序代码
}

void CBrPlayerDlg::OnBnClickedButtonOpenfile()
{
    // TODO: 在此添加控件通知处理程序代码
    CFileDialog dlg(TRUE);///TRUE为OPEN对话框,FALSE为SAVE AS对话框
    memset(m_lpMediaFileName,0,sizeof(m_lpMediaFileName));

    CString FilePathName="";

    if(dlg.DoModal()==IDOK)
    {
        FilePathName=(CString)dlg.GetPathName();
        strcat(m_lpMediaFileName,(const char*)(LPCTSTR)FilePathName);

        if( dlg.GetFileExt() != "rgb")
        {
            AfxMessageBox("必须输入rgb裸数据!");
            return;
        }

        m_play_ctrl.EnableWindow(TRUE);
        m_stop_ctrl.EnableWindow(TRUE);
        m_openfile_ctrl.EnableWindow(TRUE);
        m_List_File.AddString(dlg.GetFileTitle());//文件名
    }
    else
    {
        memset(m_lpMediaFileName,0,sizeof(m_lpMediaFileName));

        m_play_ctrl.EnableWindow(FALSE);
        m_stop_ctrl.EnableWindow(FALSE);
        m_openfile_ctrl.EnableWindow(TRUE);

        return ;
    }
}

void CBrPlayerDlg::AnyChatSDKinit()
{
    // 获取SDK的版本信息
    DWORD dwMainVer,dwSubVer;
    CHAR szCompileTime[100] = {0};
    BRAC_GetSDKVersion(dwMainVer,dwSubVer,szCompileTime,sizeof(szCompileTime));

    CString logstr;
    logstr.Format("AnyChat Core SDK Version:%d.%d(%s)",dwMainVer,dwSubVer,szCompileTime);

    // 打开(关闭)SDK的日志记录功能
    BRAC_ActiveCallLog(TRUE);

    // 设置SDK核心组件所在目录(注:demo程序只是设置为当前目录,项目中需要设置为实际路径)
    CHAR szCoreSDKPath[MAX_PATH] = {0};
    GetModuleFileName(NULL,szCoreSDKPath,sizeof(szCoreSDKPath));
    (strrchr(szCoreSDKPath,‘\\‘))[1] = 0;
    BRAC_SetSDKOption(BRAC_SO_CORESDK_PATH,szCoreSDKPath,strlen(szCoreSDKPath));

    // 根据BRAC_InitSDK的第二个参数:dwFuncMode,来告诉SDK该如何处理相关的任务(详情请参考开发文档)
    DWORD dwFuncMode = BRAC_FUNC_VIDEO_AUTODISP | BRAC_FUNC_AUDIO_AUTOPLAY /*BRAC_FUNC_AUDIO_CBDATA*/ | BRAC_FUNC_CHKDEPENDMODULE |
        BRAC_FUNC_AUDIO_VOLUMECALC | BRAC_FUNC_NET_SUPPORTUPNP | BRAC_FUNC_FIREWALL_OPEN |
        BRAC_FUNC_AUDIO_AUTOVOLUME | BRAC_FUNC_CONFIG_LOCALINI;
    BRAC_InitSDK(this->GetSafeHwnd(),dwFuncMode);

    // 设置外部音频、视频输入模式
    BOOL bExtVideoInput = 1;
    BRAC_SetSDKOption(BRAC_SO_CORESDK_EXTVIDEOINPUT, (CHAR*)&bExtVideoInput, sizeof(DWORD));
    BOOL bExtAudioInput = 1;
    BRAC_SetSDKOption(BRAC_SO_CORESDK_EXTAUDIOINPUT, (CHAR*)&bExtAudioInput, sizeof(DWORD));

    BRAC_Connect("demo.anychat.cn", 8906);//官方服务器地址
    BRAC_Login("RgbInupter", "", 0);              //登录用户名
    BRAC_EnterRoom(1,"", 0);                         //进入相应房间

}

void CBrPlayerDlg::AnyChatSDKRelease()
{
    BRAC_Release();
}

void CBrPlayerDlg::OnBnClickedButtonPlay()
{
    m_play_ctrl.EnableWindow(FALSE);
    m_stop_ctrl.EnableWindow(TRUE);
    m_openfile_ctrl.EnableWindow(TRUE);

    //CWnd* pWnd = GetDlgItem(IDC_STATIC_SHOW);
    CRect rc;
    //pWnd->GetClientRect(rc);
    m_media_play.GetClientRect(rc);
    BRAC_SetVideoPos(-1, m_media_play.GetSafeHwnd(), rc.left, rc.top, rc.right, rc.bottom);

    CString str_w;
    CString str_h;
    int i_w = 0;
    int i_h = 0;
    m_eidt_width.GetWindowTextA(str_w);
    m_edit_height.GetWindowTextA(str_h);

    i_w = atoi(str_w);
    i_h = atoi(str_h);

    if (i_h && i_w)
    {
        m_nPic_Width  = i_w;
        m_nPic_Height = i_h;
        m_nPic_Fps    = 15;
    }
    else{
        m_nPic_Width  = 288;
        m_nPic_Height = 352;
        m_nPic_Fps    = 15;
    }

    if (m_lpMediaBuf)
    {
        free(m_lpMediaBuf);//释放,因为打开不同视频文件需要重新分配YUV大小,否则会内存溢出
        m_lpMediaBuf=NULL;
    }

    if(!m_lpMediaBuf)  //重新分配
    {
        if (m_img_format == IMG_FORMAT_RGB24) //RGB24
        {
            m_lpMediaBuf = (UCHAR*)malloc(m_nPic_Width * m_nPic_Height * 3);
            memset(m_lpMediaBuf,0,sizeof(UCHAR)*m_nPic_Width * m_nPic_Height * 3);

            if(!m_lpMediaBuf)
            {
                AfxMessageBox("分配缓冲区失败!");
                return;
            }
        }
        else if (m_img_format == IMG_FORMAT_RGB32) //RGB32
        {
            m_lpMediaBuf = (UCHAR*)malloc(m_nPic_Width * m_nPic_Height * 4);
            memset(m_lpMediaBuf,0,sizeof(UCHAR)*m_nPic_Width * m_nPic_Height * 4);

            if(!m_lpMediaBuf)
            {
                AfxMessageBox("分配缓冲区失败!");
                return;
            }
        }
        else
            AfxMessageBox("没有选择输入图像格式:RGB24 或 RGB32!");

    }

    BRAC_UserCameraControl(-1,1);

    // 设置输入视频格式
    if (m_img_format == IMG_FORMAT_RGB24)
    {
        BRAC_SetInputVideoFormat(BRAC_PIX_FMT_RGB24, m_nPic_Width, m_nPic_Height, m_nPic_Fps, 0);

    }
    else if (m_img_format == IMG_FORMAT_RGB32)
    {
        BRAC_SetInputVideoFormat(BRAC_PIX_FMT_RGB32, m_nPic_Width, m_nPic_Height, m_nPic_Fps, 0);

    }

    m_sPlayStatue->stop= 0;

    hReadDataThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)PlayRgb32Thread,this,0,&dwReadThreadID);
}

int CBrPlayerDlg::PlayRgb32Thread(LPARAM lpParam)
{
    CBrPlayerDlg *BrPlayObj =(CBrPlayerDlg*)lpParam;

    FILE *fp_rgb = fopen(BrPlayObj->m_lpMediaFileName,"rb");
    int dwImageSize=0;

    if (BrPlayObj->m_img_format == IMG_FORMAT_RGB24)  //RGB24
    {
        dwImageSize = BrPlayObj->m_nPic_Height * BrPlayObj->m_nPic_Width*3;
    }
    else if (BrPlayObj->m_img_format == IMG_FORMAT_RGB32) //RGB32
    {
        dwImageSize = BrPlayObj->m_nPic_Height * BrPlayObj->m_nPic_Width*4;
    }

    int ret = fread(BrPlayObj->m_lpMediaBuf,1,dwImageSize,fp_rgb);

    BRAC_UserCameraControl(-1,1);

    do
    {
        DWORD dwTimeStamp = GetTickCount();
        ret = BRAC_InputVideoData(BrPlayObj->m_lpMediaBuf, dwImageSize,dwTimeStamp);
        if (ret !=0 )
        {
            AfxMessageBox(ret);
        }
        Sleep(100);

    } while (BrPlayObj->m_sPlayStatue->stop!=1);

    BRAC_UserCameraControl(-1,0);
    CWnd *pWnd =BrPlayObj->GetDlgItem(IDC_STATIC_SHOW);
    CDC *pDC=pWnd->GetDC();
    pDC-> SetBkColor(RGB(0,0,0));
    BrPlayObj->Invalidate();
    BrPlayObj->ReleaseDC(pDC);

    fclose(fp_rgb);
    return 0;
}

void CBrPlayerDlg::OnLbnSelchangeListFile()
{
    // TODO: 在此添加控件通知处理程序代码
}

void CBrPlayerDlg::OnBnClickedButtonStop()
{
    // TODO: 在此添加控件通知处理程序代码
    m_sPlayStatue->stop = 1;

}

void CBrPlayerDlg::OnCbnSelchangeCombo1()
{
    // TODO: 在此添加控件通知处理程序代码
    int index_type = m_select_img_type.GetCurSel();

    if (index_type == IMG_FORMAT_RGB32)
    {
        m_img_format = IMG_FORMAT_RGB32;   //RGB32
    }
    else if (index_type == IMG_FORMAT_RGB24)
    {
        m_img_format = IMG_FORMAT_RGB24;   //RGB24
    }
}

//网络断开消息
void CBrPlayerDlg::OnAnyChatLinkCloseMessage(DWORD dwErrorCode)
{
    //CString str;
    //str.Format("与AnyChat服务器连接断开,出错代码:%d", dwErrorCode);
    m_bSuccessEnterRoom = FALSE;
    m_dwRemoteUserId = -1;
    m_dwLocalUserId  = -1;
}

/**
 *    收到当前房间的在线用户信息
 *    进入房间后触发一次
 *    @param dwUserNum (INT)表示在线用户数(不包含自己)
 *    @param dwRoomId (INT)表示房间ID
 */
void CBrPlayerDlg::OnAnyChatOnlineUserMessage(DWORD dwUserNum, DWORD dwRoomId)
{

    LPDWORD lpdwUserList = (LPDWORD)malloc(sizeof(DWORD)*dwUserNum);
    BRAC_GetOnlineUser(lpdwUserList,dwUserNum);    ///< 真正获取在线用户列表

    ASSERT(m_dwRemoteUserId == -1);
    for(INT i=0; i< (INT)dwUserNum; i++)
    {
        if (lpdwUserList[i] != m_dwLocalUserId) //寻找除本地ID外的在线ID,且只寻找一个
        {
            m_dwRemoteUserId = lpdwUserList[i];

            //设置显示区域并打开远程视频
            CRect rc;
            m_display_remote_user.GetClientRect(rc);
            BRAC_SetVideoPos(m_dwRemoteUserId, m_display_remote_user.GetSafeHwnd(), rc.left, rc.top, rc.right, rc.bottom);
            BRAC_UserCameraControl(m_dwRemoteUserId,TRUE);
            break;
        }
    }
    free(lpdwUserList);
}

// 用户进入/退出房间消息
void CBrPlayerDlg::OnAnyChatUserAtRoomMessage(DWORD dwUserId, BOOL bEnter)
{
    if(!bEnter)        // 用户离开房间
    {
        BRAC_UserCameraControl(dwUserId,FALSE);
        return;
    }
    if (dwUserId != m_dwLocalUserId)
    {
        m_dwRemoteUserId = dwUserId;
    }
    else
        return;

    //设置显示区域并打开远程视频
    CRect rc;
    m_display_remote_user.GetClientRect(rc);
    BRAC_SetVideoPos(m_dwRemoteUserId, m_display_remote_user.GetSafeHwnd(), rc.left, rc.top, rc.right, rc.bottom);
    BRAC_UserCameraControl(m_dwRemoteUserId,TRUE);
    //BRAC_UserSpeakControl(m_dwRemoteUserId,TRUE);
}

// 本地用户登陆消息
void CBrPlayerDlg::OnAnyChatLoginMessage(DWORD dwUserId, DWORD dwErrorCode)
{
    CString str;
    if(dwErrorCode == 0)
    {
        //str.Format("登录AnyChat服务器成功,用户ID:%d", (int)dwUserId);
        m_dwLocalUserId = dwUserId;
    }
    else
    {
        //str.Format("登录AnyChat服务器失败,出错代码:%d", dwErrorCode);
        m_dwLocalUserId = -1;
    }
}        

注意事项:这里读取的RGB数据为裸数据,支持RBG32和RGB24两种方法,根据官方资料说明,还支持YUV420P和H264两种数据格式,下次再尝试做一个和大家分享!

时间: 2024-10-09 14:31:16

项目中利用AnyChat SDK实现将RGB数据作为视频源的实时推送功能的相关文章

在Web微信应用中使用博客园RSS以及Quartz.NET实现博客文章内容的定期推送功能

本篇随笔介绍在Web微信应用中使用博客园RSS以及Quartz.NET实现博客文章内容的定期推送功能,首先对Quartz.NET进行一个简单的介绍和代码分析,掌握对作业调度的处理,然后对博客园RSS内容的处理如何获取,并结合微信消息的群发接口进行内容的发送,从而构建了一个在Web应用中利用作业调度来进行消息发送的业务模型. Quartz.NET是一个开源的作业调度框架,非常适合在平时的工作中,定时轮询数据库同步,定时邮件通知,定时处理数据等. Quartz.NET允许开发人员根据时间间隔(或天)

asp.net中利用session对象传递、共享数据[session用法]

下面介绍Asp.net中利用session对象传递.共享数据用法: 1.传递值: 首先定义将一个文本值或单独一个值赋予session,如下: session["name"]=textbox1.text:将文本1的值赋给了session变量name,当调查到其它页面时,此值可以传递,依然存在,下面是调用或判断此值. If(session["name"]==null) {} Else { lable1.text=session["name"].tos

【转】asp.net中利用session对象传递、共享数据[session用法]

来自:http://blog.unvs.cn/archives/session-transfer-method.html 下面介绍Asp.net中利用session对象传递.共享数据用法: 1.传递值: 首先定义将一个文本值或单独一个值赋予session,如下: session["name"]=textbox1.text:将文本1的值赋给了session变量name,当调查到其它页面时,此值可以传递,依然存在,下面是调用或判断此值. If(session["name"

vue-cli项目中使用mock结合axios-mock-adapter生成模拟数据【转】

1.安装 npm i mockjs axios --save-dev npm i mockjs axios-mock-adapter --save-dev 2.创建数据 // 文件夹配置 ----mock --------data ------------good.js ------------user.js --------index.js users.js: import Mock from "mockjs" const Users = []; for (let i=0; i<

Asp.NET MVC 中使用 SignalR 实现推送功能

一,简介 Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息. 可访问其官方网站:https://github.com/SignalR/ 获取更多资讯. 二.Asp.net SignalR 是个什么东东 Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,SignalR会使用JavaScript的长轮询(lo

使用【百度云推送】第三方SDK实现推送功能详解

之前介绍过如何使用shareSDK实现新浪微博分享功能,今天介绍如何使用百度云推送SDK实现Android手机后台推送功能. 运行效果如下 第一步,如果使用百度的SDK,当然要先成为百度的开发者啦,这个就不详述了.成为开发者之后,我们要建立一个应用,如下图所示 第二步,创建好应用之后,我们点击开方者服务管理,进入工程管理页面,然后点击左侧云推送,进入云推送功能页面,具体如下图 进入云推送详细页面之后,我们点击推送设置,设置好我们的应用的包名,然后点击快速实例,将系统给我们产生的示例代码下载下来

在 Asp.NET MVC 中使用 SignalR 实现推送功能 [转]

在 Asp.NET MVC 中使用 SignalR 实现推送功能 罗朝辉 ( http://blog.csdn.net/kesalin ) CC许可,转载请注明出处 一,简介 Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息. 可访问其官方网站:https://github.com/SignalR/ 获取更多资讯. 二,实现机制

使用【百度云推送】第三方SDK实现推送功能具体解释

之前介绍过怎样使用shareSDK实现新浪微博分享功能,今天介绍怎样使用百度云推送SDK实现Android手机后台推送功能. 执行效果例如以下 第一步,假设使用百度的SDK,当然要先成为百度的开发人员啦,这个就不详述了.成为开发人员之后,我们要建立一个应用,例如以下图所看到的 第二步,创建好应用之后,我们点击开方者服务管理,进入project管理页面,然后点击左側云推送,进入云推送功能页面,详细例如以下图 进入云推送具体页面之后,我们点击推送设置,设置好我们的应用的包名,然后点击高速实例,将系统

在 Asp.NET MVC 中使用 SignalR 实现推送功能

一,简介 Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架.它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息. 二,实现机制 SignalR 的实现机制与 .NET WCF 或 Remoting 是相似的,都是使用远程代理来实现.在具体使用上,有两种不同目的的接口:PersistentConnection 和 Hubs,其中 PersistentConnection 是实现了长