数据导入
数据导入接口
由于 SDK 同时只允许发布一路音频 Track,所以在导入外部音频数据时,无需指定 Track Id; 相反,视频 Track 可能会同时存在多个,所以导入外部视频数据时需指定视频 Track Id;
音频
当用户需要使用外部音频数据源进行通话,而非使用 SDK 内置的麦克风采集时,那么请使用接口 EnableAudioFakeInput(true)
激活外部音频数据导入功能,然后按照固定频率导入音频 PCM 数据格式即可,接口为 InputAudioFrame
,目前支持的 PCM 格式如下:
- 采样率:支持各种采样率
- 位宽:16bits per sample
- 声道数:单、双声道
- 数据存储格式:packet 交叉存储
注意事项:
- 当前 SDK 内部传输格式为单声道,外部导入双声道数据后,SDK 内部会自动转换为单声道然后进行后续处理;
- 使用音频数据导入功能时,时间戳将根据实时导入的 PCM 数据进行累加计算,无需外部传入,外部务必保证数据导入频率均匀切稳定,比如:每次导入 20ms 的 PCM 数据,那么一秒钟内需导入 50 帧左右,否则可能会出现声音的卡顿等异常;
导入外部音频数据涉及 QNAudioInterface
中以下接口,分别如下:
// 开启或关闭外部音频导入的功能,需在 Publish 之前进行调用
// @param enable_flag_ true:开启;false:关闭
// @return 是否开启成功,如果失败则返回非 0 值
virtual int EnableAudioFakeInput(bool enable_flag_) = 0;
// 导入外部音频 PCM 数据,目前仅支持 signed 16bit per sample, mono 和 stereo 声道数;
// 导入外部音频数据需要均匀控制频率,过高或过低均会导致连麦语音质量的下降
// @param audio_data_ PCM 数据指针
// @param data_size_ 数据大小
// @param bits_per_sample_ 位深,目前仅支持 16bit
// @param sample_rate_ 输入音频数据的采样率
// @param number_of_channels_ 输入音频的声道数
// @param number_of_frames_ audio_data_ 数据中包含的采样点数(每声道)
virtual int InputAudioFrame(
const void* audio_data_,
unsigned int data_size_,
unsigned int bits_per_sample_,
unsigned int sample_rate_,
unsigned int number_of_channels_,
unsigned int number_of_frames_
) = 0;
// 判断是否激活了导入外部音频数据的功能
virtual bool IsEnableAudioFakeInput() = 0;
注:外部数据导入与监听功能不能同时开启。
视频
SDK 提供此接口供用户导入自己的视频数据;
注意事项:
- 目前支持导入的原始视频数据格式包括:kI420、kYUY2 和 kRGB24;
- 目前 SDK 内部支持的最大分辨率为 4K * 2K;
- 导入视频数据时时间戳单位为
微妙
,如果用户导入数据时间戳不均匀的话,将会导致画面卡顿; - 如果用户发现音画不同步的现象,可以通过调整视频导入的时间戳进行校正;
导入外部视频数据涉及 QNVideoInterface
中以下接口,分别如下:
// 导入用户自定义视频数据,前提是已发布 TrackSourceType 为 tst_ExternalYUV 的 Video Track
// @param track_id_ 已成功发布的 TrackSourceType 为 tst_ExternalYUV 的 Video Track'id
// @param data_ 数据内存指针
// @param data_size_ 数据长度
// @param width_ 图像宽度
// @param height_ 图像高度
// @param timestamp_us_ 时间戳,单位为:微妙
// @param raw_type_ 视频原始格式,目前支持:kI420 kYUY2 kRGB24
// @param rotation_ 导入后旋转角度,如果不需要旋转则使用默认值 kVideoRotation_0 即可
// @param mirror_flag_ 导入后是否镜像
// @return 成功返回 0,否则请参考错误码列表
virtual int InputVideoFrame(
const string& track_id_,
const unsigned char* data_,
const unsigned int& data_size_,
const unsigned int& width_,
const unsigned int& height_,
const unsigned long long& timestamp_us_,
qiniu_v2::VideoCaptureType raw_type_,
qiniu_v2::VideoRotation rotation_ = kVideoRotation_0,
bool mirror_flag_ = false
) = 0;
注:导入外部视频数据前,需先发布 TrackSourceType 为 tst_ExternalYUV 的视频 TrackInfo,发布成功后,再实时导入数据。
示例代码
void CRtcDemoV2::ImportExternalRawFrame(const string& track_id_)
{
// 模拟导入视频数据,使用当前目录下指定的音视频文件
_stop_external_flag = true;
if (_fake_video_thread.joinable()) {
_fake_video_thread.join();
}
if (_fake_audio_thread.joinable()) {
_fake_audio_thread.join();
}
string track_id(track_id_.c_str());
_fake_video_thread = thread([&, track_id] {
FILE* fp = nullptr;
fopen_s(&fp, "426x240.yuv", "rb");
uint8_t *buf = (uint8_t*)malloc(426 * 240 * 3 / 2);
if (!fp || !buf) {
MessageBox(_T("426x240.yuv 文件打开失败,请确认此文件件是否存在!"));
return;
}
size_t ret(0);
_stop_external_flag = false;
chrono::system_clock::time_point start_tp = chrono::system_clock::now();
while (!_stop_external_flag) {
ret = fread_s(buf, 426 * 240 * 3 / 2, 1, 426 * 240 * 3 / 2, fp);
if (ret > 0) {
_rtc_video_interface->InputVideoFrame(
track_id,
buf,
426 * 240 * 3 / 2,
426,
240,
chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now() - start_tp).count(),
qiniu_v2::VideoCaptureType::kI420,
qiniu_v2::kVideoRotation_0);
} else {
fseek(fp, 0, SEEK_SET);
continue;
}
this_thread::sleep_for(chrono::milliseconds(1000 / 30)); // 30 fps
}
free(buf);
fclose(fp);
});
// 模拟导入音频数据
_rtc_audio_interface->EnableAudioFakeInput(true);
_fake_audio_thread = thread([&] {
FILE* fp = nullptr;
fopen_s(&fp, "44100_16bits_2channels.pcm", "rb");
if (!fp) {
MessageBox(_T("PCM 文件:44100_16bits_2channels.pcm 打开失败,请确认此文件件是否存在!"));
return;
}
// 每次导入 20 ms 的数据,即 441 * 2 个 samples
uint8_t *buf = (uint8_t*)malloc(441 * 2 * 2 * 2);
size_t ret(0);
chrono::system_clock::time_point start_tp = chrono::system_clock::now();
int64_t audio_frame_count(0);
while (!_stop_external_flag) {
if (chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now() - start_tp).count() >= audio_frame_count * 20000) {
} else {
this_thread::sleep_for(chrono::microseconds(10));
continue;
}
ret = fread_s(buf, 441 * 2 * 4, 1, 441 * 2 * 4, fp);
if (ret >= 441 * 8) {
_rtc_audio_interface->InputAudioFrame(
buf,
441 * 8,
16,
44100,
2,
441 * 2
);
++audio_frame_count;
} else {
fseek(fp, 0, SEEK_SET);
continue;
}
}
free(buf);
fclose(fp);
});
}