Windows
阿里云RTC為您提供了輸入外部音視頻流的功能。通過閱讀本文,您可以了解輸入外部音視頻流、視頻流數(shù)據(jù)輸出以及外部音頻輸入播放的方法。
使用場景
使用場景包括但不限于以下:
需要將本地媒體文件(視頻/音頻)及第三方音視頻數(shù)據(jù),通過SDK傳輸?shù)竭h(yuǎn)端播放渲染,可使用音視頻外部輸入推流實(shí)現(xiàn)。
需要在使用音頻外部輸入的同時(shí),本地播放(耳返)輸入內(nèi)容,可使用外部音頻輸入播放實(shí)現(xiàn)。
需要將通信過程中視頻數(shù)據(jù)保存或輸出處理(外部渲染,修改內(nèi)容)時(shí),可使用視頻數(shù)據(jù)裸數(shù)據(jù)輸出實(shí)現(xiàn)。
輸入外部視頻數(shù)據(jù)推流
調(diào)用SetExternalVideoSource啟用外部視頻輸入推流。
說明目前Windows端不支持texture模式。
應(yīng)用側(cè)持續(xù)調(diào)用接PushExternalVideoFrame,向 SDK 投遞視頻裸數(shù)據(jù),進(jìn)行推流,參數(shù) frame 傳入裸數(shù)據(jù)相關(guān)信息,參數(shù) track 指明推流 track 類型,與步驟1 中設(shè)置開啟的track類型保持一致。
說明投遞視頻幀數(shù)據(jù)的頻率由應(yīng)用方控制,依據(jù)視頻源幀率保持間隔投遞,直至輸入停止。建議應(yīng)用側(cè)獨(dú)立開啟線程,進(jìn)行數(shù)據(jù)投遞,保證數(shù)據(jù)輸入及時(shí)性。
目前 Windows 端支持輸入 YUV數(shù)據(jù)(格式I420),需要在參數(shù) frame 的裸數(shù)據(jù)信息中,指定 pixelFormat 為 RtcEngineVideoI420, frameType 為 RtcEngineVideoFrameRaw。
賦值裸數(shù)據(jù)信息時(shí),需要數(shù)據(jù)指針、視頻寬高信息、stride 信息等完整傳入;旋轉(zhuǎn)角度 rotation 目前暫未支持設(shè)置,請保持默認(rèn)值 0;時(shí)間戳取當(dāng)期時(shí)間,單位為毫秒。
數(shù)據(jù)源輸入結(jié)束或應(yīng)用中止外部視頻輸入,調(diào)用接口SetExternalVideoSource關(guān)閉外部視頻輸入。
代碼示例:
ding::rtc::RtcEngine *mediaEngine =;
.....
// 1.啟用外部視頻輸入
mediaEngine->SetExternalVideoSource(true, ding::rtc::RtcEngineVideoTrackCamera); // camera track啟用外部輸入
.....
// 2.獨(dú)立線程推送外部視頻數(shù)據(jù)
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);
// 控制幀數(shù)據(jù)投遞頻率
Sleep(1000 / frameRate);
/* 產(chǎn)品代碼用控制幀率比較復(fù)雜一些,不能用Sleep(frame_duration)方式,
* 因?yàn)镾leep非常不準(zhǔn)
int64_t now = osal_get_time_ms();
if (now - startTime < frameCount * frameDurationMs) {
// 時(shí)間未到,再等一會(huì)
osal_sleep_ms(2);
continue;
}
*/
} while (more);
free(buffer);
.....
// 3.停止外部視頻輸入
mediaEngine->SetExternalVideoSource(false, ding::rtc::RtcEngineVideoTrackCamera);
輸入外部音頻數(shù)據(jù)推流
調(diào)用接口SetExternalAudioSource啟用外部音頻輸入推流。
應(yīng)用側(cè)持續(xù)調(diào)用PushExternalAudioFrame接口,向 SDK 投遞音頻PCM 數(shù)據(jù),參數(shù)frame帶入音頻數(shù)據(jù)。
說明投遞音頻裸數(shù)據(jù)的頻率由應(yīng)用方控制,每次投遞數(shù)據(jù)量不要超過 240ms 的音頻數(shù)據(jù)量,建議每次投遞 20ms 的音頻數(shù)據(jù),保持循環(huán)投遞直到結(jié)束。當(dāng)輸入數(shù)據(jù)頻率過快,SDK 緩存已滿暫時(shí)無法消費(fèi)數(shù)據(jù)時(shí),將丟失輸入音頻數(shù)據(jù)。
與視頻輸入一致,同樣建議應(yīng)用側(cè)單獨(dú)開啟線程,投遞音頻裸數(shù)據(jù),直到輸入停止。
數(shù)據(jù)源輸入結(jié)束或應(yīng)用中止外部音頻輸入,調(diào)用接口SetExternalAudioSource關(guān)閉外部視音頻輸入。
代碼示例:
ding::rtc::RtcEngine *mediaEngine =;
.....
// 1.啟用外部視頻輸入
mediaEngine->SetExternalAudioSource(true, sample_rate, stereo ? 2 : 1);
.....
// 2.獨(dú)立線程推送外部音頻數(shù)據(jù)
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) {
// 從文件讀取到一幀數(shù)據(jù),通過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();