如果你希望为你的 PC 端 RTC 应用接入第三方 SDK 进行视频处理,例如美颜,参考 Native 端自定义视频处理。根据所使用的第三方 SDK 的特性,你可能还需要考虑线程设计和视频帧格式对齐的问题。
本文以接入美颜 SDK 为例,介绍在自定义视频处理时,线程设计和视频帧格式处理的最佳实践。
RTC SDK 回调 processVideoFrame 的线程来自不同的线程,如果美颜 SDK 不支持在多个线程渲染,可能会导致渲染失败等问题,如下图:
因此,你需要将美颜相关活动集中到一个线程中。
m_thread_process
。funcProcessVideoFrame
,初始化美颜模型,处理来自 RTC SDK 回调的视频帧。processVideoFrame
函数负责将传入的视频帧存储起来,并通知 funcProcessVideoFrame
线程内通过 dealVideoFrame
进行自定义的视频帧处理操作。{ //初始化变量 std::thread* m_thread_process = nullptr; std::mutex m_mutex_process; std::atomic<bool> m_thread_run {true}; std::condition_variable m_cond1, m_cond2; std::atomic<int> m_process_num; bytertc::IVideoFrame* m_currentFrame = nullptr; } m_thread_process = new std::thread(funcProcessVideoFrame, this); //启动新线程 //创建引擎 m_video = bytertc::createRTCVideo(g_appid.c_str(), m_handler.get(), nullptr); m_video->startAudioCapture(); m_video->startVideoCapture(); m_video->registerLocalVideoProcessor(this); //注册回调接口 m_room = m_video->createRTCRoom(roomid); m_room->joinRoom(); //自定义帧处理 bytertc::IVideoFrame *processVideoFrame(bytertc::IVideoFrame * videoFrame) { if (!videoFrame) return nullptr; std::unique_lock<std::mutex> lock(m_mutex_process); m_process_num++; m_currentFrame = videoFrame; m_cond1.notify_one(); m_cond2.wait(lock, [this]() {return m_thread_run == false || m_process_num == 0; }); return m_currentFrame; } void funcProcessVideoFrame() { while (1) { if (m_thread_run == false) { return; } std::unique_lock<std::mutex> l(m_mutex_process); m_cond1.wait(l, [this]() {return m_thread_run == false || m_process_num > 0; }); dealVideoFrame();//处理视频帧,美颜 m_process_num--; m_cond2.notify_one(); } } void dealVideoFrame(){ // 处理视频帧的具体实现 //你可以在此进行自定义美颜处理 //sdk->deal(); }
RTC SDK 返回的视频帧格式会进行字节对齐,因此,在内存拷贝时,需要先判断对齐格式,再进行数据拷贝。
//将 YUV frame 的数据拷贝到 buffer 中: if (m_currentFrame->getPlaneStride(0) > width || m_currentFrame->getPlaneStride(1) > (width+1)/2 || m_currentFrame->getPlaneStride(2) > (width+1)/2) { int dst_offset = 0; int src_offset = 0; for(int i=0; i<height; i++) { memcpy(frameBuffer + dst_offset, m_currentFrame->getPlaneData(0) + src_offset, width); src_offset += m_currentFrame->getPlaneStride(0); dst_offset += width; } src_offset = 0; for (int i=0; i<height/2; i++) { memcpy(frameBuffer + dst_offset, m_currentFrame->getPlaneData(1) + src_offset, width/2); src_offset += m_currentFrame->getPlaneStride(1); dst_offset += width/2; } src_offset = 0; for (int i=0; i<height/2; i++) { memcpy(frameBuffer + dst_offset, m_currentFrame->getPlaneData(2) + src_offset, width/2); src_offset += m_currentFrame->getPlaneStride(2); dst_offset += width/2; }