当前位置: 首页 > news >正文

基于实时音视频技术的远程控制传输SDK的功能设计

      一直以来从pc anywhere 到vnc 以及windows的自带远程工具,后来疫情加速了todesk的成功,占据了向日葵的远控市场,还有开源的rustdesk,硬件版的pikvm,以及久负盛名的云游戏sunshine moonlight。所有的远控工具都在追求极速,小码流,画质以及私密连接,这些努力在多年以后引来了巨大的行业技术的发展,特别是云游戏的现金诱惑,让这门技术变得越来越值钱。

     经过多年的积累,在音视频的应用过程中,如何生成一套自己的sdk来适应这些新的快速变化的音视频应用场景,我做了一些工程化的尝试实践,并取得了一些成果。

  

 

      流媒体的极速传输一定是非webrtc莫属,但是webrtc的传输又有很多Qos的设计,会影响画质和延迟,需要基于webrtc传输来实现自己的数据传输应用层协议,在延迟和质量间做取舍是一门工程学的艺术,比如工程实践中可以尽量采用单一IDR帧,在非必须情况下可以只传p帧以减少传输量,采集编码采用零拷贝以降低cpu消耗和延迟。当然这些实践需要耗费很多精力和时间去摸索,兴趣和正向激励都不可或缺,我也在路上不停的给自己打气。

     我没有科班经历,所有的都是野路子,写的代码也不是那么的优雅,大部分都是我实践的一些经验,希望大佬们看到了不要笑话,在下会虚心学习各位的意见建议,下面是部分sdk的使用代码,可以实现信令,从远端拉流,本地发布并向远端传输流,支持多种流的协议,支持极速H265编码传输,可以快速的部署到windows, linux, android等平台设备,方便的集成sunshine的云游戏的生态,能够对等的进行音视频以及文件的互传互通,本地流管理方便的采用了订阅发布机制,可以将复杂的业务逻辑转换成简单的生产消费,包括级联也是可以的。在ipc监控,远程控制,群组通话等应用场景均可以实现简单的接入,甚至同时拥有这些功能,驻留后台服务既是应用也是边缘服务器,可以组网也可以p2p。下面的代码算是抛砖引玉吧,在大环境不好的当下,希望结识优秀的你共同学习,创造更多可能性

//
// Created by xiang on 2023/7/14.
//#include "gomediainterface.h"
#include "macro.h"
#include "Nalu.h"
extern "C" {
#include <libgomedia.h> // 根据实际情况包含生成的头文件
}#include <boost/log/expressions.hpp>
#include "../logging.h"static void *pullThread(void* self){gomediainterface* tself = (gomediainterface*)self;if(tself) {LOGD("pullThread %p", tself);LOGD("muri %s", tself->GetUri());//CallRtspClientStart(reinterpret_cast<void*>(tself->GetgoInstance()),tself->GetUri());CallRtspClientStart2(reinterpret_cast<void *>(tself->GetgoInstance()), tself->GetUri(),tself->GetChannel());}return 0;
}
static void *pullWebrtcThread(void *self){gomediainterface* tself = (gomediainterface*)self;if(tself) {tself->StartWebRTCPull();}return 0;
}
void OnSendPacket(void * callclass, uint8_t* data, int length,int mediatype,int channel,int inject,int duration) {if(callclass){gomediainterface *rtspclient=(gomediainterface*)callclass;if(data&&length>0) {rtspclient->onMediaDataReceive(data, length, mediatype, channel, inject, duration);}}
}extern int startkvmstream(char* displayname, int width, int height, int pixfmt, int fps, int gop, int ratekbps, int audiomuted, int playerid, int channel);extern void StopVideoStream(char* videopath, int playerid, int channel);extern void ReqIDR(int playerid, int channel);extern void AbsMouseMove(int playerid, int channel, int x, int y);extern void RelMouseMove(int playerid, int channel, int x, int y);extern void MouseUp(int playerid, int channel, int button);extern void MouseDown(int playerid, int channel, int button);extern void KeyUp(int playerid, int channel, int key,int modifiers);extern void KeyDown(int playerid, int channel, int key, int modifiers);extern void KeyTap(int playerid, int channel, int key,int modifiers);extern void MouseWheel(int playerid, int channel, int delta);extern void KeyProcess(int playerid, int channel, char* keystr, bool isKeyDown);extern void ClipBoardText(int playerid, int channel, char* textstr);extern void SwitchCursor(int playerid, int channel, int ndisplay);extern void *GetKVMVideoConfigInfo();// void OnKeyUp(int playerid, int channelid, int key, int modifiers);
void OnClipBoardText(int playerid, int channelid, char* textstr) {ClipBoardText(playerid, channelid,textstr);
}
void OnKeyProcess(int playerid, int channelid, char* keystr, int isKeyDown) {KeyProcess(playerid, channelid, keystr, isKeyDown==0?false:true);
}
void OnKeyUp(int playerid, int channelid, int key, int modifiers) {KeyUp(playerid, channelid, key, modifiers);
}
// void OnKeyDown(int playerid,int channelid,int key,int modifiers);
void OnKeyDown(int playerid, int channelid, int key, int modifiers) {KeyDown(playerid, channelid, key, modifiers);
}
void OnKeyTap(int playerid, int channelid, int key, int modifiers) {KeyTap(playerid, channelid, key, modifiers);
}
void OnAbsMouseMove(int playerid, int channel, int x, int y) {AbsMouseMove(playerid, channel, x, y);
}
void OnRelMouseMove(int playerid, int channel, int x, int y) {RelMouseMove(playerid, channel, x, y);
}
void OnMouseUp(int playerid, int channel, int button) {MouseUp(playerid, channel, button);
}
void OnMouseDown(int playerid, int channel, int button) {MouseDown(playerid, channel, button);
}
void OnMouseWheel(int playerid, int channelid, int delta) {MouseWheel(playerid, channelid, delta);
}
void OnSwitchCursor(int playerid, int channel,int ndisplay) {SwitchCursor(playerid, channel,ndisplay);
}
// void OnStartTransFile(char* filename) {// }
void OnStartKVM(char* displayname, int width, int height, int pixfmt, int fps, int gop, int ratekbps, int audiomuted, int playerid, int channel) {LOGI("I go command from client to server start kvm %s %d/%d %s fps:%d gop:%d playerid:%d channelid:%d", displayname, width, height, (pixfmt == 0) ? "H264" : (pixfmt == 1) ? "H265" : "AV1",fps,gop, playerid, channel);startkvmstream(displayname, width, height, pixfmt, fps,gop,ratekbps, audiomuted,playerid, channel);
}
void OnStopKVM(char* displayname,int playerid, int channel) {LOGI("I go command from client to server stop kvm %s %d %d",displayname,playerid,channel);StopVideoStream(displayname,  playerid, channel);
}
void OnReqIDR(int playerid, int channel) {LOGI("I go command from client to server req idr  %d %d",playerid,channel);ReqIDR(  playerid, channel);
}
void AndroidLogD(char* msg) {// BOOST_LOG(info) << "gomedia msg "sv << msg;LOGD("go msg %s", msg);
}
//cgo回调获取rtsp音频参数
void OnAudioCodecConfig(void* callclass, unsigned char* data, int length,void * mpeg4audioconf, int mediatype,int channel) {LOGI("OnAudioCodecConfig %d %d %d", mediatype, length,channel);
//    dumphex("OnCodecConfig", data, length);MPEG4AudioConfig *mpeg4AudioConfig = (MPEG4AudioConfig *) mpeg4audioconf;LOGD("OnAudioCodecConfig SampleRate %d ChannelLayout %d ObjectType %d SampleRateIndex %d ChannelConfig %d ",mpeg4AudioConfig->SampleRate,mpeg4AudioConfig->ChannelLayout,mpeg4AudioConfig->ObjectType,mpeg4AudioConfig->SampleRateIndex,mpeg4AudioConfig->ChannelConfig);if(callclass){gomediainterface *rtspclient=(gomediainterface*)callclass;if(mediatype==av_AAC){ //aacrtspclient->SetConfBuf(data,length);rtspclient->SetAudioParameter(mpeg4AudioConfig->SampleRate,mpeg4AudioConfig->ChannelConfig);}}
}
// cgo回调获取rtsp视频参数
void OnVideoCodecConfig(void* callclass, void* configdata, int mediatype,int channel) {LOGI("OnVideoCodecConfig %d %d",mediatype,channel);VideoCodecData* codecData = (VideoCodecData*)configdata;LOGI("OnCodecConfig w/d %d:%d FPS %d ProfileIdc %d",codecData->Width,codecData->Height,codecData->FPS,codecData->ProfileIdc);if(callclass){gomediainterface *rtspclient=(gomediainterface*)callclass;if(mediatype==av_H264||mediatype==av_H265){// 将接收到的数据转换为兼容的结构体类型VideoCodecData* codecData = (VideoCodecData*)configdata;// 使用 codecData 中的字段进行处理rtspclient->SetVideoParameter(codecData->Width,codecData->Height,codecData->FPS,mediatype);}}// 在这里进行进一步处理...
}
void OnGoMediaStatus(void* callclass,int status,int channel) {LOGD("go msg OnGoMediaStatus status %d",status);if(callclass){gomediainterface *rtspclient=(gomediainterface*)callclass;auto statusset=rtspclient->GetMediaStatusCallBack();if(statusset){statusset(status,channel);}}}
//内部断线超时关闭时回调,让上次做自动关闭动作
void OnVideoClose(void* callclass,int videoid){LOGD("go msg OnVideoClose videoid %d",videoid);if(callclass) {gomediainterface *rtspclient = (gomediainterface *) callclass;auto callb=rtspclient->GetOnVideoCloseCallBack();if(callb){callb(videoid);}}
}
void OnAudioClose(void* callclass,int cameraid){LOGD("go msg OnAudioClose videoid %d",cameraid);if(callclass) {gomediainterface *rtspclient = (gomediainterface *) callclass;auto callb=rtspclient->GetOnAudioCloseCallBack();if(callb){callb(cameraid);}}
}
std::mutex gomediamutex;
std::vector<gomediainterface*> g_GomediaInterfaces;
void _AddGlobleGoWebrtcInterface(gomediainterface *&go_interface){std::lock_guard<std::mutex> lock(gomediamutex);g_GomediaInterfaces.push_back(go_interface);LOGI("_AddGlobleGoWebrtcInterface  %d ",g_GomediaInterfaces.size());
}
void _ReleaseGlobleGoWebrtcInterface(){std::lock_guard<std::mutex> lock(gomediamutex);g_GomediaInterfaces.clear();}
void OnFileDataReceived(void* callclass, int channel, int len, unsigned char* data){if (callclass){LOGD("on file data %d",len);//onFileData received        }
}
void OnHIDDataReceived(void* callclass, int channel, int len, unsigned char* data){if (callclass){LOGD("on hid data %d",len);//onHIDData received        }
}//呼叫回调,做响铃提醒,并在app层调用流发布和应答开启对讲
void OnTalkCallIn(char *userid){LOGD("have %s call in \n",userid);const char *msg="just test";for (int i = 0; i < g_GomediaInterfaces.size(); i ++){auto interface=g_GomediaInterfaces[i];LOGD("g_GomediaInterfaces[%d]=%p",i,interface);if(interface){}}
//    char *oncallmsg="agree call in";
//    AgreeTalk((void*)oncallrtsp->GetgoInstance(),userid,2,1,oncallmsg);
}
gomediainterface::gomediainterface(int channel){goInstance = NewMediaImplementation();if(goInstance){LOGD("goInstance %lu",goInstance);}else{LOGE("NewMediaImplementation FAILED");}SetCallbackWrapper(reinterpret_cast<void*>(goInstance), reinterpret_cast<void*>(this),channel);
//    mChannel=channel;}
gomediainterface::gomediainterface(const char *uri,int channel){if(mUri==nullptr){mUri=new char[strlen(uri)+1];if(mUri!=nullptr){strcpy(mUri,uri);LOGD("mUri %s",mUri);}}goInstance = NewMediaImplementation();if(goInstance){LOGD("goInstance %lu",goInstance);}else{LOGE("NewMediaImplementation FAILED");}SetCallbackWrapper(reinterpret_cast<void*>(goInstance), reinterpret_cast<void*>(this),channel);mChannel=channel;}
//构造函数直接启动webrtc信令
gomediainterface::gomediainterface(const char *uri,int channel,char *mqttserver,char* sn,char *clientid,char * stunserver,char * turnserver ,char *turnservername,char * turnserverpassword,bool busewebrtc){if(mUri==nullptr){mUri=new char[strlen(uri)+1];if(mUri!=nullptr){strcpy(mUri,uri);LOGD("mUri %s",mUri);}}goInstance = NewMediaImplementation();if(goInstance)    LOGD("goInstance %lu",goInstance);SetCallbackWrapper(reinterpret_cast<void*>(goInstance), reinterpret_cast<void*>(this),channel);mChannel=channel;if(busewebrtc){SetWebRTCConfig(mqttserver, sn, clientid,stunserver, turnserver, turnservername,turnserverpassword);}
}
gomediainterface::~gomediainterface(){if(mUri!=nullptr){delete mUri;mUri = nullptr;}if(mServername) {delete mServername;mServername=nullptr;}if(mStreamname) {delete mStreamname;mStreamname=nullptr;}if(mTransmode) {delete mTransmode;mTransmode=nullptr;}if(opusdecoder){delete opusdecoder;opusdecoder=nullptr;}if(aacDecoder) {delete aacDecoder;aacDecoder = nullptr;}
#if USE_MIXERif(audiomixer) {delete audiomixer;audiomixer= nullptr;}
#endifif(goInstance){//销毁 goInstanceRemoveMediaImplementation(reinterpret_cast<void*>(goInstance));goInstance=0;}
}
void gomediainterface::StartRtspClient()
{// pull_rtsp_client_thread=std::thread(&rtspClient::pullThread, this,this);pthread_create(&pull_rtsp_client_thread, 0, pullThread, (void*)this);
}
void gomediainterface::GetRtspConfig(int channel){LOGD("GetRtspConfig");GetMediaAudioExternData(reinterpret_cast<void*>(goInstance),channel);
}
void gomediainterface::StopRtspClient(int channel){LOGD("gomediainterface::StopRtspClient close ...");CallRtspClientStop(reinterpret_cast<void*>(goInstance),channel);if(pull_rtsp_client_thread){pthread_join(pull_rtsp_client_thread,NULL);}LOGD("gomediainterface::StopRtspClient closed");// pull_rtsp_client_thread.join();
}
int gomediainterface::PublishStream(char *streamname,int channel,int mediatype,int card,int device){int deviceid=-1;switch(mediatype) {case av_H264:case av_H265:deviceid = CallStartVideoStreamPublish((void *) (goInstance), streamname, channel);LOGD("\ngomediainterface PublishStream deviceid is %d\n", deviceid);return deviceid;case av_OPUS:deviceid=CallStartAudioStreamPublish((void*)(goInstance),streamname,card,device,channel);LOGD("\ngomediainterface PublishStream deviceid is %d\n",deviceid);return deviceid;default:break;}return deviceid;
//        mVideodeviceID=deviceid;
}
//启动从节点servername拉流streamname,生成级联拉流并生成本地流供本地处理和其他节点共享
// 采用transmode “webrtc_trans” 或者“datachannel_trans”传输模式 H265目前支持支datachannel_trans,
// bOnDataCall表示允许回调供渲染,不回调数据则只做数据级联生成本地流
void gomediainterface::SetWebRTCPullStream(char *servername, char *streamname,char * transmode,bool bIsH265,int bUseVideo,int bUseAudio,int bOnDataCall,int signalid,int channel){if(mServername) {delete mServername;mServername=nullptr;}if(mStreamname) {delete mStreamname;mStreamname=nullptr;}if(mTransmode) {delete mTransmode;mTransmode=nullptr;}mServername=new char(strlen(servername)+1);strcpy(mServername,servername);mStreamname=new char(strlen(streamname)+1);strcpy(mStreamname,streamname);mTransmode=new char(strlen(transmode)+1);strcpy(mTransmode,transmode);mSignalid=signalid;mbIsH265=bIsH265;mbUseVideo=bUseVideo;mbUseAudio=bUseAudio;mbOnDataCall=bOnDataCall;mChannel=channel;
}
void gomediainterface::StartWebRTCPullStream() {pthread_create(&pull_webrtc_client_thread, 0, pullWebrtcThread, (void*)this);
}
void gomediainterface::StartWebRTCPull() {LOGD("StartWebRTCPull %s %s %s %d %d %d %d %d %d\n",mServername,mStreamname,mTransmode,mbIsH265,mbUseVideo,mbUseAudio,mbOnDataCall,mChannel,mSignalid);if(mbIsH265) {CallStartVideoStreamPull((void *) (goInstance),mSignalid, mServername, mStreamname,"audio/opus", "video/H265", "datachannel_trans",mbUseVideo, mbUseAudio, mbOnDataCall,mChannel);}else{CallStartVideoStreamPull((void *) (goInstance),mSignalid, mServername, mStreamname, "audio/opus", "video/H264",mTransmode, mbUseVideo, mbUseAudio, mbOnDataCall,mChannel);}
}
//设置webrtc参数并启动mqtt信令代理
void gomediainterface::SetWebRTCConfig(char *mqttserver , char *sn , char *clientid, char *stunserver, char *turnserver, char *turnservername, char *turnserverpassword ,int signalid){goWebrtcConfig = GetMediaWebRTCConfig((void *) (goInstance),signalid);if(goWebrtcConfig) {LOGD("GetKVMVideoConfigInfo\r\n");auto config=(VideoConfigInfo*)GetKVMVideoConfigInfo();if(config){//设置kvm的视频采集参数,用于注册BOOST_LOG(info) << "videoconfig is width: "sv << config->width << "height :"sv <<config->height<< "framerate:"sv <<config->framerate << "videoformate:"sv <<config->videoFormat; SetVideoConfigInfo((void *) goWebrtcConfig,config);   }SetWebrtcConfig((void *) goWebrtcConfig, mqttserver, sn , clientid , stunserver , turnserver , turnservername , turnserverpassword );CallStartWebrtc((void *) (goInstance),signalid);}
}
//停止信令
void gomediainterface::StopWebRTCSignal(int signalid){CallStopWebrtc((void *) (goInstance),signalid);
}
void gomediainterface::StopWebRTCPullStream(char *streamname){LOGD("gomediainterface::StopWebRTCPullStream  CallStopWebrtcStream close %s...",streamname);CallStopWebrtcStream(reinterpret_cast<void*>(goInstance),streamname);
}
void gomediainterface::StopWebRTCPullStream(int channel){LOGD("gomediainterface::StopWebRTCPullStream close %d...",channel);
//    CallStopWebrtcStream(reinterpret_cast<void*>(goInstance),channel);
}
void gomediainterface::StopWebRTCClient(){if(pull_webrtc_client_thread){pthread_join(pull_webrtc_client_thread,NULL);}LOGD("gomediainterface::StopWebRTCClient closed");// pull_rtsp_client_thread.join();
}
//此处回调只有rtsp拉流或者webrtc拉流,增加一个逻辑变量标识是否再注入
void gomediainterface::onMediaDataReceive( uint8_t* data, int length,int mediatype,int channel,int binject,int duration){
//    LOGD("onMediaDataReceive channel:%d mediatype:%d length %d",mChannel , mediatype ,length);
//    Nalu::dumphex("onMediaDataReceive", data, length);if(mMediacallfunc)   mMediacallfunc((void *) data, (size_t) length,mediatype,channel); //回调if(binject){//向webrtc 流中注入数据,内部会判断是否有虚拟摄像头或音频节点,只有本地的设备节点或者rtsp拉流到本地才发布生成内部虚拟节点,webrtc拉流不会生成虚拟节点,只有流管理,所以webrtc拉流出来注入式直接返回//目前还没有其他办法来判断过滤是否注入数据,注入音频数据需要转换成opus格式,如果回调的数据类型为opus类型则需要处理解码或者转aac编码if(mediatype==av_AAC){}else {tInfo videoinfo;videoinfo.a = data;// void* a;// unsigned long long  int timestamp;videoinfo.size = length;// int  pcmsize;videoinfo.mediatype = mediatype;videoinfo.devid = channel;CallSendData((void *) goInstance, &videoinfo);}}
}
void gomediainterface::SendToWebrtc(void * data,int length,int mediatype,int channel){if(mediatype==av_AAC){//transcode aac to opustrancodeAAC2OPUS(data, length,channel);}else {tInfo videoinfo;videoinfo.a = data;// void* a;// unsigned long long  int timestamp;videoinfo.size = length;// int  pcmsize;videoinfo.mediatype = mediatype;videoinfo.devid = channel;CallSendData((void *) goInstance, &videoinfo);}
}void gomediainterface::trancodeAAC2OPUS(void * databuffer,int len,int channel){void *pData=databuffer;int outSize = len;int encsize=len;if(!aacDecoder){// GetMediaAudioExternData(reinterpret_cast<void*>(goInstance),channel);// char filename[256];// snprintf(filename,256,"%s.mp4",mSN);// StartMP4Recorder(filename);aacDecoder=new AACDecoder();if (!aacDecoder) {return ;}// int dsi[]={0x11,0x90};// printf("")// MP4E_set_dsi(mux, audio_track_id,  maacInfo!=NULL?maacInfo->confBuf:(void*)dsi, maacInfo!=NULL?maacInfo->confSize:2);// if (aacDecoder->initDecoder(maacInfo!=NULL?maacInfo->confBuf:(void*)dsi,maacInfo!=NULL?maacInfo->confSize:2)) {if (aacDecoder->initDecoder(mConfBuf,mConfBufLen)) {
//            pOut=  (char*)&OutputBuf[audioDataOutIndex][0];char* pOut=(char*)pData;if(aacDecoder->Decode((char *)(databuffer), static_cast<UINT>(len),pOut,outSize)){
//                pData=(void*)p}//            if(++audioDataOutIndex>=MAX_AUDIO_QUEUE_SIZE) audioDataOutIndex=0;}else{return;}
//        if(mBPublish){
//            mAudioDeviceID=CallStartAudioStreamPublish((void*)(goInstance),(char*)mStreamath,0,0,mChannel);
//        }}else{char* pOut=(char*)pData;
//        pOut=(char *)&OutputBuf[audioDataOutIndex][0];aacDecoder->Decode((char *)(databuffer), static_cast<UINT>(len),pOut,outSize);
//        if(++audioDataOutIndex>=MAX_AUDIO_QUEUE_SIZE) audioDataOutIndex=0;}
//    int outSize = outsize;
#if USE_MIXERif(audiomixer== nullptr){audiomixer=new AudioMixer();if(audiomixer) {// 添加输入流// 参数根据实际pcm数据设置
//            printf("addAudioInput %d %d\n",mpeg4AudioConfig.SampleRate, mpeg4AudioConfig.ChannelLayout);audiomixer->addAudioInput(0, mAudioRate, mAudioChannels, 16, AV_SAMPLE_FMT_S16);
//                audiomixer->addAudioInput(1, rate, channels, 16, AV_SAMPLE_FMT_S16);// 添加输出流audiomixer->addAudioOutput(48000, 2, 16, AV_SAMPLE_FMT_S16);// tempchannels = 2;// 初始化if (audiomixer->init("longest") < 0) {printf("audioMix.init() failed!");}else{
//                    if ((audiomixer->addFrame(0, (uint8_t*)buffer, size) < 0) &&(audiomixer->addFrame(1, NULL, 0) < 0)) {
//                printf("addFrame %p %d\n",pOut,outsize);if(audiomixer->addFrame(0, (uint8_t*)pData, outSize) < 0){printf("audioMix.addFrame() failed!");return;}else {while ((outSize = audiomixer->getFrame((uint8_t*)pData, 10240)) > 0) {
//                            pData = &outBuf[0];
//                        outsize=outSize;printf("audiomixer->getFrame %d \n",outSize);//            fwrite(outBuf, 1, outSize, fileOut);}}}}}else{
//            if ((audiomixer->addFrame(0, (uint8_t*)buffer, size) < 0) && (audiomixer->addFrame(1, NULL, 0) < 0)) {
//        printf("addFrame %p %d\n",pOut,outsize);if(audiomixer->addFrame(0, (uint8_t*)pData, outSize) < 0){printf("audioMix.addFrame() failed!");return;}else {while ((outSize = audiomixer->getFrame((uint8_t*)pData, 10240)) > 0) {
//                    pData = &outBuf[0];
//                outsize=outSize;printf("audiomixer->getFrame %d \n",outSize);//            fwrite(outBuf, 1, outSize, fileOut);}}}
#endifif(!opusdecoder){// GetMediaAudioExternData(reinterpret_cast<void*>(goInstance),channel);opusdecoder = new OpusCodec;
//        auto audioconfig=GetMPEG4AudioConfig();// printf("audioconfig %d %d\n",audioconfig->SampleRate, audioconfig->ChannelLayout);// opusdecoder->initEncoder(audioconfig->SampleRate, audioconfig->ChannelLayout, 0);opusdecoder->initEncoder(48000, 2, 0);opus_frame_callback_=std::bind(&gomediainterface::SendToWebrtc, this, std::placeholders::_1,std::placeholders::_2, std::placeholders::_3,std::placeholders::_4);opusdecoder->encode((const short *) pData, outSize,channel, opus_frame_callback_);}else{opusdecoder->encode((const short *) pData, outSize,channel, opus_frame_callback_);}
}

    

http://www.xdnf.cn/news/1242451.html

相关文章:

  • 【ECCV2024】AdaCLIP:基于混合可学习提示适配 CLIP 的零样本异常检测
  • [GESP202306 四级] 2023年6月GESP C++四级上机题超详细题解,附带讲解视频!
  • 刷题记录0804
  • ref和reactive的区别
  • 8位以及32位的MCU如何进行选择?
  • ArrayDeque双端队列--底层原理可视化
  • Redis 常用数据结构以及单线程模型
  • LeetCode 140:单词拆分 II
  • Array容器学习
  • app-1
  • 优选算法 力扣 11. 盛最多水的容器 双指针降低时间复杂度 贪心策略 C++题解 每日一题
  • Javascript面试题及详细答案150道之(031-045)
  • python包管理器uv踩坑
  • 力扣面试150题--加一
  • PCL统计点云Volume
  • ArcGIS的字段计算器生成随机数
  • 配置Mybatis环境
  • 【多智能体cooragent】CoorAgent 系统中 5 个核心系统组件分析
  • 一起学springAI系列一:流式返回
  • 【实战】Dify从0到100进阶--中药科普助手(1)
  • 嵌入式硬件中三极管原理分析与控制详解
  • 零售消费行业研究系列报告
  • 微帧GPU视频硬编优化引擎:面向人工智能大时代的AI算法与硬编协同优化方案
  • [特殊字符]️ 整个键盘控制无人机系统框架
  • 【AI 加持下的 Python 编程实战 2_13】第九章:繁琐任务的自动化(中)——自动批量合并 PDF 文档
  • 【银河麒麟服务器系统】自定义ISO镜像更新内核版本
  • 数据结构---配置网络步骤、单向链表额外应用
  • 从物理扇区到路径访问:Linux文件抽象的全景解析
  • 深入剖析RT-Thread串口驱动:基于STM32H750的FinSH Shell全链路Trace分析与实战解密(上)
  • 深度学习TR3周:Pytorch复现Transformer