Linux ( C++ )

DingRTC的基本功能包含初始化SDK、加入频道、本地发布、订阅远端和离开频道等。通过阅读本文,您可以了解DingRTC的基本功能。

操作步骤

说明 本文中的实现方法仅供参考,您可以根据实际业务需求进行开发。
  1. 初始化SDK。

    您需要创建RtcEngine实例,并注册回调。

    // 设置日志路径
    RtcEngine::SetLogDirPath(logDirPath);
    
    // 创建引擎实例
    RtcEngine * engine = RtcEngine::Create(extras);
    
    // 按需实现RtcEngineEventListener,设置事件回调
    engine->SetEngineEventListener(listener);
  2. 加入频道。

    入会需获取鉴权Token,通常由业务方服务端生成,具体方法请参考“使用Token鉴权

    struct RtcEngineAuthInfo {
        /*! 频道ID。 */ 
        String channelId;
        /*! 用户ID。 */
        String userId;
        /*! 应用ID。 */
        String appId;
    	/*! 令牌。 */
        String token;
    	/*! GSLB地址。 */
        String gslbServer;
    };
    
    // 获取token
    RtcEngineAuthInfo authInfo = getYourAuthInfo();
    // 入会
    engine->JoinChannel(authInfo, userName);

    参数

    描述

    appId

    应用ID,在控制台应用管理页面创建和查看。

    channelId

    频道ID。1~64位,由大小写字母、数字、下划线(_)、短划线(-)组成。

    userId

    用户ID。1~64位,由大小写字母、数字、下划线(_)、短划线(-)组成。

    说明

    同一个用户ID在其他端登录,先入会的端会被后入会的端踢出频道。

    token

    频道鉴权令牌。

    gslbServer

    服务地址,可为空,默认值为:"https://gslb.dingrtc.com",请您通过业务服务器下发到客户端SDK,不建议您将该地址固化在客户端代码。

  3. 发布本地音频流。

    入会之后,SDK默认不推流,需主动调用API开启推流(可在入会前调用)。

    • 开启音频推流

      // 开启音频推流,可在JoinChannel前调用
      engine->PublishLocalAudioStream(true);
    • 外部音频推流

      • 如果是Linux服务器或没有麦克风声音采集设备,可使用外部音频源接口来推送音频数据。

      // 开启外部音频源,同时设置音频采样率、声道数等参数
      engine->SetExternalAudioSource(true, sampleRate, channels);
      • 通常需要一个单独的线程来循环推送音频数据,同时控制数据保持一定的时间间隔均匀输入给SDK。可类比为麦克风设备,按固定周期采集音频数据输入给SDK。

      static const int kBytePerSamplePcm16 = 2; // 16bit音频采样点字节数
      
      std::string pcm_file_path = "/path/to/my_audio.pcm"; // pcm文件路径
      int audio_sample_rate = 48000; // 采样率
      int audio_channels = 1; // 声道数
      int read_ms = 40; // 单次读取40ms音频数据
      bool push_audio_quit = false; // quit标记位
      
      // 音频推流线程,从PCM文件读取音频数据,保持一定时间间隔输入给SDK推流
      // 可以理解为模拟音频采集设备,按固定周期采集音频数据
      std::thread push_audio_thread = std::thread([=]{
          // 打开文件
          FILE *fp = fopen(pcm_file_path.c_str(), "rb");
          if (fp) {
              // 统计音频帧数,并用于计算音频时间戳
              int64_t sample_count = 0;
              // 音频时延
              int64_t delay_ms = 0;
              // 起始的系统时钟
              auto start_clock = std::chrono::high_resolution_clock::now();
              
              // 单次读取的音频采样点数
              int samples_to_read = audio_sample_rate / (1000 / read_ms);
              // 单次读取音频字节数
              int buffer_size = samples_to_read * audio_channels * kBytePerSamplePcm16;
              // 分配相应内存buffer以存储读取数据
              std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
      
              // 进入循环推流阶段
              while (true) {
                  // 直至quit标记为true,退出推流
                  if (push_audio_quit)
                      break;
      
                  size_t read_bytes = fread(buffer.get(), 1, buffer_size, fp);
                  // 读取到文件末尾,则从头开始读取
                  if (read_bytes == 0) {
                      fseek(fp, 0, SEEK_SET);
                      continue;
                  }
      
                  // 填充音频帧字段
                  RtcEngineAudioFrame frame;
                  frame.type = RtcEngineAudioFramePcm16;
                  frame.bytesPerSample = kBytePerSamplePcm16;
                  frame.samplesPerSec = audio_sample_rate;
                  frame.channels = audio_channels;
                  frame.buffer = buffer.get();
                  frame.samples = read_bytes / frame.bytesPerSample / frame.channels;
                  frame.timestamp = sample_count * 1000 / audio_sample_rate;
      
                  // 计算音频时延与系统时钟的差值,sleep来控制输入频率
                  delay_ms = frame.timestamp;
                  int64_t elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_clock).count();
                  if (delay_ms - elapsed_ms > 5) {
                      std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms - elapsed_ms));
                  }
      
                  engine->PushExternalAudioFrame(&frame);
                  sample_count += frame.samples;
              }
      
              // 关闭文件
              fclose(fp);
          }
      });
    • 停止音频推流

      // 关闭外部音频源
      engine->SetExternalAudioSource(false, sampleRate, channels);
      
      // 关闭音频推流
      engine->PublishLocalAudioStream(false);
  4. 发布本地视频流。

    入会之后,SDK默认不推流,需主动调用API开启推流(可在入会前调用)。

    • 开启视频推流

      // 开启视频推流,可在JoinChannel前调用
      engine->PublishLocalVideoStream(true);
    • 外部视频推流

      • 如果是Linux服务器或没有摄像头视频采集设备,可使用外部视频源接口来推送视频数据。

      // 开启外部视频源,来代替Camera流
      engine->SetExternalVideoSource(true, RtcEngineVideoTrackCamera);
      • 通常需要一个单独的线程来循环推送视频数据,同时控制数据保持一定的时间间隔均匀输入给SDK。可类比为摄像头设备,按固定周期采集音频数据输入给SDK。

      std::string yuv_file_path = "/path/to/my_yuv.yuv"; // yuv文件路径
      RtcEngineVideoPixelFormat video_pixel_format = RtcEngineVideoI420; // 视频像素格式
      int video_width = 1280; // 视频宽度
      int video_height = 720; // 视频高度
      int video_fps = 25; // 视频帧率
      bool push_video_quit = false; // quit标记位
      
      // 视频推流线程,从YUV文件读取音频数据,保持一定时间间隔输入给SDK推流
      // 可以理解为模拟音频采集设备,按固定周期采集音频数据
      std::thread push_video_thread = std::thread([=] {
          FILE *fp = fopen(yuv_file_path.c_str(), "rb");
          if (fp) {
              // 统计视频帧数,并用于计算视频时间戳
              int64_t frame_count = 0;
              // 视频时延
              int64_t delay_ms = 0;
              // 起始的系统时钟
              auto start_clock = std::chrono::high_resolution_clock::now();
      
              // 一帧视频的字节数
              int buffer_size = CalcBufferSize(video_pixel_format, video_width, video_height);
              // 分配相应内存buffer以存储读取数据
              std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
              
              // 进入循环推流阶段
              while (true) {
                  // 直至quit标记为true,退出推流
                  if (push_video_quit)
                      break;
                  
                  size_t read_bytes = fread(buffer.get(), 1, buffer_size, fp);
                  // 读取到文件末尾,则从头开始读取
                  if (read_bytes == 0) {
                      fseek(fp, 0, SEEK_SET);
                          continue;
                  }
      
                  // 配置外部视频流参数
                  RtcEngineVideoFrame frame;
                  memset(&frame, 0, sizeof(frame));
                  frame.frameType = RtcEngineVideoFrameRaw;
                  frame.pixelFormat = video_pixel_format;
                  frame.width = video_width;
                  frame.height = video_height;
                  frame.stride[0] = video_width;
                  frame.stride[1] = frame.stride[2] = video_width >> 1;
                  frame.rotation = config.rotation;
                  frame.data = buffer.get();
                  frame.timestamp = frame_count * 1000 / video_fps;
      
                  // 计算视频时延与系统时钟的差值,sleep来控制输入频率
                  delay_ms = frame.timestamp;
                  int64_t elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_clock).count();
                  if (delay_ms - elapsed_ms > 5) {
                      std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms - elapsed_ms));
                  }
                  
                  engine->PushExternalVideoFrame(&frame, RtcEngineVideoTrackCamera);
                  frame_count++;
              }
      
              fclose(fp);
          }
      });
      
      size_t CalcBufferSize(RtcEngineVideoPixelFormat pixel_format, int width, int height) {
          size_t buffer_size = 0;
          switch (pixel_format) {
              case RtcEngineVideoI420:
              case RtcEngineVideoNV12:
              case RtcEngineVideoNV21:
                  {
                      int half_width = (width + 1) >> 1;
                      int half_height = (height + 1) >> 1;
                      buffer_size = width * height + half_width * half_height * 2;
                      break;
                  }
              case RtcEngineVideoBGRA:
              case RtcEngineVideoARGB:
              case RtcEngineVideoRGBA:
              case RtcEngineVideoABGR:
                  buffer_size = width * height * 4;
                  break;
              default:
                  break;
          }
          return buffer_size;
      }
    • 停止视频推流

      // 关闭外部视频源
      engine->SetExternalVideoSource(false, RtcEngineVideoTrackCamera);
      
      // 关闭音频推流
      engine->PublishLocalVideoStream(false);

  5. 订阅远程音频流。

    SDK默认自动订阅远端音视频并拉流,如需关闭拉流请主动调用API关闭拉流(可在入会前调用)。

    • 订阅音频流

      SDK当前不支持按指定用户订阅音频流,仅支持订阅音频合流(即所有远端用户的声音合流)。

      // 订阅音频流,可在JoinChannel前调用
      engine->SubscribeAllRemoteAudioStreams(true);
      
      // 取消订阅音频流,可在JoinChannel前调用
      engine->SubscribeAllRemoteAudioStreams(false);
    • 监听音频帧数据

      // 实现RtcEngineAudioFrameObserver,接收音频数据回调
      class MyAudioFrameObserver : public ding::rtc::RtcEngineAudioFrameObserver {
      public:
          void OnPlaybackAudioFrame(ding::rtc::RtcEngineAudioFrame &frame) override {}
          void OnCapturedAudioFrame(ding::rtc::RtcEngineAudioFrame &frame) override {}
          void OnProcessCapturedAudioFrame(ding::rtc::RtcEngineAudioFrame &frame) override {}
          void OnPublishAudioFrame(ding::rtc::RtcEngineAudioFrame &frame) override {}
      };
      
      MyAudioFrameObserver myObserver;
      
      // 注册音频数据回调
      engine->RegisterAudioFrameObserver(&myObserver);
      // 开启音频回调,可指定接收哪种类型的音频数据回调
      engine->EnableAudioFrameObserver(true, RtcEngineAudioPositionPlayback);
      // 可指定同时接收多种类型数据回调
      engine->EnableAudioFrameObserver(true, RtcEngineAudioPositionCaptured | RtcEngineAudioPositionPub | RtcEngineAudioPositionPlayback);
      
      // 关闭音频回调
      engine->EnableAudioFrameObserver(false, RtcEngineAudioPositionPlayback);
      // 取消注册音频数据回调
      engine->RegisterAudioFrameObserver(nullptr);
    • 监听发言人音量

      /**
       * @ingroup CPP_DingRtcEngineAudio
       * @since 3.0
       * @brief 设置音量回调频率和平滑系数。
       * @param interval 时间间隔,单位毫秒,最小值不得小于100ms, 建议设置300-500ms, <= 0表示不启用音量提示和说话人提示功能。
       * @param smooth 平滑系数,数值越大平滑程度越高,反之越低,实时性越好,建议设置3,范围[0, 9]。
       * @param reportVad 说话人检测开关。
       * - 1:开启。
       * - 0:关闭。
       * @return
       * - 0:成功;
       * - <0:失败。
       */
      virtual int EnableAudioVolumeIndication(int interval, int smooth, int reportVad) = 0;
      
      class MyEventListener : public RtcEngineEventListener {
      public:
          void OnAudioVolumeIndication(const ding::rtc::AudioVolumeInfo* speakers, unsigned int speakerNumber) override {}
      };
      
      // 开启音量回调,OnAudioVolumeIndication回调说话人音量信息
      engine->EnableAudioVolumeIndication(300, 3, 1);
      
      // 关闭音量回调
      engine->EnableAudioVolumeIndication(0, 3, 1);

  6. 订阅远程视频流。

    • 订阅视频流

      // 订阅所有远端用户的视频流,可在JoinChannel前调用
      engine->SubscribeAllRemoteVideoStreams(true);
      // 取消订阅所有远端用户的视频流,可在JoinChannel前调用
      engine->SubscribeAllRemoteVideoStreams(false);
      
      // 订阅某个远端用户的视频流
      engine->SubscribeRemoteVideoStream(uid, track, true);
      // 取消订阅某个远端用户的视频流,可在JoinChannel前调用
      engine->SubscribeRemoteVideoStream(uid, track, false);
    • 监听视频帧数据

      // 实现RtcEngineVideoFrameObserver,接收音频数据回调
      class MyVideoFrameObserver : public ding::rtc::RtcEngineVideoFrameObserver {
      public:
          ding::rtc::RtcEngineVideoPixelFormat GetVideoFormatPreference() override;
          bool OnCaptureVideoFrame(ding::rtc::RtcEngineVideoFrame &frame) override;
          bool OnRemoteVideoFrame(ding::rtc::String uid, ding::rtc::RtcEngineVideoTrack track, ding::rtc::RtcEngineVideoFrame &frame) override;
          bool OnPreEncodeVideoFrame(ding::rtc::RtcEngineVideoTrack track, ding::rtc::RtcEngineVideoFrame &frame) override;
      };
      
      MyVideoFrameObserver myObserver;
      
      // 注册视频数据回调
      engine->RegisterVideoFrameObserver(&myObserver);
      // 开启视频回调,可指定接收哪种类型的视频数据回调
      engine->EnableVideoFrameObserver(true, RtcEnginePositionPreRender);
      // 可指定同时接收多种类型数据回调
      engine->EnableAudioFrameObserver(true, RtcEnginePositionPostCapture | RtcEnginePositionPreRender | RtcEngineAudioPositionPlayback);
      
      // 关闭视频回调
      engine->EnableVideoFrameObserver(false, RtcEnginePositionPreRender);
      // 取消注册视频数据回调
      engine->RegisterVideoFrameObserver(nullptr);

  7. 离开频道。

    // 离会
    engine->LeaveChannel();

  8. 销毁引擎。

// 取消事件回调监听
engine->SetEngineEventListener(nullptr); 
// 销毁SDK
RtcEngine::Destroy(engine);