Windows(PC) SDK 实时音视频开发手册

网易云通信服务概要

网易云通信 SDK 提供完善的音视频通话开发框架,提供基于网络的点对点视频通话和语音通话功能,还提供多人视频和音频会议功能,支持通话中音视频设备控制和实时音视频模式切换,支持音视频采集数据回调以实现美颜和变声等自定义功能。

注:SDK 兼容的系统有 Windows xp(sp2及以上)、Windows 7、Windows 8/8.1、Windows 10。SDK 从V3.2.5版本开始全面支持32位和64位程序接入。

开发准备

SDK内容

SDK不提供debug版本的动态链接库供开发者调试,如果遇到问题请联系技术支持或在线客服。

快速接入SDK

关于如何快速集成SDK到现有项目,初始化SDK登录退出,发送/接收第一条IM消息,发送/接收第一条聊天室消息,如何开始基于IM Demo源码开发的信息请前往Windows(PC) SDK Getting Started

SDK数据目录

当收到多媒体消息后,SDK 会负责下载这些多媒体文件,同时SDK 还要记录一些log,因此SDK 需要一个数据缓存目录。该目录由第三方App 通过nim_client_init 初始化接口传入,默认为存放到系统的AppData 目录下,App 传入一个目录名即可,SDK 会自动生成用户数据缓存目录。数据缓存目录默认为"{系统的AppData 目录}\{App 传入的目录名}\NIM\{某个用户对应的用户数据目录}”,还可以由App 完全自定义用户数据目录,需要传入完整的路径,并确保读写权限正确。如果第三方App 需要清除缓存功能,可扫描该目录下的文件,按照你们的规则清理即可。 具体某个用户对应的缓存目录下面包含如下子目录:

SDK 提供了接口nim_tool_get_user_specific_appdata_dir 获取某个用户对应的具体类型的App data 目录(如图片消息文件存放目录,语音消息文件存放目录等)(注意:需要调用nim_free_buf 接口释放其返回的内存)。

接口调用与约定

SDK 提供的所有API 都是 C接口 ,根据模块划分为不同的API 文件,根据业务需要可以显式或者隐式的调用(3.4.0开始)。显式调用下,开发者可以在程序进行到任何时间点按需加载SDK dll, 功能完成后如果不需要继续使用NIM可以卸载SDK dll, 更加灵活和节省内存, 该模式下开发者只需要将SDK包里nim_c_sdk\include 目录下的模块定义头文件(命名方式如nim_xxx_def.h)加入到工程项目中。隐式调用下,开发者除了链接lib文件外,需要将SDK包里nim_c_sdk 目录里的API 头文件(命名方式如nim_xxx.h)以及相关常量定义的头文件等其他头文件一并加入到工程项目中。一般来说,每个模块都有对应的API 头文件和相关常量的定义头文件,命名上一一对应。

SDK 提供了3种类型的接口。

  1. 全局广播类通知的注册接口。

    常见的几类消息或通知是通过该注册的回调函数中通知开发者的,比如收到的消息,会话数据更新,用户相关数据的变更等,开发者需要在登录前提前注册好这些接口,例如:

    C++

     //注册数据同步结果的回调
     nim::DataSync::RegCompleteCb(nbase::Bind(&nim_comp::DataSyncCallback::SyncCallback, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
    
     /* 以下注册的回调函数,都是在收到服务器推送的消息或事件时执行的。因此需要在程序开始时就注册好。 */
     //注册重连、被踢、掉线、多点登录、把移动端踢下线的回调
     nim::Client::RegReloginCb(&nim_comp::LoginCallback::OnReLoginCallback);
     nim::Client::RegKickoutCb(&nim_comp::LoginCallback::OnKickoutCallback);
     nim::Client::RegDisconnectCb(&nim_comp::LoginCallback::OnDisconnectCallback);
     nim::Client::RegMultispotLoginCb(&nim_comp::LoginCallback::OnMultispotLoginCallback);
     nim::Client::RegKickOtherClientCb(&nim_comp::LoginCallback::OnKickoutOtherClientCallback);
     nim::Client::RegSyncMultiportPushConfigCb(&nim_comp::MultiportPushCallback::OnMultiportPushConfigChange);
    
     //注册返回发送消息结果的回调,和收到消息的回调。
     nim::Talk::RegSendMsgCb(nbase::Bind(&nim_comp::TalkCallback::OnSendMsgCallback, std::placeholders::_1));
     nim::Talk::RegReceiveCb(nbase::Bind(&nim_comp::TalkCallback::OnReceiveMsgCallback, std::placeholders::_1));
     nim::Talk::RegRecallMsgsCallback(nbase::Bind(&nim_comp::TalkCallback::OnReceiveRecallMsgCallback, std::placeholders::_1, std::placeholders::_2));
     nim::Talk::RegReceiveMessagesCb(nbase::Bind(&nim_comp::TalkCallback::OnReceiveMsgsCallback, std::placeholders::_1));
     nim::MsgLog::RegMessageStatusChangedCb(nbase::Bind(&nim_comp::TalkCallback::OnMsgStatusChangedCallback, std::placeholders::_1));

    C#

    //注册数据同步结果的回调
    NIM.DataSync.RegCompleteCb(DataSyncDelegate cb);
    //登录结果通知,开发者可以通过注册该事件获取登录结果,或者在调用登录接口NIM.ClientAPI.Login 时填充回调函数
    NIM.ClientAPI.LoginResultHandler
    //注册客户端掉线回调
    NIM.ClientAPI.RegDisconnectedCb(Action handler);
    //注册客户端多点登录通知回调
    NIM.ClientAPI.RegMultiSpotLoginNotifyCb(MultiSpotLoginNotifyResultHandler handler);
    //注册将登录当前账号的其他端踢下线的结果回调
    NIM.ClientAPI.RegKickOtherClientCb(KickOtherClientResultHandler handler);
    //注册当前客户端被踢下线通知回调
    NIM.ClientAPI.RegKickoutCb(KickoutResultHandler handler)
    //注册自动重连结果回调
    NIM.ClientAPI.RegAutoReloginCb(LoginResultDelegate handler, string jsonExtension = null)

     //接收消息
    NIM.TalkAPI.OnReceiveMessageHandler += ReceiveMessageHandler;
    //发送消息结果
    NIM.TalkAPI.OnSendMessageCompleted += SendMessageResultHandler;
    //注册消息撤回结果回调
    NIM.TalkAPI.RegRecallMessageCallback(RecallMessageDelegate cb);

C

    // 数据同步结果通知(nim_data_sync)
    void nim_data_sync_reg_complete_cb(nim_data_sync_cb_func cb, const void *user_data);        
    // 接收会话消息通知(nim_talk)
    void nim_talk_reg_receive_cb(const char *json_extension, nim_talk_receive_cb_func cb, const void *user_data);        
    // 接收系统消息通知(nim_sysmsg)
    void nim_sysmsg_reg_sysmsg_cb(const char *json_extension, nim_sysmsg_receive_cb_func cb, const void *user_data);    
    // 发送消息结果通知(nim_talk)
    void nim_talk_reg_arc_cb(const char *json_extension, nim_talk_arc_cb_func cb, const void *user_data);
    // 发送自定义系统通知的结果通知(nim_talk)
    void nim_talk_reg_custom_sysmsg_arc_cb(const char *json_extension, nim_custom_sysmsg_arc_cb_func cb, const void *user_data);
    // 群组事件通知(nim_team)
    void nim_team_reg_team_event_cb(const char *json_extension, nim_team_event_cb_func cb, const void *user_data);    
    // 帐号被踢通知(nim_client)
    void nim_client_reg_kickout_cb(const char *json_extension, nim_json_transport_cb_func cb, const void *user_data);        
    // 网络连接断开通知(nim_client)
    void nim_client_reg_disconnect_cb(const char *json_extension, nim_json_transport_cb_func cb, const void *user_data);    
    // 将本帐号的其他端踢下线的结果通知(nim_client)
    void nim_client_reg_kickout_other_client_cb(const char *json_extension, nim_json_transport_cb_func cb, const void *user_data); 
    // 好友通知 
    void nim_friend_reg_changed_cb(const char *json_extension, nim_friend_change_cb_func cb, const void *user_data);    
    // 用户特殊关系通知
    void nim_user_reg_special_relationship_changed_cb(const char *json_extension, nim_user_special_relationship_change_cb_func cb, const void *user_data);    
    ...
  1. 异步接口。

    回调函数作为参数,传入执行接口,然后执行接口时,会触发传入的回调函数。注意,回调函数中如果涉及到跨线程资源的需要开发者切换线程操作,即使不涉及到资源问题,也要保证回调函数中不会处理耗时任务,以免堵塞SDK底层线程。

  2. 同步接口。

    为方便开发者使用,我们提供了一些同步接口,调用接口获取的内容同步返回,以block命名的都是同步接口,该类会堵塞SDK线程,谨慎使用。

从版本2.7.0开始,服务器和客户端上线了频控策略,与服务器有交互的接口(接口命名结尾为_online的查询接口以及命令类接口,如同意群邀请等)增加频控控制,开发者关注错误码416。

C++ SDK

为了方便桌面应用开发者更快的接入网易云通信SDK,PC SDK下载包中还提供了官方的C++ 封装层 项目文件及源码,接入和使用方法请看Windows(PC) SDK开发手册(C++ 封装层),目前IM Demo(C++)源码就是通过接入和调用该SDK完成IM功能的。

C# SDK

网易云通信SDK还提供了C# 程序集,方便.net 开发人员接入,PC SDK下载包中包括官方的C# 封装层 项目文件及源码,接入和使用方法请看Windows(PC) SDK开发手册(C# 封装层),目前IM Demo(C#)源码就是通过接入和调用该SDK完成IM功能的。

如果开发者在调用C接口过程中或者解析接口返回结果过程中出现疑问,可以参考和借鉴C++封装层。

初始化SDK

准备工作:将SDK 相关的dll 文件(nim.dll,nim_audio.dll, nim_tools_http.dll, nrtc.dll)放到App 的运行目录下,并将SDK 的配置文件目录nim_conf(目录里包含一个ver_ctrl.dll文件)放到App 的运行目录下。SDK 基于vs2010 开发,如果App 没有对应的运行时库文件(msvcp100.dll和msvcr100.dll),请将其放到App 的运行目录下。

准备工作完成后,在程序启动时,如果直接使用C接口,需要调用LoadLibrary 函数动态加载nim.dll,然后调用GetProcAddress获取API 接口:nim_client_init,调用此接口初始化NIM SDK。同时,SDK 能力的一些参数以及如果SDK 需要连接独立部署的服务器的地址等配置也是在初始化SDK 时传入。例如:

C++

nim::SDKConfig config;

//组装SDK能力参数(必填)
config.database_encrypt_key_ = "Netease";     //string(db key必填,目前只支持最多32个字符的加密密钥!建议使用32个字符)
config.sdk_log_level_ = ;                     //bool 选填,是否需要预下载附件(图片和语音),SDK默认预下载,如果有对带宽流量有较高要求的请关闭该选项,改为上层开发者按需下载附件文件
config.preload_image_quality_ = ;             //int 预下载图片质量,选填,范围0-100
config.preload_image_resize_ = ;             //string 预下载图片基于长宽做内缩略,选填,比如宽100高50,则赋值为100x50,中间为字母小写x
config.sync_session_ack_ = true;             //bool 选填,消息已读未读是否多端同步,默认true
config.login_max_retry_times_ = ;             //int 登录重试最大次数,如需设置建议设置大于3次,默认填0,SDK默认设置次数

//组装SDK独立部署的服务器配置(选填)
config.use_private_server_ = true;
config.rsa_public_key_module_ = "http://xxx";
config.default_nos_download_address_.push_back("http://xxx.xxx.xxx.xxx:xxxx");
config.lbs_address_ = "http://xxx";
config.nos_lbs_address_ = "http://xxx";
config.default_link_address_.push_back("http://xxx.xxx.xxx.xxx:xxxx");
config.default_nos_upload_address_.push_back("http://xxx.xxx.xxx.xxx:xxxx");
config.default_nos_access_address_.push_back("http://xxx.xxx.xxx.xxx:xxxx");

bool ret = nim::Client::Init("45c6af3c98409b18a84451215d0bdd6e", "Netease", "", config); // 载入网易云通信sdk,初始化安装目录和用户目录,第一个参数是appkey(此处填写的是测试示例)

C#

C# 程序在运行时需要使用到 C++ SDK 的dll,必须将SDK相关的dll 文件(nim.dll,nim\_audio.dll, nim\_tools\_http.dll, nrtc.dll)放到最终的生成文件目录,如果缺少运行时库文件(msvcp100.dll和msvcr100.dll)也需要拷贝到生成目录中,否则可能会出现找不到dll的异常。

C# 提供了参数配置类 NimUtility.NimConfig,通过创建该类对象完成参数配置。
var config = new NimUtility.NimConfig();
config.CommonSetting = new SdkCommonSetting();

//组装SDK能力参数
config.CommonSetting.DataBaseEncryptKey = "Netease"; //string(db key必填,目前只支持最多32个字符的加密密钥!建议使用32个字符)
config.CommonSetting.LogLevel = SdkLogLevel.Pro;  //SDK 日志记录级别 
config.CommonSetting.PredownloadAttachmentThumbnail = True;   //bool 选填,是否需要预下载附件(图片和语音),SDK默认预下载,如果有对带宽流量有较高要求的请关闭该选项,改为上层开发者按需下载附件文件
config.CommonSetting.UsePriviteServer = False;    //是否使用自定义服务器,默认False,如果使用自定义服务器,需设置config.PrivateServerSetting 对象

//组装SDK独立部署的服务器配置
config.PrivateServerSetting = new SdkPrivateServerSetting();
config.PrivateServerSetting.RSAPublicKey = "http://xxx";
config.PrivateServerSetting.DownloadServerList = new List<string>({"",""});
config.PrivateServerSetting.LbsAddress = "http://xxx";
config.PrivateServerSetting.NOSLbsAddress = "http://xxx";
config.PrivateServerSetting.LinkServerList = new List<string>({"",""});
config.PrivateServerSetting.UploadServerList = new List<string>({"",""});
config.PrivateServerSetting.AccessServerList = new List<string>({"",""});

bool ret = NIM.ClientAPI.Init("45c6af3c98409b18a84451215d0bdd6e", "NIMCSharpDemo", null, config); // 载入网易云通信sdk,初始化安装目录和用户目录,第一个参数是appkey(此处填写的是测试示例)

C

typedef bool(*nim_client_init)(const char *app_data_dir, const char *app_install_dir, const char *json_extension);

void foo()
{
    //获取SDK初始化接口
    HINSTANCE hInst = LoadLibraryW(L"nim.dll");
    nim_client_init func = (nim_client_init) GetProcAddress(hInst, "nim_client_init");

    //组装SDK初始化参数
    Json::Value config_root;

    //组装SDK能力参数(必填)
    Json::Value config_values;
    config_values[kNIMAppKey] = "xxxx"; //用户的APP key
    config_values[kNIMDataBaseEncryptKey] = ""; //string(db key必填,目前只支持最多32个字符的加密密钥!建议使用32个字符)
    config_values[kNIMPreloadAttach] = ;        //bool 选填,是否需要预下载附件(图片和语音),SDK默认预下载,如果有对带宽流量有较高要求的请关闭该选项,改为上层开发者按需下载附件文件
    config_values[kNIMPreloadImageQuality] = ;  //int 预下载图片质量,选填,范围0-100
    config_values[kNIMPreloadImageResize] = ;   //string 预下载图片基于长宽做内缩略,选填,比如宽100高50,则赋值为100x50,中间为字母小写x
    config_values[nim::kNIMSyncSessionAck] = ;            //bool 选填,消息已读未读是否多端同步,默认true
    config_values[nim::kNIMLoginRetryMaxTimes] = ;        //int 登录重试最大次数,如需设置建议设置大于3次,默认填0,SDK默认设置次数
    config_root[kNIMGlobalConfig] = config_values;

    //组装SDK独立部署的服务器配置(选填)
    Json::Value server_values;
    server_values[kNIMLbsAddress] = "http://xxx";             //lbs地址
    server_values[kNIMNosLbsAddress] = "http://xxx";        //nos lbs地址
    server_values[kNIMDefaultLinkAddress].append("xxx.xxx.xxx.xxx:xxxx");                //默认的link地址
    server_values[kNIMDefaultNosUploadAddress].append("http://xxx.xxx.xxx.xxx:xxxx");     //默认的nos上传地址
    server_values[kNIMDefaultNosDownloadAddress].append("http://xxx.xxx.xxx.xxx:xxxx");    //默认的nos下载地址
    server_values[kNIMRsaPublicKeyModule] = "";                //密钥
    server_values[kNIMRsaVersion] = 0;                        //密钥版本号      
    config_root[kNIMPrivateServerSetting] = server_values;
    config_root[kNIMAppKey] = "45c6af3c98409b18a84451215d0bdd6e"; //必填appkey(此处填写的是测试示例)

    //初始化SDK
    func("appdata path", "app installation path", config_root.toStyledString().c_str());
}

在程序退出前,调用接口nim_client_cleanup 进行NIM SDK 的清理工作,然后调用FreeLibrary 函数释放nim.dll,对于清理工作的注意事项请查看后续的"登出/退出和清理SDK"章节。

C++

nim::Client::Cleanup();

C#

NIM.ClientAPI.Cleanup();

C

typedef    void(*nim_client_cleanup)(const char *json_extension);

void foo()
{
    nim_client_cleanup func = (nim_client_cleanup) GetProcAddress(hInst, "nim_client_cleanup");
    func("");
    FreeLibrary(hInst);
}

登录与登出

登录集成必读

客户端代理

SDK目前支持配置代理,支持Socket4/5,暂不支持Http代理,音视频模块只支持Socket5代理。

C++

void foo()
{
    nim::Global::SetProxy(nim::kNIMProxySocks5, "123.123.123.123", "400", "test", "test");
}

C#

void foo()
{
    NIM.GlobalAPI.SetProxy(NIM.NIMProxyType.kNIMProxySocks5, "123.123.123.123", "400", "test", "test");
}

C

typedef void(*nim_global_set_proxy)(NIMProxyType, const char*, int, const char*, const char*);

void foo()
{
    nim_global_set_proxy func = (nim_global_set_proxy) GetProcAddress(hInst, "nim_global_set_proxy");
    func(kNIMProxySocks5, "123.123.123.123", "400", "test", "test");
}

首次登录

SDK 登录后会同步群信息,离线消息,漫游消息,系统通知等数据,所以首次登录前需要 提前注册所需的全局广播类通知的回调 (具体说明请参阅API 文档),以下以注册最近会话变更通知回调为例:

C++

void OnSessionChangeCallback(nim::NIMResCode rescode, const nim::SessionData& data, int total_unread_counts)
{
    if (rescode != nim::kNIMResSuccess)
    {
        return;
    }

    switch (data.command_)
    {
    case nim::kNIMSessionCommandAdd:
    case nim::kNIMSessionCommandUpdate:
    case nim::kNIMSessionCommandMsgDeleted:
    break;
    ···
    }
}

void foo()
{
    //注册全局会话列表变更通知函数
    nim::Session::RegChangeCb(&OnSessionChangeCallback);
}

C#

//C#通过订阅会话列表变更事件来获取更新通知:
NIM.Session.SessionAPI.RecentSessionChangedHandler += OnSessionChanged;

private void OnSessionChanged(object sender, SessionChangedEventArgs e)
{
    //根据会话列表变更类型进行相应的操作
    if (e.Info.Command == NIMSessionCommand.kNIMSessionCommandAdd ||
        e.Info.Command == NIMSessionCommand.kNIMSessionCommandUpdate)
    {
        //TODO:会话列表新增、更新

    }
    else if (e.Info.Command == NIMSessionCommand.kNIMSessionCommandRemove)
    {
        //TODO:删除会话项
    }
}

C

//全局会话列表变更通知函数
void CallbackSessionChange(int rescode, const char *result, int total_unread_counts, const char *json_extension, const void *user_data)
{
    if (rescode == kNIMResSuccess)
    {
        Json::Value value;
        Json::Reader reader;
        if (reader.parse(result, value))
        { 
            nim::NIMSessionCommand command = nim::NIMSessionCommand(value[nim::kNIMSessionCommand].asInt());
            switch (command)
            {
                case kNIMSessionCommandAdd:
                    ...
                case kNIMSessionCommandUpdate:    
                    ...
                ...                
            }

        }
    }
    else
    {
        ...
    }
}

typedef void(*nim_session_reg_change_cb)(const char *json_extension, nim_session_change_cb_func cb, const void *user_data);

void foo()
{
    //注册全局会话列表变更通知函数
    nim_session_reg_change_cb func = (nim_session_reg_change_cb)GetProcAddress(hInst, "nim_session_reg_change_cb");
    func(nullptr, &CallbackSessionChange, nullptr);    // 会话列表变更通知(nim_session)
}

登录

C++

void OnLoginCallback(const nim::LoginRes& login_res, const void* user_data)
{
    if (login_res.res_code_ == nim::kNIMResSuccess)
    {
        if (login_res.login_step_ == nim::kNIMLoginStepLogin)
        {
            ···
        }
    }
    else
    {
        ···
    }
}

void foo()
{
    nim::Client::Login(app_key, "app acount", "token", &OnLoginCallback);
}

C#

private void HandleLoginResult(NIM.NIMLoginResult result)
{
    //在UI线程进行处理登录结果
    Action action = () =>
    {
        if (result.LoginStep == NIM.NIMLoginStep.kNIMLoginStepLogin)
        {
            if (result.Code == NIM.ResponseCode.kNIMResSuccess)
            {
                //TODO:登录成功
            }
            else
            {
                //TODO:登录失败
            }
        }
    };
    this.Invoke(action);
}

private void foo()
{
    NIM.ClientAPI.Login(appkey, "app acount", "token", HandleLoginResult);
}

C

void CallbackLogin(const char* res, const void *user_data)
{
    Json::Value values;
    Json::Reader reader;
    if (reader.parse(res, values) && values.isObject())
    {
        if (values[kNIMLoginStep].asInt() == kNIMLoginStepLogin && values[kNIMErrorCode].asInt() == 200)
        {
            ...
        }
        ...
    }
}

typedef    void(*nim_client_login)(const char *app_key, const char *account, const char *password, const char *json_extension, nim_json_transport_cb_func cb, const void* user_data);

void foo()
{
    nim_client_login func = (nim_client_login) GetProcAddress(hInst, "nim_client_login");
    func("app key", "app account", "token", nullptr, &CallbackLogin, nullptr);
    //app key: 应用标识,不同应用之间的数据(用户,消息,群组等)是完全隔离的。开发自己的应用时,需要替换成自己申请来的app key
    //注意:替换成客户自己的app key之后,请不要对登录密码进行md5加密。
}

自动重新登录

SDK 在网络连接断开后,会监听网络状况,如果网络状况好转,那么SDK 会进行重新登录,登录结果可以在注册的重新登录回调函数中处理。

注意 自动重新登录的机制可能会失效:

如果重新登录回调函数返回的的错误号既不是kNIMResSuccess,也不是网络错误相关错误号(kNIMResTimeoutError或者kNIMResConnectionError),那么说明自动重新登录的机制已经失效,需要开发者退回到登录界面进行手动重新登录。(详见nim_client_reg_auto_relogin_cb接口说明)

手动重新登录

SDK 在重新登录失败后,可以由用户手动调用重新登录接口nim_client_relogin。

c++

nim::Client::Relogin();

c#

NIM.ClientAPI.Relogin();

c

typedef void(*nim_client_relogin)(const char *json_extension);
void foo()
{
    nim_client_relogin func = (nim_client_relogin) GetProcAddress(hInst, "nim_client_relogin");
    func(nullptr);
}

登出/退出和清理SDK

执行接口nim_client_logout 进行登出或退出,登出或退出的过程因为涉及到和服务器的交互以及需要将缓存中的数据持久化到本地,因此根据实际情况回调通知会有1s到20s左右的延迟。清理SDK必须在完全退出(收到退出回调)后执行。

Trick:因为退出和其他接口一样,总是在某些情况下会出现未及时返回需要上层界面等待的情况(比如网络原因导致服务器返回慢甚至超时,缓存数据大持久化到本地慢等原因),因此开发者可以通过暂时隐藏UI,让进程等待退出完成的回调后再执行清理SDK,退出程序的工作。

C++

void LoginCallback::OnLogoutCallback(nim::NIMResCode res_code)
{
    //如果是退出程序
    nim::Client::Cleanup();
    ...

    //如果是登出到登录界面,这里不能执行清理SDK工作,否则会报错
    ...
}

void foo()
{
    //登出到登录界面(nim::kNIMLogoutChangeAccout) 或者 退出程序(nim::kNIMLogoutAppExit)
    nim::Client::Logout(nim::kNIMLogoutAppExit, &OnLogoutCallback);
}

C#

private void foo()
{
    System.Threading.Semaphore s = new System.Threading.Semaphore(0, 1);
    NIM.ClientAPI.Logout(NIM.NIMLogoutType.kNIMLogoutAppExit, (r) =>
    {
        s.Release();
    });
    //需要logout执行完才能退出程序
    s.WaitOne(TimeSpan.FromSeconds(10));
    NIM.ClientAPI.Cleanup();
}

C

void CallbackLogout(const char* res, const void *user_data)
{
    ...
}

typedef    void(*nim_client_logout)(nim::NIMLogoutType logout_type, const char *json_extension, nim_json_transport_cb_func cb, const void* user_data);

void foo()
{
    nim_client_logout func = (nim_client_logout) GetProcAddress(hInst, "nim_client_logout");
    func(kNIMLogoutAppExit, nullptr, &CallbackLogout, nullptr);
}

断网重连通知

通过接口nim_client_reg_disconnect_cb 来注册连接断开回调函数,断开连接后根据实际需要做后续处理(比如提示用户)。

C++

void OnDisconnectCallback()
{
    ···
}

void foo()
{
    nim::Client::RegDisconnectCb(&OnDisconnectCallback);
}

C#

private foo()
{
    NIM.ClientAPI.RegDisconnectedCb(() =>
    {
         MessageBox.Show("网络连接断开,网络恢复后后会自动重连");
    });
}

C

void CallbackDisconnect(const char* res, const void* user_data)
{
    //解析res
}

typedef void(*nim_client_reg_disconnect_cb)(const char *json_extension, nim_json_transport_cb_func cb, const void* user_data);

void foo()
{
    nim_client_reg_disconnect_cb func = (nim_client_reg_disconnect_cb) GetProcAddress(hInst, "nim_client_reg_disconnect_cb");
    func(nullptr, &CallbackDisconnect, nullptr);
}

被踢出通知

通过接口nim_client_reg_kickout_cb 来注册被踢出回调函数,当收到此通知时,一般是退出程序然后回到登录窗口,回调函数参数中详细说明了被踢出的原因。

C++

void OnKickoutCallback(const nim::KickoutRes& res)
{
    ···
}

void foo()
{
    nim::Client::RegKickoutCb(&OnKickoutCallback);
}

C#

private foo()
{
    NIM.ClientAPI.RegKickoutCb((r) =>
    {
        MessageBox.Show(r.Dump(), "被踢下线,请注意账号安全");
    });
}

C

void CallbackKickout(const char* res, const void* user_data)
{
    //解析res
}

typedef void(*nim_client_reg_kickout_cb)(const char *json_extension, nim_json_transport_cb_func cb, const void* user_data);

void foo()
{
    nim_client_reg_kickout_cb func = (nim_client_reg_kickout_cb) GetProcAddress(hInst, "nim_client_reg_kickout_cb");
    func(nullptr, &CallbackKickout, nullptr);
}

多端登录

通过调用nim_client_reg_multispot_login_notify_cb 来注册多端登录通知的回调函数,当用户在某个客户端登录时,其他没有被踢掉的端会触发这个回调函数,并携带当前时间登录的设备列表的数据。

网易云通信内置踢人策略为:移动端(Android,iOS)互踢,桌面端(PC,Web)互踢,移动端和桌面端共存。

如果当前的互踢策略无法满足业务需求的话,可以联系我们取消内置互踢,根据多端登录的回调和当前的设备列表,判断本设备是否需要被踢出。如果需要踢出,直接调用登出接口并在界面上给出相关提示即可。

c++

void LoginCallback::OnMultispotLoginCallback(const nim::MultiSpotLoginRes& res)
{
    ...
}
nim::Client::RegMultispotLoginCb(&nim_comp::LoginCallback::OnMultispotLoginCallback);

c#

//注册多端登录通知回调
NIM.ClientAPI.RegMultiSpotLoginNotifyCb(OnMultiSpotLogin);

//result 包含多端登录的其他端信息
private static void OnMultiSpotLogin(NIMMultiSpotLoginNotifyResult result)
{

}

c

static void CallbackMutliSpotLogin(const char* json_res, const void* user_data)
{
    Json::Reader reader;
    Json::Value values;
    if (json_res != nullptr && reader.parse((std::string)json_res, values) && values.isObject())
    {
        ...
    }
}

typedef void(*nim_client_reg_multispot_login_notify_cb)(const char *json_extension, nim_json_transport_cb_func cb, const void *user_data);
void foo()
{
    nim_client_reg_multispot_login_notify_cb func = (nim_client_reg_multispot_login_notify_cb) GetProcAddress(hInst, "nim_client_reg_multispot_login_notify_cb");
    func(nullptr, &CallbackMutliSpotLogin, nullptr);
}

网易云通信 ID

网易云通信的每个用户由网易云通信 ID 唯一标识。网易云通信 ID是由用户帐号和token成功同步到网易云通信服务器后自动分配的,可以通过 NIMMessage 的 from 字段获取消息发送方的网易云通信 ID。

音视频网络通话

NIM的音视频使用的是私有协议,与web sdk中的webrtc互通需要在点对点发起和多人通话的创建扩展json中打开webrtc兼容开关。kNIMVChatWebrtc代表是否需要开启web的webrtc互通,对于WebRTC beta版,在有 webrtc 客户端参与的房间中需要打开该开关,如果没有 webrtc 客户端参与,不要打开该开关。

static const char *kNIMVChatWebrtc            = "webrtc";            /**< int, 是否支持webrtc互通,1表示是,0表示否。默认否 */

初始化与清理

在调用音视频网络通话相关功能前,需要在nim.dll所在目录中有对应版本的nrtc.dll文件。

初始化NIM VCHAT,需要在SDK的nim_client_init成功之后
bool nim_vchat_init(const char *json_extension);
    param[in] json_extension 无效的扩展字段
    return bool 初始化结果,如果是false则音视频网络通话所有接口调用无效

清理NIM VCHAT,需要在SDK的nim_client_cleanup之前
void nim_vchat_cleanup(const char *json_extension);
    param[in] json_extension 无效的扩展字段
    return void 无返回值

C++

void Init ()
{
    // 初始化网易云通信SDK
    ···

    // 初始化网易云通信音视频
    bool ret = nim::VChat::Init("");
    assert(ret);
}

void Cleaup()
{
    nim::VChat::Cleanup();

    // 清理网易云通信SDK
    ···
}

C#

void Init ()
{
    // 初始化网易云通信SDK
    ···

    // 初始化网易云通信音视频
    NIM.VChatAPI.Init();
}

void Cleaup()
{
    NIM.VChatAPI.Cleanup();

    // 清理网易云通信SDK
    ···
}

C

typedef    bool(*nim_vchat_init)(const char *json_extension);
typedef    void(*nim_vchat_cleanup)(const char *json_extension);

void Init ()
{
    // 初始化网易云通信SDK
    ···

    // 初始化网易云通信音视频
    nim_vchat_init func = (nim_vchat_init) GetProcAddress(hInst, "nim_vchat_init");
    func("");
}

void Cleaup()
{
    nim_vchat_cleanup func = (nim_vchat_cleanup) GetProcAddress(hInst, "nim_vchat_cleanup");
    func("");

    // 清理网易云通信SDK
    ···
}

网络探测

底层进行udp的网络探测,在json_extension中允许扩展填写探测类型和探测时间限制。本接口是通话前探测,通话过程中使用通话中的网络状态回调,不建议频繁使用此接口。

typedef unsigned __int64(*nim_vchat_net_detect)(const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

C++

void NetDetectCallback(int, NetDetectCbInfo)
{
    //...
}
void foo()
{
    nim::VChat::NetDetect(&NetDetectCallback);
}

C#

...

C

typedef void(*nim_vchat_net_detect)(const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_net_detect func = (nim_vchat_net_detect) GetProcAddress(hInst, "nim_vchat_net_detect");
    func("", NULL, NULL);
}

返回的 nim_vchat_opt_cb_func 中的json扩展:

static const char *kNIMNetDetectTaskId        = "task_id";        /**< uint64 任务id */
static const char *kNIMNetDetectLoss        = "loss";            /**< int 丢包率百分比 */
static const char *kNIMNetDetectRttmax        = "rttmax";            /**< int rtt 最大值 */
static const char *kNIMNetDetectRttmin        = "rttmin";            /**< int rtt 最小值 */
static const char *kNIMNetDetectRttavg        = "rttavg";            /**< int rtt 平均值 */
static const char *kNIMNetDetectRttmdev        = "rttmdev";        /**< int rtt 偏差值 mdev */
static const char *kNIMNetDetectDetail        = "detailinfo";        /**< string 扩展信息 */

在上述返回的字段中,loss、rttavg、rttmdev这三个值最能反应当前客户端的实际网络情况。由这三个值可以计算出当前的网络状况指数:

网络状况指数 = (loss/20)*50% +(rttavg/1200)*25% +(rttmdev/150)*25%

经过我们的反复测试,现提供三个网络状况指数节点

网络状况指数节点 loss(%) rttavg(ms) rttmdev(ms) 网络状况指数
A 3 500 50 0.2625
B 10 800 80 0.55
C 20 1200 150 1

备注:

  1. 当网络状况指数≤0.2625时,网络状况非常好,音视频通话流畅;

  2. 当0.2625<网络状况指数≤0.55时,网络状况好,音视频通话偶有卡顿;

  3. 当0.55<网络状况指数≤1时,网络状况差,音频通话流畅;

  4. 当网络状况指数>1时,网络状况非常差,音频通话偶有卡顿。

设置通话回调函数

在使用音视频网络通话前,需要先设置通话回调

注册通话回调函数
void nim_vchat_set_cb_func(nim_vchat_cb_func cb);

回调原型
void nim_vchat_cb_func(NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json_extension, const void *user_data);
    具体参数说明见nim_vchat_def.h;在接下来的功能中也将一一说明

C++

void VChatCb(nim::NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json, const void*)
{
    switch (type)
    {
    case nim::kNIMVideoChatSessionTypeStartRes:
    {
        ···
    }
    break;
    case nim::kNIMVideoChatSessionTypeInviteNotify:
    {
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            ···
        }
    }
    break;
    }
}

void foo()
{
    nim::VChat::SetCbFunc(&VChatCb);
}

C#

private static NIM.NIMVChatSessionStatus _vchatHandlers;

foo()
{
    _vchatHandlers.onSessionStartRes = OnSessionStartRes;
    _vchatHandlers.onSessionInviteNotify = OnSessionInviteNotify;
    _vchatHandlers.onSessionCalleeAckRes = OnSessionCalleeAckRes;
    _vchatHandlers.onSessionCalleeAckNotify = OnSessionCalleeAckNotify;
    _vchatHandlers.onSessionControlRes = OnSessionControlRes;
    _vchatHandlers.onSessionControlNotify = OnSessionControlNotify;
    _vchatHandlers.onSessionConnectNotify = OnSessionConnectNotify;
    _vchatHandlers.onSessionPeopleStatus = OnSessionPeopleStatus;
    _vchatHandlers.onSessionNetStatus = OnSessionNetStatus;
    _vchatHandlers.onSessionHangupRes = OnSessionHangupRes;
    _vchatHandlers.onSessionHangupNotify = OnSessionHangupNotify;

    _vchatHandlers.onSessionSyncAckNotify =(channel_id,code,uid,  mode, accept,  time, client)=>
    {

    };

    //注册音视频会话交互回调
    NIM.VChatAPI.SetSessionStatusCb(_vchatHandlers);
}

C

void VChatCb(nim::NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json, const void*)
{
    switch (type)
    {
    case nim::kNIMVideoChatSessionTypeStartRes:
    {
        ···
    }
    break;
    case nim::kNIMVideoChatSessionTypeInviteNotify:
    {
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            ···
        }
    }
    break;
    }
}

typedef void(*nim_vchat_set_cb_func)(nim_vchat_cb_func cb, const void *user_data);

void foo()
{
    // 初始化网易云通信SDK 
    ···

    // 初始化网易云通信音视频
    nim_vchat_set_cb_func func = (nim_vchat_set_cb_func) GetProcAddress(hInst, "nim_vchat_set_cb_func");
    func(&VChatCb, nullptr);
}

发起通话

发起一个音视频网络通话,结果将在nim_vchat_cb_func中返回

启动通话
bool nim_vchat_start(NIMVideoChatMode mode, const char* json_extension, const void *user_data)
    param[in] mode NIMVideoChatMode 启动音视频通话类型 见nim_vchat_def.h
    param[in] json_extension Json string 扩展,kNIMVChatUids成员id列表 如{ "uids":["uid_temp"]}
    param[in] user_data 无效的扩展字段
    return bool true 调用成功,false 调用失败可能有正在进行的通话

异步回调见nim_vchat_cb_func中type为以下值时
    kNIMVideoChatSessionTypeStartRes,            //创建通话结果 code=200成功,json无效
    kNIMVideoChatSessionTypeConnect,            //通话中链接状态通知 code对应NIMVChatConnectErrorCode, 非200均失败并底层结束 json无效
    kNIMVideoChatSessionTypeInfoNotify            //实时状态    json返回    {"static_info":{ "video": {"fps":20, "KBps":200, "width":1280,"height":720}, "audio": {"fps":17, "KBps":3}}} \n

C++

void foo()
{
    nim::NIMVideoChatMode mode = nim::kNIMVideoChatModeAudio;

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = "session_id";
    value[nim::kNIMVChatUids].append("uid");
    value[nim::kNIMVChatSound] = "video_chat_tip_receiver.aac";
    value[nim::kNIMVChatNeedBadge] = 0;
    value[nim::kNIMVChatNeedFromNick] = 0;
    if (1)
    {
        std::string video_quality = GetConfigValue("video_quality");
        std::string audio_record = GetConfigValue("audio_record");
        std::string video_record = GetConfigValue("video_record");
        value[nim::kNIMVChatVideoQuality] = atoi(video_quality.c_str());
        value[nim::kNIMVChatRecord] = atoi(audio_record.c_str());
        value[nim::kNIMVChatVideoRecord] = atoi(video_record.c_str());
    }
    std::string json_value = fs.write(value);
    nim::VChat::Start(mode, nbase::UTF16ToUTF8(mode == nim::kNIMVideoChatModeAudio ? L"语音通话邀请test"), "test custom info", json_value.c_str());
}

C#

void foo()
{
    NIMVChatInfo info = new NIMVChatInfo();
    info.Uids = new System.Collections.Generic.List<string>();
    info.Uids.Add("user_id");
    VChatAPI.Start(NIMVideoChatMode.kNIMVideoChatModeVideo, info);//邀请user_id进行语音通话
}

C

typedef bool(*nim_vchat_start)(NIMVideoChatMode mode, const char* apns_text, const char* custom_info, const char* json_extension, const void *user_data);

void foo()
{
    nim::NIMVideoChatMode mode = nim::kNIMVideoChatModeAudio;

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = "session_id";
    value[nim::kNIMVChatUids].append("uid");
    value[nim::kNIMVChatSound] = "video_chat_tip_receiver.aac";
    value[nim::kNIMVChatNeedBadge] = 0;
    value[nim::kNIMVChatNeedFromNick] = 0;
    if (1)
    {
        std::string video_quality = GetConfigValue("video_quality");
        std::string audio_record = GetConfigValue("audio_record");
        std::string video_record = GetConfigValue("video_record");
        value[nim::kNIMVChatVideoQuality] = atoi(video_quality.c_str());
        value[nim::kNIMVChatRecord] = atoi(audio_record.c_str());
        value[nim::kNIMVChatVideoRecord] = atoi(video_record.c_str());
L"视频通话邀请test"), "test custom info", json_value);
    }
    std::string json_value = fs.write(value);

    nim_vchat_start func = (nim_vchat_start) GetProcAddress(hInst, "nim_vchat_start");
    func(mode, nbase::UTF16ToUTF8(mode == nim::kNIMVideoChatModeAudio ? L"语音通话邀请test" : L"视频通话邀请test"), "test custom info", json_value.c_str(), nullptr);
}

收到通话邀请

通话邀请由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeInviteNotify,其中code无效,json返回 kNIMVChatUid为发起者,kNIMVChatType对应NIMVideoChatMode

通话邀请回应

通知邀请方自己的处理结果,结果将在nim_vchat_cb_func中返回

邀请回应接口
bool nim_vchat_callee_ack(__int64 channel_id, bool accept, const char* json_extension, const void *user_data)
    param[in] channel_id 音视频通话通道id
    param[in] accept true 接受,false 拒绝
    param[in] json_extension 无效的扩展字段
    param[in] user_data 无效的扩展字段
    return bool true 调用成功,false 调用失败(可能channel_id无匹配,如要接起另一路通话前先结束当前通话)

异步回调见nim_vchat_cb_func中type为以下值时
    kNIMVideoChatSessionTypeCalleeAckRes,        //接受拒绝结果 json 无效 code: 200:成功 9103 : 已经在其他端接听或拒绝过这通电话

C++

void foo(uint64_t channel_id, bool accept, const std::string& session_id)
{
    std::string json_value;
    if (accept)
    {
        std::string video_quality = GetConfigValue("video_quality");
        std::string audio_record = GetConfigValue("audio_record");
        std::string video_record = GetConfigValue("video_record");
        Json::FastWriter fs;
        Json::Value value;
        value[nim::kNIMVChatVideoQuality] = atoi(video_quality.c_str());
        value[nim::kNIMVChatRecord] = atoi(audio_record.c_str());
        value[nim::kNIMVChatVideoRecord] = atoi(video_record.c_str());
        value[nim::kNIMVChatSessionId] = session_id;
        json_value = fs.write(value);
    }
    nim::VChat::CalleeAck(channel_id, accept, json_value);
}

C#

void foo()
{
    NIM.NIMVChatInfo info = new NIM.NIMVChatInfo();
    NIM.VChatAPI.CalleeAck(channel_id, true, info);
}

C

typedef bool(*nim_vchat_callee_ack)(__int64 channel_id, bool accept, const char* json_extension, const void *user_data);

void foo(uint64_t channel_id, bool accept, const std::string& session_id)
{
    std::string json_value;
    if (accept)
    {
        std::string video_quality = GetConfigValue("video_quality");
        std::string audio_record = GetConfigValue("audio_record");
        std::string video_record = GetConfigValue("video_record");
        Json::FastWriter fs;
        Json::Value value;
        value[nim::kNIMVChatVideoQuality] = atoi(video_quality.c_str());
        value[nim::kNIMVChatRecord] = atoi(audio_record.c_str());
        value[nim::kNIMVChatVideoRecord] = atoi(video_record.c_str());
        value[nim::kNIMVChatSessionId] = session_id;
        json_value = fs.write(value);
    }

    nim_vchat_callee_ack func = (nim_vchat_callee_ack) GetProcAddress(hInst, "nim_vchat_callee_ack");
    func(channel_id, accept, json_value.c_str(), nullptr);
}

收到被邀请方的回应

被邀请方的回应由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeCalleeAckNotify,其中code无效,json返回 kNIMVChatUid为发起者,kNIMVChatType对应VideoChatMode, kNIMVChatAccept

收到自己在其他端的同步回应通知

本人在其他端回应了由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeSyncAckNotify,其中code无效,json返回 kNIMVChatTime,kNIMVChatType(对应NIMVideoChatMode),kNIMVChatAccept,kNIMVChatClient

设置通话类型

开始通话,或者和对方协商转换通话类型后,告诉SDK当前的通话类型(不告知对方)

bool nim_vchat_set_talking_mode(NIMVideoChatMode mode, const char* json_extension)
    param[in] mode NIMVideoChatMode 音视频通话类型 见nim_vchat_def.h
    param[in] json_extension 无效的扩展字段
    return bool true 调用成功,false 调用失败

C++

void foo()
{
    nim::VChat::SetTalkingMode(nim::kNIMVideoChatModeVideo, "");
}

C#

void foo()
{
    NIM.VChatAPI.SetMode(NIM.NIMVideoChatMode.kNIMVideoChatModeAudio);
}

C

typedef bool(*nim_vchat_set_talking_mode)(NIMVideoChatMode mode, const char* json_extension);

void foo()
{
    nim_vchat_set_talking_mode func = (nim_vchat_set_talking_mode) GetProcAddress(hInst, "nim_vchat_set_talking_mode");
    func(nim::kNIMVideoChatModeVideo, "");
}

通话控制

协商或通知对方当前会话的一些变化

bool nim_vchat_control(__int64 channel_id, NIMVChatControlType type, const char* json_extension, const void *user_data)
    param[in] channel_id 音视频通话通道id
    param[in] type NIMVChatControlType 见nim_vchat_def.h
    param[in] json_extension 无效的扩展字段
    param[in] user_data 无效的扩展字段
    return bool true 调用成功,false 调用失败

异步回调见nim_vchat_cb_func中type为以下值时
    kNIMVideoChatSessionTypeControlRes,        //code=200成功,json返回 kNIMVChatType对应NIMVChatControlType

C++

void foo(int64_t channel_id)
{
    nim::VChat::Control(channel_id, nim::kNIMTagControlOpenAudio);
}

C#

void foo(long channel_id)
{
    NIM.VChatAPI.ChatControl(channel_id, NIM.NIMVChatControlType.kNIMTagControlOpenAudio);
}

C

typedef bool(*nim_vchat_control)(__int64 channel_id, NIMVChatControlType type, const char* json_extension, const void *user_data);

void foo(int64_t channel_id)
{
    nim_vchat_control func = (nim_vchat_control) GetProcAddress(hInst, "nim_vchat_control");
    func(channel_id, nim::kNIMTagControlCloseAudio, "", nullptr);
}

对方通话控制的通知

收到对方的通话控制消息,由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeControlNotify,其中code无效,json返回 kNIMVChatUid为发起者,kNIMVChatType对应NIMVChatControlType, kNIMVChatAccept

通话中成员进出

通话中成员进出由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypePeopleStatus,其中code对应NIMVideoChatSessionStatus, json返回kNIMVChatUid,对方离开后应主动结束通话

通话中网络变化

通话中网络变化由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeNetStatus,其中code对应NIMVideoChatSessionNetStat, json返回kNIMVChatUid

观众模式

设置观众模式(多人模式下),全局有效(重新发起时也生效),观众模式能减少运行开销

void nim_vchat_set_viewer_mode(bool viewer)
    param[in] viewer 是否观众模式
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetViewerMode(true);
}

C#

void foo()
{
    NIM.VChatAPI.SetViewerMode(true);
}

C

typedef void(*nim_vchat_set_viewer_mode)(bool viewer);

void foo()
{
    nim_vchat_set_viewer_mode func = (nim_vchat_set_viewer_mode) GetProcAddress(hInst, "nim_vchat_set_viewer_mode");
    func(true);
}

获取当前是否是观众模式

bool nim_vchat_get_viewer_mode()
    return bool true 观众模式,false 非观众模式

C++

void foo()
{
    bool view = nim::VChat::GetViewerMode();
}

C#

void foo()
{
    bool view = NIM.VChatAPI.GetViewerMode();
}

C

typedef bool(*nim_vchat_get_viewer_mode)();

void foo()
{
    nim_vchat_get_viewer_mode func = (nim_vchat_get_viewer_mode) GetProcAddress(hInst, "nim_vchat_get_viewer_mode");
    bool view = func();
}

音频静音

设置音频静音,全局有效(重新发起时也生效)

void nim_vchat_set_audio_mute(bool muted)
    param[in] muted true 静音,false 不静音
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetAudioMuted(true);
}

C#

void foo()
{
    NIM.VChatAPI.SetAudioMute(true);
}

C

typedef void(*nim_vchat_set_audio_mute)(bool muted);

void foo()
{
    nim_vchat_set_audio_mute func = (nim_vchat_set_audio_mute) GetProcAddress(hInst, "nim_vchat_set_audio_mute");
    return func(true);
}

获取音频静音状态

bool nim_vchat_audio_mute_enabled()
    return bool true 静音,false 不静音

C++

void foo()
{
    bool mute = nim::VChat::GetAudioMuteEnabled();
}

C#

void foo()
{
    bool mute = NIM.VChatAPI.GetAudioMuteEnabled();
}

C

typedef bool(*nim_vchat_audio_mute_enabled)();

void foo()
{
    nim_vchat_audio_mute_enabled func = (nim_vchat_audio_mute_enabled) GetProcAddress(hInst, "nim_vchat_audio_mute_enabled");
    bool mute = func();
}

对端视频自动旋转

设置不自动旋转对方画面,默认打开,全局有效(重新发起时也生效)

void nim_vchat_set_rotate_remote_video(bool rotate)
    param[in] rotate true 自动旋转,false 不旋转
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetRotateRemoteVideo(true);
}

C#

void foo()
{
    NIM.VChatAPI.SetRotateRemoteVideo(true);
}

C

typedef void(*nim_vchat_set_rotate_remote_video)(bool rotate);

void foo()
{
    nim_vchat_set_rotate_remote_video func = (nim_vchat_set_rotate_remote_video) GetProcAddress(hInst, "nim_vchat_set_rotate_remote_video");
    return func(true);
}

获取自动旋转对方画面设置状态

bool nim_vchat_rotate_remote_video_enabled()
    return bool true 自动旋转,false 不旋转

C++

void foo()
{
    bool rotate = nim::VChat::IsRotateRemoteVideo();
}

C#

void foo()
{
    bool rotate = NIM.VChatAPI.IsRotateRemoteVideo();
}

C

typedef bool(*nim_vchat_rotate_remote_video_enabled)();

void foo()
{
    nim_vchat_rotate_remote_video_enabled func = (nim_vchat_rotate_remote_video_enabled) GetProcAddress(hInst, "nim_vchat_rotate_remote_video_enabled");
    bool rotate = func();
}

发送画面裁剪

设置发送画面裁剪模式,默认不裁剪,全局有效(重新发起时也生效)。裁剪模式以原画面的横竖方向为准裁剪,如果元画面1:1,需要裁剪成4:3,则优先为横屏

void nim_vchat_set_video_frame_scale(enum NIMVChatVideoFrameScaleType type)
    param[in] type 裁剪模式
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetVideoFrameScaleType(kNIMVChatVideoFrameScale16x9);
}

C#

void foo()
{
        NIM.VChatAPI.SetVideoFrameScale(kNIMVChatVideoFrameScale16x9);
}

C

typedef void(*nim_vchat_set_video_frame_scale)(enum NIMVChatVideoFrameScaleType type);

void foo()
{
    nim_vchat_set_video_frame_scale func = (nim_vchat_set_video_frame_scale) GetProcAddress(hInst, "nim_vchat_set_video_frame_scale");
    return func(kNIMVChatVideoFrameScale16x9);
}

获取发送画面裁剪模式

int nim_vchat_get_video_frame_scale_type()
    return int 返回NIMVChatVideoFrameScaleType裁剪模式

C++

void foo()
{
    bool rotate = nim::VChat::GetVideoFrameScaleType();
}

C#

void foo()
{
    bool rotate = NIM.VChatAPI.GetVideoFrameScale();
}

C

typedef int(*nim_vchat_get_video_frame_scale_type)();

void foo()
{
    nim_vchat_get_video_frame_scale_type func = (nim_vchat_get_video_frame_scale_type) GetProcAddress(hInst, "nim_vchat_get_video_frame_scale_type");
    int type = func();
}

成员音视频控制

设置单个成员的黑名单状态,即是否显示对方的音频或视频数据,当前通话有效(只能设置进入过房间的成员)

void nim_vchat_set_member_in_blacklist(const char *uid, bool add, bool audio, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data)
    param[in] uid 成员account
    param[in] add true表示添加到黑名单,false表示从黑名单移除
    param[in] audio true表示音频黑名单,false表示视频黑名单
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void OnOptCallback(bool ret, int code, const char *json_extension)
{
    ···
}

void foo()
{
    nim::VChat::SetMemberBlacklist("uid", true, false, "", &OnOptCallback);
}

C#

nim_vchat_opt_cb_func _vediosetblacklistop = null;

_vediosetblacklistop = new nim_vchat_opt_cb_func(
(ret, code, json_extension, user_data) =>
  {
      if (ret)
      {
          ···
      }
  }

);

VChatAPI.SetMemberInBlackList(id, !muted, false, "",
                              _vediosetblacklistop,
                              IntPtr.Zero);

C

void OnOptCallback(bool ret, int code, const char *json_extension, const void *user_data)
{
    if (user_data)
    {
        ···
    }
}

typedef void(*nim_vchat_set_member_in_blacklist)(const char *uid, bool add, bool audio, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_member_in_blacklist func = (nim_vchat_set_member_in_blacklist) GetProcAddress(hInst, "nim_vchat_set_member_in_blacklist");
    func("uid", true, true, "", &OnOptCallback, nullptr);
}

开始录制MP4

开始录制MP4,同一个成员一次只允许一个MP4录制文件,在通话开始的时候才有实际数据。状态变化见nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeMp4Notify。接口中json扩展kNIMVChatUid录制的成员,如果是自己填空;kNIMVChatMp4AudioType填0标识只录制当前成员,填1标识录制通话全部混音(等同音频文件录制的声音)

C++

void StartRecordCb(bool ret, int code, const std::string& file, __int64 time)
{
    if (ret)
    {

    }
    else
    {

    }
}

void foo()
{
    nim::VChat::StartRecord(path, uid, &StartRecordCb);
}

C#

void VChatRecordStartCallback(bool ret, int code,string file,Int64 time,string json_extension,IntPtr user_data)
{
    if(ret)
    {
        MessageBox.Show("开始录制");
    }
    else
    {
        MessageBox.Show("录制失败-错误码:" + code.ToString());
    }
}

void foo()
{
    NIMVChatMP4RecordJsonEx recordInfo;
    NIM.VChatAPI.StartRecord(path, recordInfo, VChatRecordStartCallback);
}

C

void mp4_record_opt_cb(bool ret, int code, const char *file, __int64 time, const char *json_extension, const void *user_data)
{
}

typedef void(*nim_vchat_start_record)(const char *path, const char *json_extension, nim_vchat_mp4_record_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_start_record func = (nim_vchat_start_record) GetProcAddress(hInst, "nim_vchat_start_record");
    func(path.c_str(), "", &mp4_record_opt_cb, nullptr);
}

停止录制MP4

停止录制MP4

C++

void StopRecordCb(bool ret, int code, const std::string& file, __int64 time)
{

}

void foo()
{
    nim::VChat::StopRecord(uid, &StopRecordCb);
}

C#

void VChatRecordStartCallback(bool ret, int code,string file,Int64 time,string json_extension,IntPtr user_data)
{

}

void foo()
{
    NIMVChatMP4RecordJsonEx recordInfo;
    NIM.VChatAPI.StopRecord(recordInfo, VChatRecordStartCallback);
}

C

void mp4_record_opt_cb(bool ret, int code, const char *file, __int64 time, const char *json_extension, const void *user_data)
{
}

typedef void(*nim_vchat_stop_record)(const char *json_extension, nim_vchat_mp4_record_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_stop_record func = (nim_vchat_stop_record) GetProcAddress(hInst, "nim_vchat_stop_record");
    func("", &mp4_record_opt_cb, nullptr);
}

开始录制混音文件

开始录制混音文件,一次只允许一个录制文件,在通话开始的时候才有实际数据。状态变化见nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeAuRecordNotify。

C++

void StartRecordCb(bool ret, int code, const std::string& file, __int64 time)
{
    if (ret)
    {

    }
    else
    {

    }
}

void foo()
{
    nim::VChat::StartAudioRecord(path, &StartRecordCb);
}

C#

void VChatRecordStartCallback(bool ret, int code,string file,Int64 time,string json_extension,IntPtr user_data)
{
    if(ret)
    {
        MessageBox.Show("开始录制");
    }
    else
    {
        MessageBox.Show("录制失败-错误码:" + code.ToString());
    }
}

void foo()
{
    NIM.VChatAPI.StartAudioRecord(path, "", VChatRecordStartCallback, IntPtr.Zero);
}

C

void audio_record_opt_cb(bool ret, int code, const char *file, __int64 time, const char *json_extension, const void *user_data)
{
}

typedef void(*nim_vchat_start_audio_record)(const char *path, const char *json_extension, nim_vchat_audio_record_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_start_record func = (nim_vchat_start_audio_record) GetProcAddress(hInst, "nim_vchat_start_audio_record");
    func(path.c_str(), "", &audio_record_opt_cb, nullptr);
}

停止录制混音文件

停止录制混音文件

C++

void StopRecordCb(bool ret, int code, const std::string& file, __int64 time)
{

}

void foo()
{
    nim::VChat::StopAudioRecord(&StopRecordCb);
}

C#

void VChatRecordStartCallback(bool ret, int code,string file,Int64 time,string json_extension,IntPtr user_data)
{

}

void foo()
{
    NIM.VChatAPI.StopAudioRecord("", VChatRecordStartCallback, IntPtr.Zero);
}

C

void audio_record_opt_cb(bool ret, int code, const char *file, __int64 time, const char *json_extension, const void *user_data)
{
}

typedef void(*nim_vchat_stop_audio_record)(const char *json_extension, nim_vchat_audio_record_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_stop_record func = (nim_vchat_stop_audio_record) GetProcAddress(hInst, "nim_vchat_stop_audio_record");
    func("", &audio_record_opt_cb, nullptr);
}

结束通话

需要在通话结束后调用,用于底层挂断和清理数据,如果是自己主动结束会通知对方

C++

void foo()
{
    nim::VChat::End("");
}

C#

void foo()
{
    NIM.VChatAPI.End();
}

C

typedef void(*nim_vchat_end)(const char* json_extension);

void foo()
{
    nim_vchat_end func = (nim_vchat_end) GetProcAddress(hInst, "nim_vchat_end");
    func("");
}

收到对方结束通知

收到对方结束由nim_vchat_cb_func通知,type值对应kNIMVideoChatSessionTypeHangupNotify,其中code无效,json无效

C++

void VChatCb(nim::NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json, const void*)
{

}

void foo()
{
    nim::VChat::SetCbFunc(&VChatCb);
}

C#

private NIM.NIMVChatSessionStatus _vchatHandlers;

void foo()
{
    _vchatHandlers.onSessionConnectNotify = (channel_id, code, record_addr, record_file) =>
    {
        if (code == 200)
        {

        }
        else
        {

        }
    };


    NIM.VChatAPI.SetSessionStatusCb(_vchatHandlers);
}

C

void VChatCb(nim::NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json, const void*)
{

}

typedef void(*nim_vchat_set_cb_func)(nim_vchat_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_cb_func func = (nim_vchat_set_cb_func) GetProcAddress(hInst, "nim_vchat_set_cb_func");
    func(&VChatCb, nullptr);
}

多人音视频

创建一个多人房间(后续需要主动调用加入接口进入房间)

void nim_vchat_create_room(const char *room_name, const char *custom_info, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data)
    param[in] room_name 房间名
    param[in] custom_info 自定义的房间信息(加入房间的时候会返回)
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void CreateRoomCb(int code, __int64 channel_id, const std::string& json_extension)
{

}

void foo()
{
    nim::VChat::CreateRoom("room_name", "custom_info", "", CreateRoomCb);
}

C#

void CreateMultiVChatRoomCallback(bool ret, int code,string json_extension,IntPtr user_data)
{
    if (ret)
    {

    }
    else
    {
        Action action = () =>
        {
            MessageBox.Show("创建房间失败-错误码:" + code.ToString());
        };
        this.BeginInvoke(action);

    }
}

void foo()
{
    string custom_info = "custom_info";
    string json_extension = "";
    room_name = Guid.NewGuid().ToString("N");

    VChatAPI.CreateRoom(room_name, custom_info, json_extension, CreateMultiVChatRoomCallback, IntPtr.Zero);
}

C

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_vchat_create_room)(const char *room_name, const char *custom_info, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_create_room func = (nim_vchat_create_room) GetProcAddress(hInst, "nim_vchat_create_room");
    func("room_name", "custom_info", "", OnOpt2Callback, nullptr);
}

加入一个多人房间(进入房间后成员变化等,等同点对点nim_vchat_cb_func)

bool nim_vchat_join_room(NIMVideoChatMode mode, const char *room_name, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data)
    param[in] mode NIMVideoChatMode 音视频通话类型 见nim_vchat_def.h
    param[in] room_name 房间名
    param[in] json_extension 可选 如{"custom_video":0, "custom_audio":0, "video_quality":0, "session_id":"1231sda"}
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension扩展字段中包含 kNIMVChatCustomInfo,kNIMVChatSessionId
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return bool true 调用成功,false 调用失败可能有正在进行的通话

C++

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

void foo()
{
    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = session_id;
    std::string json_value = fs.write(value);
    nim::VChat::JoinRoom(nim::kNIMVideoChatModeAudio, "room_name", json_value, OnOpt2Callback);
}

C#

void JoinMultiVChatRoomCallback(int code, Int64 channel_id, string json_extension,IntPtr user_data)
{
    if (code==200)
    {
    }
    else
    {
        Action action = () =>
        {
            MessageBox.Show("加入房间失败-错误码:" + code.ToString());
        };
        this.BeginInvoke(action);

    }
}

void foo()
{
    json_extension = "{\"session_id\":\"leewp\"}";
    VChatAPI.JoinRoom(NIMVideoChatMode.kNIMVideoChatModeVideo, "room_name", json_extension, JoinMultiVChatRoomCallback, IntPtr.Zero)
}

C

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

typedef bool(*nim_vchat_join_room)(NIMVideoChatMode mode, const char *room_name, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_join_room func = (nim_vchat_join_room) GetProcAddress(hInst, "nim_vchat_join_room");

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = session_id;
    std::string json_value = fs.write(value);
    func(nim::kNIMVideoChatModeAudio, "room_name", json_value, cb);
}

修改分辨率

通话中修改发送画面分辨率,在底层发送的时候发现数据源大于此设置则会缩小后发送,如果数据源小则不作处理。分辨率等级和宽高比例无关,和面积有关。点对点只允许在对方解码能力内调节

void nim_vchat_set_video_quality(int video_quality, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data)
    param[in] video_quality 分辨率模式,见NIMVChatVideoQuality定义
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

当前类型列表如下

kNIMVChatVideoQualityNormal        使用kNIMVChatVideoQualityHigh
kNIMVChatVideoQualityLow        使用176x144分辨率等级
kNIMVChatVideoQualityMedium        使用352x288分辨率等级
kNIMVChatVideoQualityHigh        使用480x320分辨率等级
kNIMVChatVideoQualitySuper        使用640x480分辨率等级
kNIMVChatVideoQuality540p        使用960*540分辨率等级
kNIMVChatVideoQuality720p        使用1280x720分辨率等级

C++

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

void foo()
{
    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = session_id;
    std::string json_value = fs.write(value);
    nim::VChat::JoinRoom(nim::kNIMVideoChatModeAudio, "room_name", json_value, OnOpt2Callback);
}

C#

void JoinMultiVChatRoomCallback(int code, Int64 channel_id, string json_extension,IntPtr user_data)
{
    if (code==200)
    {
    }
    else
    {
        Action action = () =>
        {
            MessageBox.Show("加入房间失败-错误码:" + code.ToString());
        };
        this.BeginInvoke(action);

    }
}

void foo()
{
    json_extension = "{\"session_id\":\"leewp\"}";
    VChatAPI.JoinRoom(NIMVideoChatMode.kNIMVideoChatModeVideo, "room_name", json_extension, JoinMultiVChatRoomCallback, IntPtr.Zero)
}

C

void OnOpt2Callback(int code, __int64 cannel_id, const char *json_extension, const void *user_data)
{

}

typedef bool(*nim_vchat_join_room)(NIMVideoChatMode mode, const char *room_name, const char *json_extension, nim_vchat_opt2_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_join_room func = (nim_vchat_join_room) GetProcAddress(hInst, "nim_vchat_join_room");

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVChatSessionId] = session_id;
    std::string json_value = fs.write(value);
    func(nim::kNIMVideoChatModeAudio, "room_name", json_value, cb);
}

设置视频帧率

实时设置视频发送帧率上限

C++

void foo()
{
    nim::VChat::SetFrameRate(nim::kNIMVChatVideoFrameRateNormal);
}

C#

void foo()
{
    NIM.VChatAPI.SetFrameRate(NIMVChatVideoFrameRate.kNIMVChatVideoFrameRateNormal, "", null, IntPtr.Zero);
}

C

typedef void(*nim_vchat_set_frame_rate)(NIMVChatVideoFrameRate frame_rate, const char* json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_frame_rate func = (nim_vchat_set_frame_rate) GetProcAddress(hInst, "nim_vchat_set_frame_rate");

    func(nim::kNIMVChatVideoFrameRateNormal, "", nullptr, nullptr);
}

修改视频码率

通话中修改视频码率,有效区间[100kb,2000kb],如果设置video_bitrate为0则取默认码率

void nim_vchat_set_video_bitrate(int video_bitrate, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data)
    param[in] video_bitrate 视频码率值
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetVideoBitrate(1000);
}

C#

暂无

C

typedef void(*nim_vchat_set_video_bitrate)(int video_bitrate, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_video_bitrate func = (nim_vchat_set_video_bitrate) GetProcAddress(hInst, "nim_vchat_set_video_bitrate");

    func(1000, "", nullptr, nullptr);
}

修改数据模式

通话中修改自定义音视频数据模式。如果设置了自定义数据模式,对应的数据底层将不直接使用采集数据直接发送,而需要用户主动调用自定义数据接口来发送数据。用户可灵活的使用此功能,在数据采集处理后,通过自定义数据接口发送,实现变声、美颜等功能,或直接使用自定义数据接口实现屏幕共享、音乐播放等功能。

void nim_vchat_set_custom_data(bool custom_audio, bool custom_video, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data)
    param[in] custom_audio true表示使用自定义的音频数据,false表示不使用
    param[in] custom_video true表示使用自定义的视频数据,false表示不使用
    param[in] json_extension 无效扩展字段
    param[in] cb 结果回调见nim_vchat_def.h,返回的json_extension无效
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetCustomData(false, true);
}

C#

private nim_vchat_opt_cb_func _set_custom_videocb = null;

void foo()
{
       if (_set_custom_videocb == null)
    {
        _set_custom_videocb = new nim_vchat_opt_cb_func((ret, code, json, intptr) =>
        {
            if (ret)
            {

            }
        });
    }
    NIM.VChatAPI.SetCustomData(false, true, "", _set_custom_videocb, IntPtr.Zero);
}

C

typedef void(*nim_vchat_set_custom_data)(bool custom_audio, bool custom_video, const char *json_extension, nim_vchat_opt_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_custom_data func = (nim_vchat_set_custom_data) GetProcAddress(hInst, "nim_vchat_set_custom_data");

    func(false, true, "", nullptr, nullptr);
}

音视频设备

提供麦克风、扬声器(听筒)、摄像头等设备的遍历、启动关闭、监听的接口,使用相关功能前需要调用OleInitialize来初始化COM库。如果用户需要采集播放器音频,需要使用nim_audio_hook.dll,使用nim_vchat_start_device接口打开设备类型kNIMDeviceTypeAudioHook,device_path对应播放器本地全路径。

遍历设备

在启动设备前或者设备有变更后需要遍历设备,其中kNIMDeviceTypeAudioOut和kNIMDeviceTypeAudioOutChat都为听筒设备,遍历的时候等同

void nim_vchat_enum_device_devpath(NIMDeviceType type, const char *json_extension, nim_vchat_enum_device_devpath_sync_cb_func cb, const void *user_data) 
    param[in] type NIMDeviceType 见nim_device_def.h
    param[in] json_extension 无效的扩展字段
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data 无效的扩展字段
    return void 无返回值,设备信息由同步返回的cb通知

C++

void EnumDevCb(bool ret, nim::NIMDeviceType type, const char* json, const void*)
{
    if (ret)
    {
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus) && valus.isArray())
        {
            int num = valus.size();
            for (int i=0;i<num;++i)
            {
                Json::Value device;
                device = valus.get(i, device);
                MEDIA_DEVICE_DRIVE_INFO info;
                info.device_path_ = device[nim::kNIMDevicePath].asString();
                info.friendly_name_ = device[nim::kNIMDeviceName].asString();
            }
        }
    }
}

void foo()
{
    nim::VChat::EnumDeviceDevpath(nim::kNIMDeviceTypeAudioIn, &EnumDevCb);
}

C#

void foo()
{
    NIMDeviceInfoList cameraDeviceinfolist = NIM.DeviceAPI.GetDeviceList(NIM.NIMDeviceType.kNIMDeviceTypeVideo);
}

C

void EnumDevCb(bool ret, nim::NIMDeviceType type, const char* json, const void*)
{
    if (ret)
    {
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus) && valus.isArray())
        {
            int num = valus.size();
            for (int i=0;i<num;++i)
            {
                Json::Value device;
                device = valus.get(i, device);
                MEDIA_DEVICE_DRIVE_INFO info;
                info.device_path_ = device[nim::kNIMDevicePath].asString();
                info.friendly_name_ = device[nim::kNIMDeviceName].asString();
            }
        }
    }
}

typedef void(*nim_vchat_enum_device_devpath)(nim::NIMDeviceType type, const char *json_extension, nim_vchat_enum_device_devpath_sync_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_enum_device_devpath func = (nim_vchat_enum_device_devpath) GetProcAddress(hInst, "nim_vchat_enum_device_devpath");

    func(nim::kNIMDeviceTypeAudioIn, "", &EnumDevCb, nullptr);
}

启动设备

可以启动麦克风、摄像头、本地采集音频播放器、对方语音播放器等,设备路径填空的时候会启动系统首选项

启动设备,同一NIMDeviceType下设备将不重复启动,不同的设备会先关闭前一个设备开启新设备
void nim_vchat_start_device(NIMDeviceType type, const char* device_path, unsigned fps, const char *json_extension, nim_vchat_start_device_cb_func cb, const void *user_data)
    param[in] type NIMDeviceType 见nim_device_def.h
    param[in] device_path 设备路径对应kNIMDevicePath,如果是kNIMDeviceTypeAudioHook,对应播放器本地全路径
    param[in] fps 摄像头为采样频率(一般取30),其他NIMDeviceType无效(麦克风采样频率由底层控制,播放器采样频率也由底层控制)
    param[in] json_extension 无效的扩展字段
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void StartDeviceCb(nim::NIMDeviceType type, bool ret, const char *json, const void*)
{

}

void foo()
{
    nim::VChat::StartDevice(nim::kNIMDeviceTypeVideo, device_path, 50, width, height, &StartDeviceCb);
}

C#

void foo()
{
   NIM.DeviceAPI.StartDeviceResultHandler handle = (type, ret) =>
    {
        Action action = () =>
        {
            if(ret)
            {

            }
            else
            {
                MessageBox.Show("启动摄像头设备失败");
            }

        };
        this.Invoke(action);
    };
    NIM.DeviceAPI.StartDevice(NIM.NIMDeviceType.kNIMDeviceTypeVideo, camera_device_path, 50, handle);//开启摄像头
}

C

void StartDeviceCb(nim::NIMDeviceType type, bool ret, const char *json, const void*)
{

}

typedef void(*nim_vchat_start_device)(nim::NIMDeviceType type, const char* device_path, unsigned fps, const char* json_extension, nim_vchat_start_device_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_start_device func = (nim_vchat_start_device) GetProcAddress(hInst, "nim_vchat_start_device");

    std::string json_value;
    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMDeviceWidth] = width;
    value[nim::kNIMDeviceHeight] = height;
    json_value = fs.write(value);

    fun(nim::kNIMDeviceTypeVideo, device_path.c_str(), 50, json_value.c_str(), &StartDeviceCb, nullptr);
}

结束设备

void nim_vchat_end_device(NIMDeviceType type, const char *json_extension)
    param[in] type NIMDeviceType 见nim_device_def.h
    param[in] json_extension 无效的扩展字段
    return void 无返回值

C++

void foo()
{
    nim::VChat::EndDevice(nim::kNIMDeviceTypeVideo);
}

C#

void foo()
{
    NIM.DeviceAPI.EndDevice(NIM.NIMDeviceType.kNIMDeviceTypeAudioOut);
}

C

typedef void(*nim_vchat_end_device)(nim::NIMDeviceType type, const char *json_extension);

void foo()
{
    nim_vchat_end_device func = (nim_vchat_end_device) GetProcAddress(hInst, "nim_vchat_end_device");

    fun(nim::kNIMDeviceTypeVideo);
}

监听设备状态

通过设置设备监听,可以定时获知设备状态并启动非主动结束的设备,这里只对麦克风和摄像头、及伴音hook有效

void nim_vchat_add_device_status_cb(NIMDeviceType type, nim_vchat_device_status_cb_func cb)
    param[in] type NIMDeviceType(kNIMDeviceTypeAudioIn和kNIMDeviceTypeVideo有效) 见nim_device_def.h
    param[in] cb 结果回调见nim_device_def.h
    return void 无返回值

C++

void DeviceStatusCb(nim::NIMDeviceType type, UINT status, const char* path, const char *json, const void *)
{

}

void foo()
{
    nim::VChat::AddDeviceStatusCb(nim::kNIMDeviceTypeVideo, &DeviceStatusCb);
}

C#

void foo()
{
   NIM.DeviceAPI.AddDeviceStatusCb(NIM.NIMDeviceType.kNIMDeviceTypeVideo, (type, status, devicePath) =>
    {
    });
}

C

void DeviceStatusCb(nim::NIMDeviceType type, UINT status, const char* path, const char *json, const void *)
{

}

typedef void(*nim_vchat_add_device_status_cb)(nim::NIMDeviceType type, nim_vchat_device_status_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_add_device_status_cb func = (nim_vchat_add_device_status_cb) GetProcAddress(hInst, "nim_vchat_add_device_status_cb");

    fun(nim::kNIMDeviceTypeVideo, &DeviceStatusCb);
}

由于开启设备监听后会定时检查设备状态,在不需要时结束监听

void nim_vchat_remove_device_status_cb(NIMDeviceType type)
    param[in] type DeviceType(kNIMDeviceTypeAudioIn和kNIMDeviceTypeVideo有效) 见nim_device_def.h
    return void 无返回值

C++

void foo()
{
    nim::VChat::RemoveDeviceStatusCb(nim::kNIMDeviceTypeVideo);
}

C#

void foo()
{
    NIM.DeviceAPI.RemoveDeviceStatusCb(NIM.NIMDeviceType.kNIMDeviceTypeVideo);
}

C

typedef void(*nim_vchat_remove_device_status_cb)(nim::NIMDeviceType type);

void foo()
{
    nim_vchat_remove_device_status_cb func = (nim_vchat_remove_device_status_cb) GetProcAddress(hInst, "nim_vchat_remove_device_status_cb");

    fun(nim::kNIMDeviceTypeVideo);
}

辅助的摄像头

启动辅助的摄像头,摄像头数据通过nim_vchat_set_video_data_cb设置采集回调返回(json返回kNIMDeviceId),不直接通过视频通话发送给对方,并且不参与设备监听检测

void nim_vchat_start_extend_camera(const char *id, const char *device_path, unsigned fps, const char *json_extension, nim_vchat_start_device_cb_func cb, const void *user_data)
    param[in] id 摄像头标识,用于开关及数据回调时的对应,不能为空。(同一id下设备将不重复启动,如果设备device_path不同会先关闭前一个设备开启新设备)
    param[in] device_path 设备路径对应kNIMDevicePath
    param[in] fps 摄像头为采样频率
    param[in] json_extension 打开摄像头是允许设置 kNIMDeviceWidth 和 kNIMDeviceHeight,并取最接近设置值的画面模式
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void VChatCallback::StartDeviceCb(nim::NIMDeviceType type, bool ret, const char *json, const void*)
{
    ...
}
void foo()
{
    nim::VChat::StartDevice(kNIMDeviceTypeVideo, device_path, 50, width, height, &VChatCallback::StartDeviceCb);
}

C#

/// <summary>
/// 启动辅助的摄像头,摄像头数据通过SetVideoCaptureDataCb设置采集回调返回,不直接通过视频通话发送给对方,并且不参与设备监听检测
/// </summary>
/// <param name="id">摄像头标识,用于开关及数据回调时的对应,不能为空。(同一id下设备将不重复启动,如果设备device_path不同会先关闭前一个设备开启新设备)</param>
/// <param name="device_path">设备路径</param>
/// <param name="fps">摄像头为采样频率</param>
/// <param name="json_extension">打开摄像头是允许设置 kNIMDeviceWidth 和 kNIMDeviceHeight,并取最接近设置值的画面模式</param>
/// <param name="handler">回调</param>
NIM.DeviceAPI.StartExtendCamera(string id, string device_path, uint fps, string json_extension, StartDeviceResultHandler handler);

C

void StartDeviceCb(nim::NIMDeviceType type, bool ret, const char *json, const void*)
{

}

typedef void(*nim_vchat_start_extend_camera)(const char *id, const char *device_path, unsigned fps, const char *json_extension, nim_vchat_start_device_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_start_extend_camera func = (nim_vchat_start_extend_camera) GetProcAddress(hInst, "nim_vchat_start_extend_camera");

    std::string json_value;
    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMDeviceWidth] = width;
    value[nim::kNIMDeviceHeight] = height;
    json_value = fs.write(value);

    fun("video_extend", device_path.c_str(), 50, json_value.c_str(), cb, nullptr);
}

结束辅助摄像头

void nim_vchat_stop_extend_camera(const char *id, const char *json_extension)
    param[in] id 摄像头标识id,如果为空,则关闭所有辅助摄像头
    param[in] json_extension 无效的扩展字段
    return void 无返回值

C++

nim::VChat::EndDevice(kNIMDeviceTypeVideo);

C#

/// <summary>
/// 结束辅助摄像头
/// </summary>
/// <param name="id">摄像头标识id,如果为空,则关闭所有辅助摄像头</param>
NIM.DeviceAPI.StartExtendCamera(string id);

C

typedef void(*nim_vchat_stop_extend_camera)(const char *id, const char *json_extension);

void foo()
{
    nim_vchat_stop_extend_camera func = (nim_vchat_stop_extend_camera) GetProcAddress(hInst, "nim_vchat_stop_extend_camera");

    fun("video_extend", "");
}

监听音频数据

通过设置监听,可以获得音频的pcm数据包,通过启动设备kDeviceTypeAudioOut和kDeviceTypeAudioOutChat由底层播放,可以不监听

void nim_vchat_set_audio_data_cb(bool capture, const char *json_extension, nim_vchat_audio_data_cb_func cb, const void *user_data)
    param[in] capture true 标识监听麦克风采集数据,false 标识监听通话中对方音频数据
    param[in] json_extension 扩展Json string:kNIMDeviceSampleRate(要求返回的音频数据为指定的采样频,缺省为0使用默认采样频)
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void SetDataCb(unsigned __int64 time, const char *data, unsigned int size, const char *json_extension, const void *user_data)
{

}

void foo()
{
    nim::VChat::SetAudioDataCb(true, &SetDataCb);
}

C#

void AudioDataCaptureHandler(UInt64 time, IntPtr data, uint size, int rate)
{

}

void foo()
{
   NIM.DeviceAPI.SetAudioCaptureDataCb(AudioDataCaptureHandler);
}

C

void SetDataCb(unsigned __int64 time, const char *data, unsigned int size, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_vchat_set_audio_data_cb)(bool capture, const char* json_extension, nim_vchat_audio_data_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_audio_data_cb func = (nim_vchat_set_audio_data_cb) GetProcAddress(hInst, "nim_vchat_set_audio_data_cb");

    fun(true, "", &SetDataCb, nullptr);
}

监听视频数据

通过设置监听,可以获得视频的ARGB数据包

void nim_vchat_set_video_data_cb(bool capture, const char *json_extension, nim_vchat_video_data_cb_func cb, const void *user_data)
    param[in] capture true 标识监听采集数据,false 标识监听通话中对方视频数据
    param[in] json_extension Json string 返回kNIMVideoSubType(缺省为kNIMVideoSubTypeARGB)
    param[in] cb 结果回调见nim_device_def.h
    param[in] user_data APP的自定义用户数据,SDK只负责传回给回调函数cb,不做任何处理!
    return void 无返回值

C++

void SetDataCb(unsigned __int64 time, const char *data, unsigned int size, unsigned int width, unsigned int height, const char *json_extension, const void *user_data)
{

}

void foo()
{
    nim::VChat::SetVideoDataCb(true, &SetDataCb);
}

C#

void VideoDataCaptureHandler(UInt64 time, IntPtr data, UInt32 size, UInt32 width, UInt32 height, string json_extension, IntPtr user_data)
{

}

void foo()
{
    JObject jo = new JObject();
    jo.Add(new JProperty("subtype", NIMVideoSubType.kNIMVideoSubTypeI420));
    string json_extention = jo.ToString();
    NIM.DeviceAPI.SetVideoCaptureDataCb(VideoDataCaptureHandler,json_extention);
}

C

void SetDataCb(unsigned __int64 time, const char *data, unsigned int size, unsigned int width, unsigned int height, const char *json_extension, const void *user_data)
{

}

typedef void(*nim_vchat_set_video_data_cb)(bool capture, const char* json_extension, nim_vchat_video_data_cb_func cb, const void *user_data);

void foo()
{
    nim_vchat_set_video_data_cb func = (nim_vchat_set_video_data_cb) GetProcAddress(hInst, "nim_vchat_set_video_data_cb");

    Json::FastWriter fs;
    Json::Value value;
    value[nim::kNIMVideoSubType] = kNIMVideoSubTypeI420;
    std::string json_value = fs.write(value);
    fun(true, json_value.c_str(), &SetDataCb, nullptr);
}

音频设置

设置音频音量,默认255,且音量均由软件换算得出,设置麦克风音量自动调节后麦克风音量参数无效

void nim_vchat_set_audio_volumn(unsigned char volumn, bool capture)
    param[in] volumn 结果回调见nim_device_def.h
    param[in] capture true 标识设置麦克风音量,false 标识设置播放音量
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetAudioVolumn(200, true);
}

C#

void foo()
{
    NIM.DeviceAPI.AudioCaptureVolumn = 200;
}

C

typedef void(*nim_vchat_set_audio_volumn)(unsigned char volumn, bool capture);

void foo()
{
    nim_vchat_set_audio_volumn func = (nim_vchat_set_audio_volumn) GetProcAddress(hInst, "nim_vchat_set_audio_volumn");

    func(200, true);
}

获取音频音量

unsigned char nim_vchat_get_audio_volumn(bool capture)
    param[in] capture true 标识获取麦克风音量,false 标识获取播放音量
    return unsigned char 音量值

C++

void foo()
{
    nim::VChat::GetAudioVolumn(true);
}

C#

void foo()
{
    byte value = NIM.DeviceAPI.AudioCaptureVolumn;
}

C

typedef unsigned char(*nim_vchat_get_audio_volumn)(bool capture);

void foo()
{
    nim_vchat_get_audio_volumn func = (nim_vchat_get_audio_volumn) GetProcAddress(hInst, "nim_vchat_get_audio_volumn");

    func(true);
}

设置麦克风音量自动调节

void nim_vchat_set_audio_input_auto_volumn(bool auto_volumn)
    param[in] auto_volumn true 标识麦克风音量自动调节,false 标识麦克风音量不调节,这时nim_vchat_set_audio_volumn中麦克风音量参数起效
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetAudioInputAutoVolumn(true);
}

C#

void foo()
{
    NIM.DeviceAPI.AudioCaptureAutoVolumn = true;
}

C

typedef void(*nim_vchat_set_audio_input_auto_volumn)(bool auto_volumn);

void foo()
{
    nim_vchat_set_audio_input_auto_volumn func = (nim_vchat_set_audio_input_auto_volumn) GetProcAddress(hInst, "nim_vchat_set_audio_input_auto_volumn");

    func(true);
}

获取是否自动调节麦克风音量

bool nim_vchat_get_audio_input_auto_volumn()
    return bool true 标识麦克风音量自动调节,false 标识麦克风音量不调节

C++

void foo()
{
    bool value = nim::VChat::GetAudioInputAutoVolumn();
}

C#

void foo()
{
    bool value = NIM.DeviceAPI.AudioCaptureAutoVolumn;
}

C

typedef bool(*nim_vchat_get_audio_input_auto_volumn)();

void foo()
{
    nim_vchat_get_audio_input_auto_volumn func = (nim_vchat_get_audio_input_auto_volumn) GetProcAddress(hInst, "nim_vchat_get_audio_input_auto_volumn");

    bool is_auto = func();
}

自定义数据接口

自定义音频数据接口, 采样位深只支持16或32, kNIMDeviceSampleRate支持8000,16000,32000,44100

bool nim_vchat_custom_audio_data(unsigned __int64 time, const char *data, unsigned int size, const char *json_extension);
    param[in] time 时间毫秒级
    param[in] data 音频数据pcm格式
    param[in] size data的数据长度
    param[in] json_extension 扩展Json string kNIMDeviceSampleRate采样频和kNIMDeviceSampleBit采样位深 默认如{"sample_rate":16000, "sample_bit":16}
    return bool true 调用成功,false 调用失败

C++

(参见IM demo)
...

C#

/// <summary>
/// 自定义音频数据接口, 采样位深只支持16或32, kNIMDeviceSampleRate支持8000,16000,32000,44100
/// </summary>
/// <param name="time">时间毫秒级</param>
/// <param name="data">音频数据pcm格式</param>
/// <param name="size">data的数据长度 sizeof(char)</param>
/// <param name="info">采样频和采样位深 默认如{"sample_rate":16000, "sample_bit":16}</param>
/// <returns></returns>
NIM.DeviceAPI.CustomAudioData(ulong time, IntPtr data, uint size, NIMCustomAudioDataInfo info)

C

暂无

自定义视频数据接口

bool nim_vchat_custom_video_data(unsigned __int64 time, const char *data, unsigned int size, unsigned int width, unsigned int height, const char *json_extension);
    param[in] time 时间毫秒级
    param[in] data 视频数据, 默认为yuv420格式
    param[in] size data的数据长度
    param[in] width  画面宽度,必须是偶数
    param[in] height  画面高度,必须是偶数
    param[in] json_extension  扩展Json string,kNIMVideoSubType视频数据格式(缺省为kNIMVideoSubTypeI420)
    return bool true 调用成功,false 调用失败

C++

//参见IM demo
void foo()
{
    static int64_t timestamp = 0;
    std::string data;
    data.resize(1280 * 720 * 4);
    int32_t w, h;
    w = 0;
    h = 0;
    bool ret = nim_comp::VideoManager::GetInstance()->video_frame_mng_.GetVideoFrame("", timestamp, (char*)data.c_str(), w, h, false, false);
    if (ret)
    {
        int32_t width = w;
        int32_t height = h;
        int32_t wxh = width*height;
        int32_t data_size = wxh * 3 / 2;

        //均方差滤波
        std::string beauty_src_data;
        beauty_src_data.resize(wxh * 4);
        nim_comp::YUV420ToARGB((char*)data.c_str(), (char*)beauty_src_data.c_str(), width, height);

        //采用色彩平衡算法进行美白
        nim_comp::colorbalance_rgb_u8((unsigned char*)beauty_src_data.c_str(), wxh, (size_t)(wxh * 2 / 100), (size_t)(wxh * 8 / 100), 4);

        nim_comp::ARGBToYUV420((char*)beauty_src_data.c_str(), (char*)data.c_str(), width, height);

        //采用均方差滤波进行磨皮
        nim_comp::smooth_process((uint8_t*)data.c_str(), width, height, 10, 0, 200);

        //保存用于预览
        std::string json;
        video_frame_mng_.AddVideoFrame(true, 0, data.c_str(), data_size, w, h, json, nim_comp::VideoFrameMng::Ft_I420);
        //发送
        nim::VChat::CustomVideoData(0, data.c_str(), data_size, w, h, nullptr);
    }
}

C#

//参见IM demo
void foo(object sender, VideoEventAgrs e)
{

    uint size=Convert.ToUInt32(e.Frame.Width*e.Frame.Height*3/2);
    //处理数据
    byte[] i420=NIMDemo.LivingStreamSDK.YUVHelper.ARGBToI420(e.Frame.Data,e.Frame.Width,e.Frame.Height);
    Beauty.Smooth.smooth_process(i420,e.Frame.Width,e.Frame.Height,10,0,200);
    e.Frame.Data = NIMDemo.LivingStreamSDK.YUVHelper.I420ToARGB(i420, e.Frame.Width, e.Frame.Height);

    //发送自定义数据
    TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
    ulong time = Convert.ToUInt64(ts.TotalMilliseconds);
    NIMDemo.LivingStreamSDK.YUVHelper.i420Revert(ref i420,e.Frame.Width,e.Frame.Height);
    IntPtr unmanagedPointer = Marshal.AllocHGlobal(i420.Length);
    Marshal.Copy(i420, 0, unmanagedPointer, i420.Length);
    NIM.DeviceAPI.CustomVideoData(time, unmanagedPointer, size, (uint)e.Frame.Width, (uint)e.Frame.Height);
    Marshal.FreeHGlobal(unmanagedPointer);
}

C

typedef bool(*nim_vchat_custom_video_data)(unsigned __int64 time, const char *data, unsigned int size, unsigned int width, unsigned int height, const char *json_extension);

void foo()
{
    nim_vchat_custom_video_data func = (nim_vchat_custom_video_data) GetProcAddress(hInst, "nim_vchat_custom_video_data");

    func(time, data, size, width, height, "");
}

音频采集数据处理开关

设置底层针对麦克风采集数据处理开关接口,默认全开(此接口是全局接口,在sdk初始化后设置一直有效)。aec 标识回音消除功能,ns标识降噪功能,vid标识人言检测功能。

void nim_vchat_set_audio_process_info(bool aec, bool ns, bool vid)
    param[in] aec true 标识打开回音消除功能,false 标识关闭
    param[in] ns true 标识打开降噪功能,false 标识关闭
    param[in] vid true 标识打开人言检测功能,false 标识关闭
    return void 无返回值

C++

void foo()
{
    nim::VChat::SetAudioProcess(false, false, false);
}

C#

NIM.DeviceAPI.SetAudioProcessInfo(bool aec,bool ns,bool vid)

C

typedef bool(*nim_vchat_set_audio_process_info)(bool aec, bool ns, bool vid);

void foo()
{
    nim_vchat_set_audio_process_info func = (nim_vchat_set_audio_process_info) GetProcAddress(hInst, "nim_vchat_set_audio_process_info");

    func(false, false, false);
}

音视频通话示例

对所有异步回调接口,在实现时post到ui线程处理

//本地设备遍历
struct MEDIA_DEVICE_DRIVE_INFO
{
    std::string device_path_;
    std::string friendly_name_;
};
std::list<MEDIA_DEVICE_DRIVE_INFO> list_audioin_;
std::list<MEDIA_DEVICE_DRIVE_INFO> list_audioout_;
std::list<MEDIA_DEVICE_DRIVE_INFO> list_video_;
void GetDeviceByJson(std::list<MEDIA_DEVICE_DRIVE_INFO>& list, const char* json)
{
    Json::Value valus;
    Json::Reader reader;
    if (reader.parse(json, valus) && valus.isArray())
    {
        int num = valus.size();
        for (int i=0;i<num;++i)
        {
            Json::Value device;
            device = valus.get(i, device);
            MEDIA_DEVICE_DRIVE_INFO info;
            info.device_path_ = device[kNIMDevicePath].asString();
            info.friendly_name_ = device[kNIMDeviceName].asString();
            list.push_back(info);
        }
    }
}
//遍历设备的回调为同步接口,不需要post
void EnumDevCb(bool ret, NIMDeviceType type, const char* json, const void*)
{
    Print("EnumDevCb: ret=%d, NIMDeviceType=%d\r\njson: %s", ret, type, json);
    if (ret)
    {
        switch (type)
        {
        case kNIMDeviceTypeAudioIn:
            GetDeviceByJson(list_audioin_, json);
            break;
        case kNIMDeviceTypeAudioOut:
            GetDeviceByJson(list_audioout_, json);
            break;
        case kNIMDeviceTypeVideo:
            GetDeviceByJson(list_video_, json);
            break;
        }
    }
}
void EnumDev()
{
    nim_vchat_enum_device_devpath(kNIMDeviceTypeVideo, "", EnumDevCb, nullptr);
    nim_vchat_enum_device_devpath(kNIMDeviceTypeAudioIn, "", EnumDevCb, nullptr);
    nim_vchat_enum_device_devpath(kNIMDeviceTypeAudioOut, "", EnumDevCb, nullptr);
}

//监听设备回调
void AudioStatus(NIMDeviceType type, UINT status, const char* path, const char*)
{
    //
}
void VideoStatus(NIMDeviceType type, UINT status, const char* path, const char*)
{
    //
}

//监听设备,建议在需要的时候开启,不需要的时候调用停止监听
void AddDeviceStatusCb()
{
    nim_vchat_add_device_status_cb(kNIMDeviceTypeVideo, VideoStatus);
    nim_vchat_add_device_status_cb(kNIMDeviceTypeAudioIn, AudioStatus);
}
//停止监听设备
void RemoveDeviceStatusCb()
{
    nim_vchat_remove_device_status_cb(kNIMDeviceTypeVideo);
    nim_vchat_remove_device_status_cb(kNIMDeviceTypeAudioIn);
}

//音视频通话
//通话回调函数
void OnVChatEvent(NIMVideoChatSessionType type, __int64 channel_id, int code, const char *json_extension, const void *user_data)
{
    std::string json = json_extension;
    switch (type)
    {
    case nim::kNIMVideoChatSessionTypeStartRes:{
        if (code == 200)
        {
            //发起通话成功
        }
        else
        {
            EndVChat();            
        }
    }break;
    case nim::kNIMVideoChatSessionTypeInviteNotify:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            std::string uid = valus[nim::kNIMVChatUid].asString();
            int mode = valus[nim::kNIMVChatType].asInt();
            bool exist = false;//是否已经有音视频通话存在
            if (exist)
            {
                //通知对方忙碌
                VChatControl(channel_id, nim::kNIMTagControlBusyLine);
                //拒绝接听,可由用户在ui触发
                VChatCalleeAck(channel_id, false);
            }
            else
            {
                //通知对方收到邀请
                VChatControl(channel_id, nim::kNIMTagControlReceiveStartNotifyFeedback);
                //接听,可由用户在ui触发
                VChatCalleeAck(channel_id, true);
            }
        }
    }break;
    case nim::kNIMVideoChatSessionTypeCalleeAckRes:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            //std::string uid = valus[nim::kNIMVChatUid].asString();
            bool accept = valus[nim::kNIMVChatAccept].asInt() > 0;
            if (accept && code != 200)
            {
                //接听失败,结束当前通话
                EndVChat();    
            }
        }
    }break;
    case nim::kNIMVideoChatSessionTypeCalleeAckNotify:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            //std::string uid = valus[nim::kNIMVChatUid].asString();
            bool accept = valus[nim::kNIMVChatAccept].asInt() > 0;
            if (!accept)
            {
                //对方拒绝,结束当前通话
                EndVChat();    
            }
        }
    }break;
    case nim::kNIMVideoChatSessionTypeControlRes:{
        //控制命令发送结果
    }break;
    case nim::kNIMVideoChatSessionTypeControlNotify:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            //std::string uid = valus[nim::kNIMVChatUid].asString();
            int type = valus[nim::kNIMVChatType].asInt();
            //收到一个控制通知
        }
    }break;
    case nim::kNIMVideoChatSessionTypeConnect:{
        if (code != 200)
        {
            //连接音视频服务器失败
        }
        else
        {
            //连接音视频服务器成功,启动麦克风、听筒、摄像头
            StartDevice();
        }
    }break;
    case nim::kNIMVideoChatSessionTypePeopleStatus:{
        if (code == nim::kNIMVideoChatSessionStatusJoined)
        {
            //对方进入
        }
        else if (code == nim::kNIMVideoChatSessionStatusLeaved)
        {
            //对方离开,结束
            EndVChat();    
        }
    }break;
    case nim::kNIMVideoChatSessionTypeNetStatus:{
        //网络状况变化
    }break;
    case nim::kNIMVideoChatSessionTypeHangupRes:{
        //挂断结果
    }break;
    case nim::kNIMVideoChatSessionTypeHangupNotify:{
        //收到对方挂断
    }break;
    case nim::kNIMVideoChatSessionTypeSyncAckNotify:{
        Json::Value valus;
        Json::Reader reader;
        if (reader.parse(json, valus))
        {
            bool accept = valus[nim::kNIMVChatAccept].asInt() > 0;
            //在其他端处理
        }
    }break;
    }
}
//一个简单的视频数据绘制
void DrawPic(bool left, unsigned int width, unsigned int height, const char* data)
{
    if (!IsWindow(m_hWnd))
    {
        return;
    }
    int pos = 10;
    const char* scr_data = data;
    int w = 0;
    int top = 30;
    if (!left)
    {
        RECT rect;
        GetWindowRect(m_hWnd, &rect);
        w = rect.right - rect.left;
        pos = w - width - 20;
    }
    // 构造位图信息头
    BITMAPINFOHEADER bih;
    memset(&bih, 0, sizeof(BITMAPINFOHEADER));
    bih.biSize = sizeof(BITMAPINFOHEADER);
    bih.biPlanes = 1;
    bih.biBitCount = 32;
    bih.biWidth = width;
    bih.biHeight = height;
    bih.biCompression = BI_RGB;
    bih.biSizeImage = width * height * 4;

    // 开始绘制
    HDC    hdc = ::GetDC(m_hWnd);
    PAINTSTRUCT ps;
    ::BeginPaint(m_hWnd, &ps);
    // 使用DIB位图和颜色数据对与目标设备环境相关的设备上的指定矩形中的像素进行设置
    SetDIBitsToDevice(
        hdc, pos, top,
        bih.biWidth, bih.biHeight,
        0, 0, 0, bih.biWidth,
        scr_data,
        (BITMAPINFO*)&bih,
        DIB_RGB_COLORS);
    ::EndPaint(m_hWnd, &ps);
    ::ReleaseDC(m_hWnd, hdc);
}
//本地视频数据监听
void VideoCaptureData(unsigned __int64 time, const char* data, unsigned int size, unsigned int width, unsigned int height, const char*)
{
    static int capture_video_num = 0;
    capture_video_num++;
    if (capture_video_num % 2 == 0)
    {
        return;
    }
    DrawPic(true, width, height, data);
}
//接收视频数据监听
void VideoRecData(unsigned __int64 time, const char* data, unsigned int size, unsigned int width, unsigned int height, const char*)
{
    DrawPic(false, width, height, data);
}
//初始化回调接口
void InitVChatCb()
{
    nim_vchat_set_cb_func(OnVChatEvent);
    nim_vchat_set_video_data_cb(true, "", VideoCaptureData, nullptr);
    nim_vchat_set_video_data_cb(false, "", VideoRecData, nullptr);
}
//启动设备
void StartDevice()
{
    nim_vchat_start_device(kNIMDeviceTypeAudioIn, "", 0, "", nullptr, nullptr);
    nim_vchat_start_device(kNIMDeviceTypeAudioOutChat, "", 0, "", nullptr, nullptr);
    nim_vchat_start_device(kNIMDeviceTypeVideo, "", 50, "", nullptr, nullptr);
}
//停用设备
void EndDevice()
{
    nim_vchat_end_device(kNIMDeviceTypeVideo, "");
    nim_vchat_end_device(kNIMDeviceTypeAudioIn, "");
    nim_vchat_end_device(kNIMDeviceTypeAudioOutChat, "");
}
//发起邀请
void StartVChat()
{
    Json::FastWriter fs;
    Json::Value value;
    value[kNIMVChatUids].append("uid");
    std::string json_value = fs.write(value);
    nim_vchat_start(kNIMVideoChatModeVideo, json_value.c_str(), nullptr);
}
//结束通话
void EndVChat()
{
    EndDevice();
    //当结束当前音视频会话时都可以调用nim_vchat_end,sdk底层会对通话进行判断清理
    nim_vchat_end("");
}