Compare commits
40 Commits
master
...
send_audio
Author | SHA1 | Date |
---|---|---|
|
95969ffcc8 | |
|
756e782ef1 | |
|
dcb81f9915 | |
|
b34a1956ee | |
|
f00ed230a0 | |
|
af0a29f3dc | |
|
d910833a9c | |
|
3157894c1a | |
|
ad48b2e55a | |
|
0bee448294 | |
|
df30aa53c9 | |
|
815956c01c | |
|
3fbe05cf92 | |
|
5c5228c3b2 | |
|
6cb5d60eb5 | |
|
a6e6f7fdde | |
|
e7fc174bc7 | |
|
9ef5758116 | |
|
29f14acd5f | |
|
7c11d681f9 | |
|
e089162220 | |
|
fe9c640c5b | |
|
b04c665ac6 | |
|
0042f506c2 | |
|
088f373770 | |
|
6f24ab5105 | |
|
847aad603e | |
|
f3fbcf94f7 | |
|
80fe99160f | |
|
c668b06ef8 | |
|
a98aa02dce | |
|
c1130200b8 | |
|
c58d016357 | |
|
8a2389bf68 | |
|
b9a981b57a | |
|
f53fc89531 | |
|
c8c3a25fe9 | |
|
418654abf4 | |
|
715687e85c | |
|
6936dfd292 |
2
build.sh
2
build.sh
|
@ -1,4 +1,4 @@
|
||||||
g++ -shared -fPIC \
|
g++ -DBOOST_DEBUG_PYTHON -shared -fPIC \
|
||||||
-I/usr/include/python3.10 -I/usr/include/python3.10/numpy -I./include \
|
-I/usr/include/python3.10 -I/usr/include/python3.10/numpy -I./include \
|
||||||
-L./lib -L/usr/lib/x86_64-linux-gnu \
|
-L./lib -L/usr/lib/x86_64-linux-gnu \
|
||||||
-DRTC_NUMPY_IMPL \
|
-DRTC_NUMPY_IMPL \
|
||||||
|
|
|
@ -39,15 +39,29 @@ int initRecv(const char* destRoomId, const char* srcUserId, const int16_t destCh
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int initSend(const char* srcRoomId, const char* destRoomId, const int16_t destChannelIndex) {
|
int initSend(const char* srcRoomId, const char* destRoomId, const int16_t destChannelIndex, const int16_t channelNum) {
|
||||||
bool res = RTCContext::instance().initSend(srcRoomId, destRoomId, destChannelIndex);
|
bool res = RTCContext::instance().initSend(srcRoomId, destRoomId, destChannelIndex, channelNum);
|
||||||
if (res) {
|
if (res) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
int getSize() {
|
||||||
|
return RTCContext::instance().getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace bp = boost::python;
|
||||||
|
namespace np = boost::python::numpy;
|
||||||
|
np::ndarray getNumpyData() {
|
||||||
|
return RTCContext::instance().getNumpyData();
|
||||||
|
}
|
||||||
|
bp::list getListData() {
|
||||||
|
return RTCContext::instance().getListData();
|
||||||
|
}
|
||||||
|
int16_t getDataCount() {
|
||||||
|
return RTCContext::instance().getDataCount();
|
||||||
|
}
|
||||||
py::object create_int16_array() {
|
py::object create_int16_array() {
|
||||||
// 1. 定义数组维度(1维,长度为 4)
|
// 1. 定义数组维度(1维,长度为 4)
|
||||||
npy_intp dims[1] = {4};
|
npy_intp dims[1] = {4};
|
||||||
|
@ -106,6 +120,9 @@ int sendCustomAudioData(int16_t destChannelIndex, py::object pD,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RetAudioFrame getData() {
|
||||||
|
return RTCContext::instance().getData();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
int sendCustomAudioData(const int16_t destChannelIndex, py::object pyData, int32_t sampleRate, uint64_t channelNum,
|
int sendCustomAudioData(const int16_t destChannelIndex, py::object pyData, int32_t sampleRate, uint64_t channelNum,
|
||||||
|
@ -196,6 +213,11 @@ BOOST_PYTHON_MODULE(rtc_plugins) {
|
||||||
py::def("initRecv", &initRecv);
|
py::def("initRecv", &initRecv);
|
||||||
py::def("initSend", &initSend);
|
py::def("initSend", &initSend);
|
||||||
py::def("sendCustomAudioData", &sendCustomAudioData);
|
py::def("sendCustomAudioData", &sendCustomAudioData);
|
||||||
|
py::def("getSize", &getSize);
|
||||||
|
py::def("getData", &getData);
|
||||||
|
py::def("getNumpyData", &getNumpyData);
|
||||||
|
py::def("getListData", &getListData);
|
||||||
|
py::def("getDataCount", &getDataCount);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Module initialization failed");
|
PyErr_SetString(PyExc_RuntimeError, "Module initialization failed");
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import rtc_plugins
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import wave
|
||||||
|
from scipy.io import wavfile
|
||||||
|
|
||||||
|
srcUserId = "srcUser12"
|
||||||
|
destUserId = "destUser12"
|
||||||
|
|
||||||
|
srcDisplayName = "srcDisplayName12"
|
||||||
|
destDisplayName = "destDisplayName12"
|
||||||
|
srcRoomId = "srcRoom12"
|
||||||
|
#destRoomId = "destRoomId12"
|
||||||
|
destRoomId = srcRoomId
|
||||||
|
srcChannelIndex = 46
|
||||||
|
destChannelIndex = 47
|
||||||
|
def my_callback(shmName, dataSize, dataCount, sampleRate, numChannels, channelIndex):
|
||||||
|
print(f"my_callback, dataSize:{dataSize}, dataCount:{dataCount}, sampleRate:{sampleRate}, numChannels:{numChannels}, channelIndex:{channelIndex}")
|
||||||
|
print(f"data:{shmName}")
|
||||||
|
print("after my_callback_r")
|
||||||
|
ret = rtc_plugins.init(srcUserId, srcDisplayName, srcRoomId, my_callback)
|
||||||
|
if ret != 0:
|
||||||
|
print(f"init fail, ret:{ret}")
|
||||||
|
exit(1)
|
||||||
|
ret = rtc_plugins.initSend(srcRoomId, destRoomId, destChannelIndex, 1)
|
||||||
|
if ret != 0:
|
||||||
|
print(f"initSend fail, ret:{ret}")
|
||||||
|
exit(1)
|
||||||
|
#audioData = np.array([0, 1, -1, 0], dtype=np.int16)
|
||||||
|
sampleRate, audioData = wavfile.read("xusample1.wav")
|
||||||
|
print(f"sampleRate:{sampleRate} HZ")
|
||||||
|
print(f"shape:{audioData.shape}")
|
||||||
|
print(f"type:{audioData.dtype}")
|
||||||
|
if audioData.dtype != np.int16:
|
||||||
|
audioData = (audioData * 32767).astype(np.int16)
|
||||||
|
ret = rtc_plugins.sendCustomAudioData(destChannelIndex, audioData, sampleRate, 1, len(audioData))
|
||||||
|
if ret != 0:
|
||||||
|
print(f"send fail, ret:{ret}")
|
||||||
|
ret = rtc_plugins.initRecv(srcRoomId, srcUserId, srcChannelIndex)
|
||||||
|
if ret != 0:
|
||||||
|
print(f"initRecv fail, ret:{ret}")
|
||||||
|
exit(1)
|
||||||
|
for i in range(100):
|
||||||
|
#ret = rtc_plugins.sendCustomAudioData(destChannelIndex, audioData, sampleRate, 1, len(audioData))
|
||||||
|
#if ret != 0:
|
||||||
|
# print(f"send fail, ret:{ret}")
|
||||||
|
|
||||||
|
#size = rtc_plugins.getSize()
|
||||||
|
#print(f"data size:{size}")
|
||||||
|
#frame = rtc_plugins.getListData()
|
||||||
|
#print(f"get frame:{frame}")
|
||||||
|
#dataCount = rtc_plugins.getDataCount()
|
||||||
|
#print(f"data count:{dataCount}")
|
||||||
|
time.sleep(3)
|
|
@ -4,6 +4,7 @@ import numpy as np
|
||||||
import mmap
|
import mmap
|
||||||
import os
|
import os
|
||||||
from ctypes import c_int16
|
from ctypes import c_int16
|
||||||
|
import struct
|
||||||
|
|
||||||
srcUserId = "srcUser12"
|
srcUserId = "srcUser12"
|
||||||
destUserId = "destUser12"
|
destUserId = "destUser12"
|
||||||
|
@ -15,26 +16,12 @@ srcRoomId = "srcRoom12"
|
||||||
destRoomId = srcRoomId
|
destRoomId = srcRoomId
|
||||||
srcChannelIndex = 46
|
srcChannelIndex = 46
|
||||||
destChannelIndex = 47
|
destChannelIndex = 47
|
||||||
def my_callback(shmName, dataSize, dataCount, sampleRate, numChannels, channelIndex):
|
def my_callback_r(shmName, dataSize, dataCount, sampleRate, numChannels, channelIndex):
|
||||||
print(f"dataSize:{dataSize}, dataCount:{dataCount}, sampleRate:{sampleRate}, numChannels:{numChannels}, channelIndex:{channelIndex}")
|
print(f"my_callback_r, dataSize:{dataSize}, dataCount:{dataCount}, sampleRate:{sampleRate}, numChannels:{numChannels}, channelIndex:{channelIndex}")
|
||||||
print(f"data:{shmName}")
|
print(f"data:{shmName}")
|
||||||
#fd = os.open(shmName, os.O_RDONLY)
|
print("after my_callback_r")
|
||||||
#if fd == -1:
|
|
||||||
# raise RuntimeError(f"无法打开共享内存 {shmName}")
|
|
||||||
|
|
||||||
## 2. 创建内存映射
|
ret = rtc_plugins.init(destUserId, destDisplayName, destRoomId, my_callback_r)
|
||||||
#shm = mmap.mmap(fd, dataSize, mmap.MAP_SHARED, mmap.PROT_READ)
|
|
||||||
|
|
||||||
## 3. 转换为numpy数组 (零拷贝)
|
|
||||||
#audio_data = np.frombuffer(shm, dtype=c_int16, count=dataCount)
|
|
||||||
#print(f" 前5个采样点: {audio_data[:5]}")
|
|
||||||
#audioData = np.array([0, 1, -1, 0], dtype=np.int16)
|
|
||||||
#ret = rtc_plugins.sendCustomAudioData(srcChannelIndex, audioData, 48000, 1, len(audioData))
|
|
||||||
#if ret != 0:
|
|
||||||
# print(f"resend fail, ret:{ret}")
|
|
||||||
#else:
|
|
||||||
# print("resend succ")
|
|
||||||
ret = rtc_plugins.init(destUserId, destDisplayName, destRoomId, my_callback)
|
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
print(f"init fail, ret:{ret}")
|
print(f"init fail, ret:{ret}")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@ -42,7 +29,7 @@ ret = rtc_plugins.initRecv(destRoomId, srcUserId, destChannelIndex)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
print(f"initRecv fail, ret:{ret}")
|
print(f"initRecv fail, ret:{ret}")
|
||||||
exit(1)
|
exit(1)
|
||||||
ret = rtc_plugins.initSend(destRoomId, srcRoomId, srcChannelIndex)
|
ret = rtc_plugins.initSend(destRoomId, srcRoomId, srcChannelIndex, 1)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
print(f"initSend fail, ret:{ret}")
|
print(f"initSend fail, ret:{ret}")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@ -55,4 +42,11 @@ while True:
|
||||||
print(f"resend fail, ret:{ret}")
|
print(f"resend fail, ret:{ret}")
|
||||||
else:
|
else:
|
||||||
print("resend succ")
|
print("resend succ")
|
||||||
time.sleep(30)
|
size = rtc_plugins.getSize()
|
||||||
|
print(f"data size:{size}")
|
||||||
|
#frame = rtc_plugins.getNumpyData()
|
||||||
|
frame = rtc_plugins.getListData()
|
||||||
|
print(f"get frame:{frame}")
|
||||||
|
dataCount = rtc_plugins.getDataCount()
|
||||||
|
print(f"data count:{dataCount}")
|
||||||
|
time.sleep(0.005)
|
||||||
|
|
|
@ -12,13 +12,15 @@ srcRoomId = "srcRoom12"
|
||||||
destRoomId = srcRoomId
|
destRoomId = srcRoomId
|
||||||
srcChannelIndex = 46
|
srcChannelIndex = 46
|
||||||
destChannelIndex = 47
|
destChannelIndex = 47
|
||||||
def my_callback(npData, dataCount, sampleRate, numChannels, channelIndex):
|
def my_callback(shmName, dataSize, dataCount, sampleRate, numChannels, channelIndex):
|
||||||
print(f"dataCount:{dataCount}, sampleRate:{sampleRate}, numChannels:{numChannels}, channelIndex:{channelIndex}")
|
print(f"my_callback, dataSize:{dataSize}, dataCount:{dataCount}, sampleRate:{sampleRate}, numChannels:{numChannels}, channelIndex:{channelIndex}")
|
||||||
|
print(f"data:{shmName}")
|
||||||
|
print("after my_callback_r")
|
||||||
ret = rtc_plugins.init(srcUserId, srcDisplayName, srcRoomId, my_callback)
|
ret = rtc_plugins.init(srcUserId, srcDisplayName, srcRoomId, my_callback)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
print(f"init fail, ret:{ret}")
|
print(f"init fail, ret:{ret}")
|
||||||
exit(1)
|
exit(1)
|
||||||
ret = rtc_plugins.initSend(srcRoomId, destRoomId, destChannelIndex)
|
ret = rtc_plugins.initSend(srcRoomId, destRoomId, destChannelIndex, 1)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
print(f"initSend fail, ret:{ret}")
|
print(f"initSend fail, ret:{ret}")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@ -34,4 +36,11 @@ for i in range(100):
|
||||||
ret = rtc_plugins.sendCustomAudioData(destChannelIndex, audioData, 48000, 1, len(audioData))
|
ret = rtc_plugins.sendCustomAudioData(destChannelIndex, audioData, 48000, 1, len(audioData))
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
print(f"send fail, ret:{ret}")
|
print(f"send fail, ret:{ret}")
|
||||||
time.sleep(30)
|
|
||||||
|
size = rtc_plugins.getSize()
|
||||||
|
print(f"data size:{size}")
|
||||||
|
frame = rtc_plugins.getListData()
|
||||||
|
print(f"get frame:{frame}")
|
||||||
|
dataCount = rtc_plugins.getDataCount()
|
||||||
|
print(f"data count:{dataCount}")
|
||||||
|
time.sleep(3)
|
||||||
|
|
|
@ -7,10 +7,30 @@ void RTCContext::onRoom(uint32_t typeId, RTCENGINE_NAMESPACE::MRTCRoomInfo& room
|
||||||
isOnRoom_ = true;
|
isOnRoom_ = true;
|
||||||
}
|
}
|
||||||
void RTCContext::onConsumer(uint32_t msgId, const char* roomId, const char* peerId, RTCENGINE_NAMESPACE::MRTCConsumerInfo& consumerInfo) {
|
void RTCContext::onConsumer(uint32_t msgId, const char* roomId, const char* peerId, RTCENGINE_NAMESPACE::MRTCConsumerInfo& consumerInfo) {
|
||||||
std::cout << "RTCContext::onConsumer():" << consumerInfo.roomId << "," << consumerInfo.displayName << "," << consumerInfo.channelIndex;
|
//std::cout << "RTCContext::onConsumer()" << std::endl;
|
||||||
|
std::cout << "RTCContext::onConsumer():msgId:" << msgId << ", roomId:" << consumerInfo.roomId << ", displayName:"
|
||||||
|
<< consumerInfo.displayName << ", channelIndex" << (int)consumerInfo.channelIndex;
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
isOnConsumer_ = true;
|
isOnConsumer_ = true;
|
||||||
//std::cout << "RTCContext::onConsumer()" << std::endl;
|
std::cout << "registerSoundLevelListener" << std::endl;
|
||||||
|
int16_t ret1 = rtcEngine_->registerSoundLevelListener(mrtc::TYPE_AUDIO_SOURCE_CUSTOM, roomId,
|
||||||
|
peerId, consumerInfo.channelIndex, this);
|
||||||
|
if (0 != ret1)
|
||||||
|
{
|
||||||
|
std::cout << "RTCContext::instance().registerSoundLevelListener() inUser failed, ret:" << ret1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "muteAudio" << std::endl;
|
||||||
|
int16_t ret2 = rtcEngine_->muteAudio(roomId, peerId, mrtc::TYPE_AUDIO_SOURCE_CUSTOM,
|
||||||
|
false, consumerInfo.channelIndex);
|
||||||
|
if (0 != ret2)
|
||||||
|
{
|
||||||
|
std::cout << "RTCContext::instance().muteAudio() failed, ret:" << ret2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "init recv succ" << std::endl;
|
||||||
}
|
}
|
||||||
void RTCContext::onRender(const char* roomId, const char* peerId,
|
void RTCContext::onRender(const char* roomId, const char* peerId,
|
||||||
RTCENGINE_NAMESPACE::MRTCVideoSourceType sourceType, const RTCENGINE_NAMESPACE::MRTCVideoFrame& videoFrame) {
|
RTCENGINE_NAMESPACE::MRTCVideoSourceType sourceType, const RTCENGINE_NAMESPACE::MRTCVideoFrame& videoFrame) {
|
||||||
|
@ -272,172 +292,191 @@ void RTCContext::onAudioProcess(const char* roomId, const char* peerId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
void printTimestamp() {
|
||||||
|
// 获取系统当前时间点
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
|
||||||
|
// 转换为时间戳(秒 + 毫秒)
|
||||||
|
auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
now.time_since_epoch()).count();
|
||||||
|
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
now.time_since_epoch()).count() % 1000;
|
||||||
|
|
||||||
|
// 转换为本地时间(可读格式)
|
||||||
|
std::time_t time = std::chrono::system_clock::to_time_t(now);
|
||||||
|
std::cout << "Timestamp: " << timestamp << "." << milliseconds << std::endl;
|
||||||
|
}
|
||||||
void RTCContext::onAudioProcess(const char* roomId, const char* peerId,
|
void RTCContext::onAudioProcess(const char* roomId, const char* peerId,
|
||||||
mrtc::MRTCAudioFrame& audioFrame,
|
mrtc::MRTCAudioFrame& audioFrame,
|
||||||
mrtc::MRTCAudioSourceType audioSourceType)
|
mrtc::MRTCAudioSourceType audioSourceType)
|
||||||
{
|
{
|
||||||
namespace py = boost::python;
|
//namespace py = boost::python;
|
||||||
std::cout << "=== 开始音频处理(共享内存版) ===" << std::endl;
|
//std::cout << "=== 开始音频处理(共享内存版) ===" << std::endl;
|
||||||
std::cout << "audioFrame:" << audioFrame.dataCount << "," << audioFrame.sampleRate << "," <<
|
//std::cout << "audioFrame:" << audioFrame.dataCount << "," << audioFrame.sampleRate << "," <<
|
||||||
audioFrame.numChannels << "," << audioFrame.channelIndex << std::endl;
|
// audioFrame.numChannels << "," << audioFrame.channelIndex << std::endl;
|
||||||
|
|
||||||
|
//printTimestamp();
|
||||||
|
setData(audioFrame);
|
||||||
// 1. 获取GIL
|
// 1. 获取GIL
|
||||||
std::cout << "[1] 获取GIL锁..." << std::endl;
|
//std::cout << "[1] 获取GIL锁..." << std::endl;
|
||||||
PyGILState_STATE gstate = PyGILState_Ensure();
|
////PyGILState_STATE gstate = PyGILState_Ensure();
|
||||||
|
|
||||||
try {
|
//try {
|
||||||
// 2. 输入参数校验
|
// // 2. 输入参数校验
|
||||||
std::cout << "[2] 检查输入参数..." << std::endl;
|
// std::cout << "[2] 检查输入参数..." << std::endl;
|
||||||
std::cout << " dataCount: " << audioFrame.dataCount
|
// std::cout << " dataCount: " << audioFrame.dataCount
|
||||||
<< " (max: " << std::numeric_limits<npy_intp>::max() << ")" << std::endl;
|
// << " (max: " << std::numeric_limits<npy_intp>::max() << ")" << std::endl;
|
||||||
|
|
||||||
if (!audioFrame.data || audioFrame.dataCount <= 0) {
|
// if (!audioFrame.data || audioFrame.dataCount <= 0) {
|
||||||
std::cout << "[ERROR] 无效音频数据指针或长度" << std::endl;
|
// std::cout << "[ERROR] 无效音频数据指针或长度" << std::endl;
|
||||||
throw std::invalid_argument("Invalid audio frame data");
|
// throw std::invalid_argument("Invalid audio frame data");
|
||||||
}
|
// }
|
||||||
|
|
||||||
const size_t data_size = audioFrame.dataCount * sizeof(int16_t);
|
// const size_t data_size = audioFrame.dataCount * sizeof(int16_t);
|
||||||
|
|
||||||
// 3. 创建共享内存
|
// // 3. 创建共享内存
|
||||||
std::cout << "[3] 创建共享内存..." << std::endl;
|
// std::cout << "[3] 创建共享内存..." << std::endl;
|
||||||
char shm_name[32];
|
// char shm_name[32];
|
||||||
snprintf(shm_name, sizeof(shm_name), "/audio_shm_%d", getpid());
|
// //snprintf(shm_name, sizeof(shm_name), "/audio_shm_%d", getpid());
|
||||||
|
// snprintf(shm_name, sizeof(shm_name), "/audio_shm_test");
|
||||||
|
|
||||||
int fd = shm_open(shm_name, O_CREAT | O_RDWR, 0666);
|
// int fd = shm_open(shm_name, O_CREAT | O_RDWR, 0666);
|
||||||
if (fd == -1) {
|
// if (fd == -1) {
|
||||||
std::cout << "[ERROR] shm_open失败: " << strerror(errno) << std::endl;
|
// std::cout << "[ERROR] shm_open失败: " << strerror(errno) << std::endl;
|
||||||
throw std::runtime_error("Failed to create shared memory");
|
// throw std::runtime_error("Failed to create shared memory");
|
||||||
}
|
// }
|
||||||
std::cout << " 共享内存fd: " << fd << " 名称: " << shm_name << std::endl;
|
// std::cout << " 共享内存fd: " << fd << " 名称: " << shm_name << std::endl;
|
||||||
|
|
||||||
// 4. 设置共享内存大小
|
// // 4. 设置共享内存大小
|
||||||
std::cout << "[4] 设置共享内存大小..." << std::endl;
|
// std::cout << "[4] 设置共享内存大小..." << std::endl;
|
||||||
if (ftruncate(fd, data_size) == -1) {
|
// if (ftruncate(fd, data_size) == -1) {
|
||||||
close(fd);
|
// close(fd);
|
||||||
std::cout << "[ERROR] ftruncate失败: " << strerror(errno) << std::endl;
|
// std::cout << "[ERROR] ftruncate失败: " << strerror(errno) << std::endl;
|
||||||
throw std::runtime_error("Failed to resize shared memory");
|
// throw std::runtime_error("Failed to resize shared memory");
|
||||||
}
|
// }
|
||||||
std::cout << " 内存大小: " << data_size << " bytes" << std::endl;
|
// std::cout << " 内存大小: " << data_size << " bytes" << std::endl;
|
||||||
|
|
||||||
// 5. 内存映射
|
// // 5. 内存映射
|
||||||
std::cout << "[5] 内存映射..." << std::endl;
|
// std::cout << "[5] 内存映射..." << std::endl;
|
||||||
void* ptr = mmap(NULL, data_size, PROT_WRITE, MAP_SHARED, fd, 0);
|
// void* ptr = mmap(NULL, data_size, PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
if (ptr == MAP_FAILED) {
|
// if (ptr == MAP_FAILED) {
|
||||||
close(fd);
|
// close(fd);
|
||||||
std::cout << "[ERROR] mmap失败: " << strerror(errno) << std::endl;
|
// std::cout << "[ERROR] mmap失败: " << strerror(errno) << std::endl;
|
||||||
throw std::runtime_error("Failed to map shared memory");
|
// throw std::runtime_error("Failed to map shared memory");
|
||||||
}
|
// }
|
||||||
std::cout << " 映射地址: " << ptr << std::endl;
|
// std::cout << " 映射地址: " << ptr << std::endl;
|
||||||
|
|
||||||
namespace py = boost::python;
|
// namespace py = boost::python;
|
||||||
namespace np = boost::python::numpy;
|
// namespace np = boost::python::numpy;
|
||||||
// 6. 拷贝数据到共享内存
|
// // 6. 拷贝数据到共享内存
|
||||||
std::cout << "[6] 拷贝音频数据到共享内存..." << std::endl;
|
// std::cout << "[6] 拷贝音频数据到共享内存..." << std::endl;
|
||||||
memcpy(ptr, audioFrame.data, data_size);
|
// memcpy(ptr, audioFrame.data, data_size);
|
||||||
std::cout << "step1" << std::endl;
|
// std::cout << "step1" << std::endl;
|
||||||
npy_intp shape[1] = { static_cast<npy_intp>(audioFrame.dataCount) };
|
// /*
|
||||||
std::cout << "step2" << std::endl;
|
// npy_intp shape[1] = { static_cast<npy_intp>(audioFrame.dataCount) };
|
||||||
np::dtype dtype = np::dtype::get_builtin<int16_t>();
|
// std::cout << "step2" << std::endl;
|
||||||
std::cout << "step3" << std::endl;
|
// np::dtype dtype = np::dtype::get_builtin<int16_t>();
|
||||||
np::ndarray audioArray = np::from_data(
|
// std::cout << "step3" << std::endl;
|
||||||
audioFrame.data, // 数据指针
|
// np::ndarray audioArray = np::from_data(
|
||||||
dtype, // 数据类型 (int16)
|
// audioFrame.data, // 数据指针
|
||||||
py::make_tuple(shape[0]), // 形状 (1D)
|
// dtype, // 数据类型 (int16)
|
||||||
py::make_tuple(sizeof(int16_t)), // 步长
|
// py::make_tuple(shape[0]), // 形状 (1D)
|
||||||
py::object() // 所有者(Python管理)
|
// py::make_tuple(sizeof(int16_t)), // 步长
|
||||||
);
|
// py::object() // 所有者(Python管理)
|
||||||
std::cout << " 数据拷贝完成" << std::endl;
|
// );
|
||||||
|
// */
|
||||||
|
// std::cout << " 数据拷贝完成" << std::endl;
|
||||||
|
|
||||||
// 7. 执行回调
|
// // 7. 执行回调
|
||||||
if (!pyCallback_.is_none()) {
|
// //if (!pyCallback_.is_none()) {
|
||||||
std::cout << "[7] 准备执行Python回调..." << std::endl;
|
// // std::cout << "[7] 准备执行Python回调..." << std::endl;
|
||||||
// 增加引用计数防止提前释放
|
// // // 增加引用计数防止提前释放
|
||||||
Py_INCREF(pyCallback_.ptr());
|
// // Py_INCREF(pyCallback_.ptr());
|
||||||
try {
|
// // try {
|
||||||
std::cout << " pyCallback_ type: " << Py_TYPE(pyCallback_.ptr())->tp_name << std::endl;
|
// // std::cout << " pyCallback_ type: " << Py_TYPE(pyCallback_.ptr())->tp_name << std::endl;
|
||||||
PyObject* repr = PyObject_Repr(pyCallback_.ptr());
|
// // PyObject* repr = PyObject_Repr(pyCallback_.ptr());
|
||||||
if (repr) {
|
// // if (repr) {
|
||||||
std::cout << " pyCallback_ repr: " << PyUnicode_AsUTF8(repr) << std::endl;
|
// // std::cout << " pyCallback_ repr: " << PyUnicode_AsUTF8(repr) << std::endl;
|
||||||
Py_DECREF(repr); // 必须手动释放
|
// // Py_DECREF(repr); // 必须手动释放
|
||||||
}
|
// // }
|
||||||
/*
|
// // // 传递共享内存信息
|
||||||
// 传递共享内存信息
|
// // pyCallback_(
|
||||||
pyCallback_(
|
// // py::str(shm_name), // 共享内存名称
|
||||||
py::str(shm_name), // 共享内存名称
|
// // data_size, // 数据大小
|
||||||
data_size, // 数据大小
|
// // audioFrame.dataCount,
|
||||||
audioFrame.dataCount,
|
// // audioFrame.sampleRate,
|
||||||
audioFrame.sampleRate,
|
// // audioFrame.numChannels,
|
||||||
audioFrame.numChannels,
|
// // audioFrame.channelIndex
|
||||||
audioFrame.channelIndex
|
// // );
|
||||||
);
|
// // /*
|
||||||
*/
|
// // pyCallback_(
|
||||||
pyCallback_(
|
// // audioArray, // numpy 数组
|
||||||
audioArray, // numpy 数组
|
// // data_size, // 数据大小
|
||||||
data_size, // 数据大小
|
// // audioFrame.dataCount,
|
||||||
audioFrame.dataCount,
|
// // audioFrame.sampleRate,
|
||||||
audioFrame.sampleRate,
|
// // audioFrame.numChannels,
|
||||||
audioFrame.numChannels,
|
// // audioFrame.channelIndex
|
||||||
audioFrame.channelIndex
|
// // );
|
||||||
);
|
// // */
|
||||||
std::cout << " after callback" << std::endl;
|
// // std::cout << " after callback" << std::endl;
|
||||||
if (PyErr_Occurred()) {
|
// // if (PyErr_Occurred()) {
|
||||||
PyObject *type, *value, *traceback;
|
// // PyObject *type, *value, *traceback;
|
||||||
PyErr_Fetch(&type, &value, &traceback);
|
// // PyErr_Fetch(&type, &value, &traceback);
|
||||||
if (value) {
|
// // if (value) {
|
||||||
PyObject* str = PyObject_Str(value);
|
// // PyObject* str = PyObject_Str(value);
|
||||||
if (str) {
|
// // if (str) {
|
||||||
std::cerr << "Python Error: " << PyUnicode_AsUTF8(str) << std::endl;
|
// // std::cerr << "Python Error: " << PyUnicode_AsUTF8(str) << std::endl;
|
||||||
Py_DECREF(str);
|
// // Py_DECREF(str);
|
||||||
}
|
// // }
|
||||||
}
|
// // }
|
||||||
Py_XDECREF(type);
|
// // Py_XDECREF(type);
|
||||||
Py_XDECREF(value);
|
// // Py_XDECREF(value);
|
||||||
Py_XDECREF(traceback);
|
// // Py_XDECREF(traceback);
|
||||||
//PyErr_Print();
|
// // //PyErr_Print();
|
||||||
throw std::runtime_error("Python callback error");
|
// // throw std::runtime_error("Python callback error");
|
||||||
}
|
// // }
|
||||||
std::cout << " 回调执行成功" << std::endl;
|
// // std::cout << " 回调执行成功" << std::endl;
|
||||||
|
|
||||||
} catch (const py::error_already_set& e) {
|
// // } catch (const py::error_already_set& e) {
|
||||||
std::cerr << "[PYTHON ERROR] ";
|
// // std::cerr << "[PYTHON ERROR] ";
|
||||||
PyErr_Print(); // 自动打印到stderr
|
// // PyErr_Print(); // 自动打印到stderr
|
||||||
// 可选:获取更详细的错误信息
|
// // // 可选:获取更详细的错误信息
|
||||||
if (PyErr_Occurred()) {
|
// // if (PyErr_Occurred()) {
|
||||||
PyObject *type, *value, *traceback;
|
// // PyObject *type, *value, *traceback;
|
||||||
PyErr_Fetch(&type, &value, &traceback);
|
// // PyErr_Fetch(&type, &value, &traceback);
|
||||||
std::cerr << "Details: "
|
// // std::cerr << "Details: "
|
||||||
<< PyUnicode_AsUTF8(PyObject_Str(value)) << std::endl;
|
// // << PyUnicode_AsUTF8(PyObject_Str(value)) << std::endl;
|
||||||
PyErr_Restore(type, value, traceback);
|
// // PyErr_Restore(type, value, traceback);
|
||||||
}
|
// // }
|
||||||
Py_DECREF(pyCallback_.ptr());
|
// // Py_DECREF(pyCallback_.ptr());
|
||||||
} catch (...) {
|
// // } catch (...) {
|
||||||
std::cout << "[ERROR] 回调执行失败" << std::endl;
|
// // std::cout << "[ERROR] 回调执行失败" << std::endl;
|
||||||
munmap(ptr, data_size);
|
// // munmap(ptr, data_size);
|
||||||
close(fd);
|
// // close(fd);
|
||||||
shm_unlink(shm_name);
|
// // shm_unlink(shm_name);
|
||||||
Py_DECREF(pyCallback_.ptr());
|
// // Py_DECREF(pyCallback_.ptr());
|
||||||
throw;
|
// // throw;
|
||||||
}
|
// // }
|
||||||
Py_DECREF(pyCallback_.ptr());
|
// // Py_DECREF(pyCallback_.ptr());
|
||||||
} else {
|
// //} else {
|
||||||
std::cout << "[7] 无回调函数设置" << std::endl;
|
// // std::cout << "[7] 无回调函数设置" << std::endl;
|
||||||
}
|
// //}
|
||||||
|
|
||||||
// 8. 释放资源
|
// // 8. 释放资源
|
||||||
std::cout << "[8] 释放共享内存资源..." << std::endl;
|
// std::cout << "[8] 释放共享内存资源..." << std::endl;
|
||||||
munmap(ptr, data_size);
|
// munmap(ptr, data_size);
|
||||||
close(fd);
|
// close(fd);
|
||||||
shm_unlink(shm_name);
|
// shm_unlink(shm_name);
|
||||||
|
|
||||||
std::cout << "[9] 释放GIL..." << std::endl;
|
// std::cout << "[9] 释放GIL..." << std::endl;
|
||||||
PyGILState_Release(gstate);
|
// //PyGILState_Release(gstate);
|
||||||
std::cout << "=== 音频处理完成 ===" << std::endl;
|
// std::cout << "=== 音频处理完成 ===" << std::endl;
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
//} catch (const std::exception& e) {
|
||||||
std::cout << "[EXCEPTION] 异常捕获: " << e.what() << std::endl;
|
// std::cout << "[EXCEPTION] 异常捕获: " << e.what() << std::endl;
|
||||||
PyGILState_Release(gstate);
|
// //PyGILState_Release(gstate);
|
||||||
std::cerr << "Audio process error: " << e.what() << std::endl;
|
// std::cerr << "Audio process error: " << e.what() << std::endl;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RTCContext::onProducer(uint32_t msgId, mrtc::MRTCProducerInfo& info)
|
void RTCContext::onProducer(uint32_t msgId, mrtc::MRTCProducerInfo& info)
|
||||||
|
@ -518,6 +557,7 @@ bool RTCContext::initRecv(const char* destRoomId, const char* srcUserId, const i
|
||||||
std::cout << "wait for OnConsumer" << std::endl;
|
std::cout << "wait for OnConsumer" << std::endl;
|
||||||
sleep(3);
|
sleep(3);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
std::cout << "registerSoundLevelListener" << std::endl;
|
std::cout << "registerSoundLevelListener" << std::endl;
|
||||||
int16_t ret1 = rtcEngine_->registerSoundLevelListener(mrtc::TYPE_AUDIO_SOURCE_CUSTOM, destRoomId,
|
int16_t ret1 = rtcEngine_->registerSoundLevelListener(mrtc::TYPE_AUDIO_SOURCE_CUSTOM, destRoomId,
|
||||||
srcUserId, destChannelIndex, this);
|
srcUserId, destChannelIndex, this);
|
||||||
|
@ -536,10 +576,12 @@ bool RTCContext::initRecv(const char* destRoomId, const char* srcUserId, const i
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "init recv succ" << std::endl;
|
std::cout << "init recv succ" << std::endl;
|
||||||
|
*/
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
bool RTCContext::initSend(const char* srcRoomId, const char* destRoomId, const int16_t destChannelIndex)
|
bool RTCContext::initSend(const char* srcRoomId, const char* destRoomId, const int16_t destChannelIndex,
|
||||||
|
const uint8_t channelNum)
|
||||||
{
|
{
|
||||||
while (!isOnRoom_)
|
while (!isOnRoom_)
|
||||||
{
|
{
|
||||||
|
@ -564,6 +606,7 @@ bool RTCContext::initSend(const char* srcRoomId, const char* destRoomId, const i
|
||||||
strcpy(option.dstRoomId, destRoomId);
|
strcpy(option.dstRoomId, destRoomId);
|
||||||
}
|
}
|
||||||
option.channelIndex = destChannelIndex;
|
option.channelIndex = destChannelIndex;
|
||||||
|
option.channel = channelNum;
|
||||||
std::cout << "startCustomAudio" << std::endl;
|
std::cout << "startCustomAudio" << std::endl;
|
||||||
int16_t ret2 = rtcEngine_->startCustomAudio(option);
|
int16_t ret2 = rtcEngine_->startCustomAudio(option);
|
||||||
if (ret2 != 0)
|
if (ret2 != 0)
|
||||||
|
@ -627,3 +670,87 @@ void RTCContext::setNumpyApi(void **numpyApi) {
|
||||||
numpyApi_ = numpyApi;
|
numpyApi_ = numpyApi;
|
||||||
std::cout << "setNupyApi, numpyApi_:" << numpyApi_[93] << std::endl;
|
std::cout << "setNupyApi, numpyApi_:" << numpyApi_[93] << std::endl;
|
||||||
}
|
}
|
||||||
|
void RTCContext::setData(const mrtc::MRTCAudioFrame& frame) {
|
||||||
|
std::lock_guard<std::mutex> lock(dataMutex_);
|
||||||
|
if (dataSize_ == totalSize_) {
|
||||||
|
bottom_ = (bottom_ + 1) % totalSize_;
|
||||||
|
dataSize_--;
|
||||||
|
}
|
||||||
|
RetAudioFrame newFrame;
|
||||||
|
newFrame.dataCount = frame.dataCount;
|
||||||
|
newFrame.sampleRate = frame.sampleRate;
|
||||||
|
newFrame.numChannels = frame.numChannels;
|
||||||
|
newFrame.channelIndex = frame.channelIndex;
|
||||||
|
newFrame.data = std::make_unique<int16_t[]>(frame.dataCount);
|
||||||
|
std::memcpy(newFrame.data.get(), frame.data, frame.dataCount* sizeof(int16_t));
|
||||||
|
data_[head_] = std::move(newFrame);
|
||||||
|
head_ = (head_ + 1) % totalSize_;
|
||||||
|
dataSize_++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RetAudioFrame RTCContext::getData() {
|
||||||
|
//std::lock_guard<std::mutex> lock(dataMutex_);
|
||||||
|
if (dataSize_ > 0) {
|
||||||
|
RetAudioFrame frame = std::move(data_[bottom_]); // 移动而非拷贝
|
||||||
|
bottom_ = (bottom_ + 1) % totalSize_;
|
||||||
|
dataSize_--;
|
||||||
|
return frame; // 返回值优化(RVO)会生效
|
||||||
|
}
|
||||||
|
return {}; // 返回空对象
|
||||||
|
}
|
||||||
|
namespace bp = boost::python;
|
||||||
|
namespace np = boost::python::numpy;
|
||||||
|
np::ndarray RTCContext::getNumpyData() {
|
||||||
|
std::cout << "step1" << std::endl;
|
||||||
|
std::lock_guard<std::mutex> lock(dataMutex_);
|
||||||
|
RetAudioFrame frame = getData();
|
||||||
|
std::cout << "step2" << std::endl;
|
||||||
|
int16_t* dataPtr = frame.data.get(); // 你的数据指针
|
||||||
|
std::cout << "step3" << std::endl;
|
||||||
|
size_t length = frame.dataCount; // 数据长度
|
||||||
|
std::cout << "step4" << std::endl;
|
||||||
|
|
||||||
|
PyGILState_STATE gstate = PyGILState_Ensure();
|
||||||
|
np::ndarray result = np::empty(bp::make_tuple(length), np::dtype::get_builtin<int16_t>());
|
||||||
|
try {
|
||||||
|
if (!dataPtr || length == 0) {
|
||||||
|
result = np::zeros(bp::make_tuple(0), np::dtype::get_builtin<int16_t>());
|
||||||
|
} else {
|
||||||
|
result = np::empty(bp::make_tuple(length), np::dtype::get_builtin<int16_t>());
|
||||||
|
std::memcpy(result.get_data(), dataPtr, length * sizeof(int16_t));
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
PyGILState_Release(gstate); // 异常时释放GIL
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
bp::list RTCContext::getListData() {
|
||||||
|
std::cout << "step1" << std::endl;
|
||||||
|
std::lock_guard<std::mutex> lock(dataMutex_);
|
||||||
|
RetAudioFrame frame = getData();
|
||||||
|
std::cout << "step2" << std::endl;
|
||||||
|
int16_t* dataPtr = frame.data.get(); // 你的数据指针
|
||||||
|
std::cout << "step3" << std::endl;
|
||||||
|
size_t length = frame.dataCount; // 数据长度
|
||||||
|
std::cout << "step4" << std::endl;
|
||||||
|
bp::list result;
|
||||||
|
if (dataPtr && length > 0) {
|
||||||
|
for (size_t i = 0; i < length; ++i) {
|
||||||
|
result.append(dataPtr[i]); // 逐个元素添加(值传递)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int16_t RTCContext::getDataCount() {
|
||||||
|
std::lock_guard<std::mutex> lock(dataMutex_);
|
||||||
|
RetAudioFrame frame = getData();
|
||||||
|
return frame.dataCount;
|
||||||
|
}
|
||||||
|
int16_t RTCContext::getSize() {
|
||||||
|
std::lock_guard<std::mutex> lock(dataMutex_);
|
||||||
|
return dataSize_;
|
||||||
|
}
|
|
@ -31,10 +31,22 @@
|
||||||
// 必须声明外部变量(关键!)
|
// 必须声明外部变量(关键!)
|
||||||
//#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
//#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
||||||
|
|
||||||
|
namespace bp = boost::python;
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
namespace np = boost::python::numpy;
|
||||||
#define ENV_PRODUCT
|
#define ENV_PRODUCT
|
||||||
//#define SEND_MODE
|
//#define SEND_MODE
|
||||||
|
|
||||||
|
// 音频数据帧
|
||||||
|
struct RetAudioFrame final
|
||||||
|
{
|
||||||
|
std::unique_ptr<int16_t[]> data;
|
||||||
|
int dataCount = 0;
|
||||||
|
int sampleRate = 48000;
|
||||||
|
int numChannels = 1;
|
||||||
|
int channelIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class RTCContext :
|
class RTCContext :
|
||||||
public RTCENGINE_NAMESPACE::IMRTCRoomCallBack,
|
public RTCENGINE_NAMESPACE::IMRTCRoomCallBack,
|
||||||
public RTCENGINE_NAMESPACE::IMRTCConsumerCallBack,
|
public RTCENGINE_NAMESPACE::IMRTCConsumerCallBack,
|
||||||
|
@ -84,7 +96,13 @@ public:
|
||||||
mrtc::IMRTCEngine* getRtcEngine() const;
|
mrtc::IMRTCEngine* getRtcEngine() const;
|
||||||
bool init(const char* selfUserId, const char* selfDisplayName, const char* selfRoomId);
|
bool init(const char* selfUserId, const char* selfDisplayName, const char* selfRoomId);
|
||||||
bool initRecv(const char* destRoomId, const char* srcUserId, const int16_t destChannelIndex);
|
bool initRecv(const char* destRoomId, const char* srcUserId, const int16_t destChannelIndex);
|
||||||
bool initSend(const char* srcRoomId, const char* destRoomId, const int16_t destChannelIndex);
|
bool initSend(const char* srcRoomId, const char* destRoomId, const int16_t destChannelIndex, const uint8_t channelNum);
|
||||||
|
int16_t getSize();
|
||||||
|
void setData(const mrtc::MRTCAudioFrame& frame);
|
||||||
|
RetAudioFrame getData();
|
||||||
|
np::ndarray getNumpyData();
|
||||||
|
bp::list getListData();
|
||||||
|
int16_t getDataCount();
|
||||||
|
|
||||||
void* getpData() const;
|
void* getpData() const;
|
||||||
void setpData(void* pData);
|
void setpData(void* pData);
|
||||||
|
@ -99,6 +117,7 @@ public:
|
||||||
private:
|
private:
|
||||||
RTCContext()
|
RTCContext()
|
||||||
{
|
{
|
||||||
|
data_.resize(totalSize_);
|
||||||
}
|
}
|
||||||
mutable std::mutex mutex_;
|
mutable std::mutex mutex_;
|
||||||
mrtc::IMRTCEngine * rtcEngine_ = nullptr;
|
mrtc::IMRTCEngine * rtcEngine_ = nullptr;
|
||||||
|
@ -109,6 +128,12 @@ private:
|
||||||
bool isMultiRoom_ = false;
|
bool isMultiRoom_ = false;
|
||||||
boost::python::object pyCallback_;
|
boost::python::object pyCallback_;
|
||||||
void ** numpyApi_;
|
void ** numpyApi_;
|
||||||
|
std::vector<RetAudioFrame> data_;
|
||||||
|
mutable std::mutex dataMutex_;
|
||||||
|
const int16_t totalSize_ = 100;
|
||||||
|
int16_t dataSize_ = 0;
|
||||||
|
int16_t bottom_ = 0;
|
||||||
|
int16_t head_= 0;
|
||||||
void onRoom(uint32_t typeId, RTCENGINE_NAMESPACE::MRTCRoomInfo& roomInfo);
|
void onRoom(uint32_t typeId, RTCENGINE_NAMESPACE::MRTCRoomInfo& roomInfo);
|
||||||
void onConsumer(uint32_t msgId, const char* roomId, const char* peerId, RTCENGINE_NAMESPACE::MRTCConsumerInfo& consumerInfo);
|
void onConsumer(uint32_t msgId, const char* roomId, const char* peerId, RTCENGINE_NAMESPACE::MRTCConsumerInfo& consumerInfo);
|
||||||
void onRender(const char* roomId, const char* peerId,
|
void onRender(const char* roomId, const char* peerId,
|
||||||
|
|
Loading…
Reference in New Issue