文档

Windows

更新时间:

阿里云RTC为您提供了输入外部音视频流的功能。通过阅读本文,您可以了解输入外部音视频流、视频流数据输出以及外部音频输入播放的方法。

使用场景

使用场景包括但不限于以下:

  • 需要将本地媒体文件(视频/音频)及第三方音视频数据,通过SDK传输到远端播放渲染,可使用音视频外部输入推流实现。

  • 需要在使用音频外部输入的同时,本地播放(耳返)输入内容,可使用外部音频输入播放实现。

  • 需要将通信过程中视频数据保存或输出处理(外部渲染,修改内容)时,可使用视频数据裸数据输出实现。

输入外部视频数据推流

  1. 调用SetExternalVideoSource启用外部视频输入推流。

    说明

    目前Windows端不支持texture模式。

  2. 应用侧持续调用接PushExternalVideoFrame,向 SDK 投递视频裸数据,进行推流,参数 frame 传入裸数据相关信息,参数 track 指明推流 track 类型,与步骤1 中设置开启的track类型保持一致。

    说明
    • 投递视频帧数据的频率由应用方控制,依据视频源帧率保持间隔投递,直至输入停止。建议应用侧独立开启线程,进行数据投递,保证数据输入及时性。

    • 目前 Windows 端支持输入 YUV数据(格式I420),需要在参数 frame 的裸数据信息中,指定 pixelFormat RtcEngineVideoI420frameTypeRtcEngineVideoFrameRaw

    • 赋值裸数据信息时,需要数据指针、视频宽高信息、stride 信息等完整传入;旋转角度 rotation 目前暂未支持设置,请保持默认值 0;时间戳取当期时间,单位为毫秒。

  3. 数据源输入结束或应用中止外部视频输入,调用接口SetExternalVideoSource关闭外部视频输入。

代码示例:

ding::rtc::RtcEngine *mediaEngine =;
.....

// 1.启用外部视频输入
mediaEngine->SetExternalVideoSource(true, ding::rtc::RtcEngineVideoTrackCamera); // camera track启用外部输入

.....

// 2.独立线程推送外部视频数据
int frameRate = 30; // 30 fps
void *buffer = malloc(bufsize);
do
{
    ding::rtc::RtcEngineVideoFrame frm;
    memset(&frm, 0, sizeof(frm));
    frm.frameType = ding::rtc::RtcEngineVideoFrameRaw;
    frm.pixelFormat = ding::rtc::RtcEngineVideoI420;
    frm.timestamp = now;
    frm.width = // pic width;
    frm.height = // pic height;
    frm.rotation = 0;
    frm.count = count;
    frm.stride[0] = // ybuffer stride;
    frm.stride[1] = // ubuffer stride;
    frm.stride[2] = // vbuffer stride;
    frm.data = buffer;
    mediaEngine->PushExternalVideoFrame(&frm, ding::rtc::RtcEngineVideoTrackCamera);

    // 控制帧数据投递频率
    Sleep(1000 / frameRate);
    /* 产品代码用控制帧率比较复杂一些,不能用Sleep(frame_duration)方式,
     * 因为Sleep非常不准
    int64_t now = osal_get_time_ms();
    if (now - startTime < frameCount * frameDurationMs) {
        // 时间未到,再等一会
        osal_sleep_ms(2);
        continue;
    }
    */
} while (more);
free(buffer);
.....

// 3.停止外部视频输入
mediaEngine->SetExternalVideoSource(false, ding::rtc::RtcEngineVideoTrackCamera);

输入外部音频数据推流

  1. 调用接口SetExternalAudioSource启用外部音频输入推流。

  2. 应用侧持续调用PushExternalAudioFrame接口,向 SDK 投递音频PCM 数据,参数frame带入音频数据。

    说明
    • 投递音频裸数据的频率由应用方控制,每次投递数据量不要超过 240ms 的音频数据量,建议每次投递 20ms 的音频数据,保持循环投递直到结束。当输入数据频率过快,SDK 缓存已满暂时无法消费数据时,将丢失输入音频数据。

    • 与视频输入一致,同样建议应用侧单独开启线程,投递音频裸数据,直到输入停止。

  3. 数据源输入结束或应用中止外部音频输入,调用接口SetExternalAudioSource关闭外部视音频输入。

代码示例:

ding::rtc::RtcEngine *mediaEngine =;
.....

// 1.启用外部视频输入
mediaEngine->SetExternalAudioSource(true, sample_rate, stereo ? 2 : 1);

.....


// 2.独立线程推送外部音频数据
void *ExtAudioSrc::worker(void *arg)
{
  ExtAudioSrc *pthis = (ExtAudioSrc *)arg;
  if (!pthis) {
    return NULL;
  }

  osal_thread_set_name("extaudiosrc");

  ding::rtc::RtcEngineAudioFrame frm;
  memset(&frm, 0, sizeof(frm));

  const int bytesPerSample = 2; // short
  const int frameMs = 20; // 20 ms per frame

  const int bufsize = pthis->sample_rate * frameMs / 1000 * (pthis->stereo ? 2 : 1) * bytesPerSample;

  FILE *fp = fopen(pthis->pcm_file_path.c_str(), "rb");
  if (fp == NULL) {
    return NULL;
  }

  void *buffer = malloc(bufsize);
  int64_t startTime = osal_get_time_ms();
  int count = 0;

  while (!pthis->quit_thread_flag_) {
    // maintain fps
    int64_t now = osal_get_time_ms();
    if ((now - startTime) * 1000 / frameMs < count * 1000) {
        osal_sleep_ms(2);
        continue;
    }

    // read pcm file
    int bytes = (int)fread(buffer, 1, bufsize, fp);
    if (bytes < bufsize) {
        // eof
        fseek(fp, 0, SEEK_SET);
        bytes = (int)fread(buffer, 1, bufsize, fp);
        if (bytes < bufsize) {
            break;
        }
    }

    count++;

    // callback
    frm.type = ding::rtc::RtcEngineAudioFramePcm16;
    frm.bytesPerSample = bytesPerSample;
    frm.samplesPerSec = pthis->sample_rate;
    frm.channels = (pthis->stereo ? 2 : 1);
    frm.samples = pthis->sample_rate * frameMs / 1000;
    frm.buffer = buffer;
    pthis->cb_on_one_frame_(&frm, pthis->cb_arg_);
  }

  if (buffer)
    free(buffer);
  if (fp != NULL)
    fclose(fp);

  return NULL;
}

void ExtAudioSrc::start_sampler(void on_frame(void *frm, void *arg), void *arg)
{
  if (sample_thread_ != NULL) {
    return;
  }

  cb_on_one_frame_ = on_frame;
  cb_arg_ = arg;

  quit_thread_flag_ = false;
  osal_thread_create(&sample_thread_, worker, this);
}
.....

ext_audio_src_.start_sampler([](void *frm, void *arg) {
      // 从文件读取到一帧数据,通过PushExternalAudioFrame传递给SDK
			ding::rtc::RtcEngine *engine = (ding::rtc::RtcEngine *)arg;
			engine->PushExternalAudioFrame((ding::rtc::RtcEngineAudioFrame *)frm);
			}, GetGlobalContext()->engine_);

// 3. 停止外部音频输入播放
void ExtAudioSrc::stop_sampler()
{
  if (sample_thread_ != NULL) {
    quit_thread_flag_ = true;
    osal_thread_join(sample_thread_, NULL);
    sample_thread_ = NULL;
  }
}

engine_->SetExternalAudioSource(false, 0, 0);
// stop reading thread
ext_audio_src_.stop_sampler();