Switched from WebRTC wrappers to stl in ChannelManager.
BUG=C-10187 Review URL: https://webrtc-codereview.appspot.com/357001 git-svn-id: http://webrtc.googlecode.com/svn/trunk@1524 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
eeaf3d1fc1
commit
a5a5cbb992
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by a BSD-style license
|
* Use of this source code is governed by a BSD-style license
|
||||||
* that can be found in the LICENSE file in the root of the source
|
* that can be found in the LICENSE file in the root of the source
|
||||||
@ -14,6 +14,7 @@
|
|||||||
#include "modules/rtp_rtcp/interface/rtp_rtcp.h"
|
#include "modules/rtp_rtcp/interface/rtp_rtcp.h"
|
||||||
#include "modules/utility/interface/process_thread.h"
|
#include "modules/utility/interface/process_thread.h"
|
||||||
#include "system_wrappers/interface/critical_section_wrapper.h"
|
#include "system_wrappers/interface/critical_section_wrapper.h"
|
||||||
|
#include "system_wrappers/interface/map_wrapper.h"
|
||||||
#include "system_wrappers/interface/trace.h"
|
#include "system_wrappers/interface/trace.h"
|
||||||
#include "video_engine/vie_channel.h"
|
#include "video_engine/vie_channel.h"
|
||||||
#include "video_engine/vie_defines.h"
|
#include "video_engine/vie_defines.h"
|
||||||
@ -50,11 +51,11 @@ ViEChannelManager::~ViEChannelManager() {
|
|||||||
"ViEChannelManager Destructor, engine_id: %d", engine_id_);
|
"ViEChannelManager Destructor, engine_id: %d", engine_id_);
|
||||||
|
|
||||||
module_process_thread_->DeRegisterModule(remb_.get());
|
module_process_thread_->DeRegisterModule(remb_.get());
|
||||||
while (channel_map_.Size() != 0) {
|
ChannelMap::iterator it = channel_map_.begin();
|
||||||
MapItem* item = channel_map_.First();
|
while (it != channel_map_.end()) {
|
||||||
const int channel_id = item->GetId();
|
DeleteChannel(it->first);
|
||||||
item = NULL;
|
channel_map_.erase(it);
|
||||||
DeleteChannel(channel_id);
|
it = channel_map_.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (voice_sync_interface_) {
|
if (voice_sync_interface_) {
|
||||||
@ -84,7 +85,7 @@ int ViEChannelManager::CreateChannel(int& channel_id) {
|
|||||||
// Get a free id for the new channel.
|
// Get a free id for the new channel.
|
||||||
if (!GetFreeChannelId(channel_id)) {
|
if (!GetFreeChannelId(channel_id)) {
|
||||||
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
||||||
"Max number of channels reached: %d", channel_map_.Size());
|
"Max number of channels reached: %d", channel_map_.size());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,22 +117,15 @@ int ViEChannelManager::CreateChannel(int& channel_id) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vie_encoder_map_.Insert(channel_id, vie_encoder) != 0) {
|
vie_encoder_map_[channel_id] = vie_encoder;
|
||||||
// Could not add to the map.
|
channel_map_[channel_id] = vie_channel;
|
||||||
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
|
||||||
"%s: Could not add new encoder for video channel %d",
|
|
||||||
__FUNCTION__, channel_id);
|
|
||||||
delete vie_channel;
|
|
||||||
delete vie_encoder;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
channel_map_.Insert(channel_id, vie_channel);
|
|
||||||
// Register the channel at the encoder.
|
// Register the channel at the encoder.
|
||||||
RtpRtcp* send_rtp_rtcp_module = vie_encoder->SendRtpRtcpModule();
|
RtpRtcp* send_rtp_rtcp_module = vie_encoder->SendRtpRtcpModule();
|
||||||
if (vie_channel->RegisterSendRtpRtcpModule(*send_rtp_rtcp_module) != 0) {
|
if (vie_channel->RegisterSendRtpRtcpModule(*send_rtp_rtcp_module) != 0) {
|
||||||
assert(false);
|
assert(false);
|
||||||
vie_encoder_map_.Erase(channel_id);
|
vie_encoder_map_.erase(channel_id);
|
||||||
channel_map_.Erase(channel_id);
|
channel_map_.erase(channel_id);
|
||||||
ReturnChannelId(channel_id);
|
ReturnChannelId(channel_id);
|
||||||
delete vie_channel;
|
delete vie_channel;
|
||||||
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, channel_id),
|
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, channel_id),
|
||||||
@ -167,7 +161,7 @@ int ViEChannelManager::CreateChannel(int& channel_id, int original_channel) {
|
|||||||
// Get a free id for the new channel.
|
// Get a free id for the new channel.
|
||||||
if (GetFreeChannelId(channel_id) == false) {
|
if (GetFreeChannelId(channel_id) == false) {
|
||||||
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
||||||
"Max number of channels reached: %d", channel_map_.Size());
|
"Max number of channels reached: %d", channel_map_.size());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
ViEChannel* vie_channel = new ViEChannel(channel_id, engine_id_,
|
ViEChannel* vie_channel = new ViEChannel(channel_id, engine_id_,
|
||||||
@ -185,14 +179,7 @@ int ViEChannelManager::CreateChannel(int& channel_id, int original_channel) {
|
|||||||
vie_channel = NULL;
|
vie_channel = NULL;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (vie_encoder_map_.Insert(channel_id, vie_encoder) != 0) {
|
vie_encoder_map_[channel_id] = vie_encoder;
|
||||||
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
|
||||||
"%s: Could not add new encoder for video channel %d",
|
|
||||||
__FUNCTION__, channel_id);
|
|
||||||
ReturnChannelId(channel_id);
|
|
||||||
delete vie_channel;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the same encoder settings for the channel as used by the master
|
// Set the same encoder settings for the channel as used by the master
|
||||||
// channel. Do this before attaching rtp module to ensure all rtp children has
|
// channel. Do this before attaching rtp module to ensure all rtp children has
|
||||||
@ -201,14 +188,14 @@ int ViEChannelManager::CreateChannel(int& channel_id, int original_channel) {
|
|||||||
if (vie_encoder->GetEncoder(encoder) == 0) {
|
if (vie_encoder->GetEncoder(encoder) == 0) {
|
||||||
vie_channel->SetSendCodec(encoder);
|
vie_channel->SetSendCodec(encoder);
|
||||||
}
|
}
|
||||||
channel_map_.Insert(channel_id, vie_channel);
|
channel_map_[channel_id] = vie_channel;
|
||||||
|
|
||||||
// Register the channel at the encoder.
|
// Register the channel at the encoder.
|
||||||
RtpRtcp* send_rtp_rtcp_module = vie_encoder->SendRtpRtcpModule();
|
RtpRtcp* send_rtp_rtcp_module = vie_encoder->SendRtpRtcpModule();
|
||||||
if (vie_channel->RegisterSendRtpRtcpModule(*send_rtp_rtcp_module) != 0) {
|
if (vie_channel->RegisterSendRtpRtcpModule(*send_rtp_rtcp_module) != 0) {
|
||||||
assert(false);
|
assert(false);
|
||||||
vie_encoder_map_.Erase(channel_id);
|
vie_encoder_map_.erase(channel_id);
|
||||||
channel_map_.Erase(channel_id);
|
channel_map_.erase(channel_id);
|
||||||
ReturnChannelId(channel_id);
|
ReturnChannelId(channel_id);
|
||||||
delete vie_channel;
|
delete vie_channel;
|
||||||
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, channel_id),
|
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, channel_id),
|
||||||
@ -228,16 +215,15 @@ int ViEChannelManager::DeleteChannel(int channel_id) {
|
|||||||
|
|
||||||
// Protect the map.
|
// Protect the map.
|
||||||
CriticalSectionScoped cs(*channel_id_critsect_);
|
CriticalSectionScoped cs(*channel_id_critsect_);
|
||||||
|
ChannelMap::iterator c_it = channel_map_.find(channel_id);
|
||||||
MapItem* map_item = channel_map_.Find(channel_id);
|
if (c_it == channel_map_.end()) {
|
||||||
if (!map_item) {
|
|
||||||
// No such channel.
|
// No such channel.
|
||||||
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
||||||
"%s Channel doesn't exist: %d", __FUNCTION__, channel_id);
|
"%s Channel doesn't exist: %d", __FUNCTION__, channel_id);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
vie_channel = reinterpret_cast<ViEChannel*>(map_item->GetItem());
|
vie_channel = c_it->second;
|
||||||
channel_map_.Erase(map_item);
|
channel_map_.erase(c_it);
|
||||||
|
|
||||||
// Deregister possible remb modules.
|
// Deregister possible remb modules.
|
||||||
RtpRtcp* rtp_module = vie_channel->rtp_rtcp();
|
RtpRtcp* rtp_module = vie_channel->rtp_rtcp();
|
||||||
@ -249,17 +235,9 @@ int ViEChannelManager::DeleteChannel(int channel_id) {
|
|||||||
ReturnChannelId(channel_id);
|
ReturnChannelId(channel_id);
|
||||||
|
|
||||||
// Find the encoder object.
|
// Find the encoder object.
|
||||||
map_item = vie_encoder_map_.Find(channel_id);
|
EncoderMap::iterator e_it = vie_encoder_map_.find(channel_id);
|
||||||
if (!map_item) {
|
assert(e_it != vie_encoder_map_.end());
|
||||||
assert(false);
|
vie_encoder = e_it->second;
|
||||||
WEBRTC_TRACE(kTraceInfo, kTraceVideo, ViEId(engine_id_),
|
|
||||||
"%s ViEEncoder not found for channel %d", __FUNCTION__,
|
|
||||||
channel_id);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the ViEEncoder item.
|
|
||||||
vie_encoder = reinterpret_cast<ViEEncoder*>(map_item->GetItem());
|
|
||||||
|
|
||||||
// Check if other channels are using the same encoder.
|
// Check if other channels are using the same encoder.
|
||||||
if (ChannelUsingViEEncoder(channel_id)) {
|
if (ChannelUsingViEEncoder(channel_id)) {
|
||||||
@ -277,7 +255,7 @@ int ViEChannelManager::DeleteChannel(int channel_id) {
|
|||||||
|
|
||||||
// We can't erase the item before we've checked for other channels using
|
// We can't erase the item before we've checked for other channels using
|
||||||
// same ViEEncoder.
|
// same ViEEncoder.
|
||||||
vie_encoder_map_.Erase(map_item);
|
vie_encoder_map_.erase(e_it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leave the write critsect before deleting the objects.
|
// Leave the write critsect before deleting the objects.
|
||||||
@ -311,11 +289,9 @@ int ViEChannelManager::SetVoiceEngine(VoiceEngine* voice_engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (MapItem* item = channel_map_.First(); item != NULL;
|
for (ChannelMap::iterator it = channel_map_.begin(); it != channel_map_.end();
|
||||||
item = channel_map_.Next(item)) {
|
++it) {
|
||||||
ViEChannel* channel = static_cast<ViEChannel*>(item->GetItem());
|
it->second->SetVoiceChannel(-1, sync_interface);
|
||||||
assert(channel);
|
|
||||||
channel->SetVoiceChannel(-1, sync_interface);
|
|
||||||
}
|
}
|
||||||
if (voice_sync_interface_) {
|
if (voice_sync_interface_) {
|
||||||
voice_sync_interface_->Release();
|
voice_sync_interface_->Release();
|
||||||
@ -388,15 +364,13 @@ bool ViEChannelManager::SetRembStatus(int channel_id, bool sender,
|
|||||||
|
|
||||||
ViEChannel* ViEChannelManager::ViEChannelPtr(int channel_id) const {
|
ViEChannel* ViEChannelManager::ViEChannelPtr(int channel_id) const {
|
||||||
CriticalSectionScoped cs(*channel_id_critsect_);
|
CriticalSectionScoped cs(*channel_id_critsect_);
|
||||||
MapItem* map_item = channel_map_.Find(channel_id);
|
ChannelMap::const_iterator it = channel_map_.find(channel_id);
|
||||||
if (!map_item) {
|
if (it == channel_map_.end()) {
|
||||||
// No such channel.
|
|
||||||
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_),
|
||||||
"%s Channel doesn't exist: %d", __FUNCTION__, channel_id);
|
"%s Channel doesn't exist: %d", __FUNCTION__, channel_id);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
ViEChannel* vie_channel = reinterpret_cast<ViEChannel*>(map_item->GetItem());
|
return it->second;
|
||||||
return vie_channel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViEChannelManager::GetViEChannels(MapWrapper& channel_map) {
|
void ViEChannelManager::GetViEChannels(MapWrapper& channel_map) {
|
||||||
@ -406,21 +380,19 @@ void ViEChannelManager::GetViEChannels(MapWrapper& channel_map) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add all items to 'channelMap'.
|
// Add all items to 'channelMap'.
|
||||||
for (MapItem* item = channel_map_.First(); item != NULL;
|
for (ChannelMap::iterator it = channel_map_.begin(); it != channel_map_.end();
|
||||||
item = channel_map_.Next(item)) {
|
++it) {
|
||||||
channel_map.Insert(item->GetId(), item->GetItem());
|
channel_map.Insert(it->first, it->second);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ViEEncoder* ViEChannelManager::ViEEncoderPtr(int video_channel_id) const {
|
ViEEncoder* ViEChannelManager::ViEEncoderPtr(int video_channel_id) const {
|
||||||
CriticalSectionScoped cs(*channel_id_critsect_);
|
CriticalSectionScoped cs(*channel_id_critsect_);
|
||||||
MapItem* map_item = vie_encoder_map_.Find(video_channel_id);
|
EncoderMap::const_iterator it = vie_encoder_map_.find(video_channel_id);
|
||||||
if (!map_item) {
|
if (it == vie_encoder_map_.end()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
ViEEncoder* vie_encoder = static_cast<ViEEncoder*>(map_item->GetItem());
|
return it->second;
|
||||||
return vie_encoder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ViEChannelManager::GetFreeChannelId(int& free_channel_id) {
|
bool ViEChannelManager::GetFreeChannelId(int& free_channel_id) {
|
||||||
@ -449,24 +421,22 @@ void ViEChannelManager::ReturnChannelId(int channel_id) {
|
|||||||
|
|
||||||
bool ViEChannelManager::ChannelUsingViEEncoder(int channel_id) const {
|
bool ViEChannelManager::ChannelUsingViEEncoder(int channel_id) const {
|
||||||
CriticalSectionScoped cs(*channel_id_critsect_);
|
CriticalSectionScoped cs(*channel_id_critsect_);
|
||||||
MapItem* channel_item = vie_encoder_map_.Find(channel_id);
|
EncoderMap::const_iterator orig_it = vie_encoder_map_.find(channel_id);
|
||||||
if (!channel_item) {
|
if(orig_it == vie_encoder_map_.end()) {
|
||||||
// No ViEEncoder for this channel.
|
// No ViEEncoder for this channel.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ViEEncoder* channel_encoder =
|
|
||||||
static_cast<ViEEncoder*>(channel_item->GetItem());
|
|
||||||
|
|
||||||
// Loop through all other channels to see if anyone points at the same
|
// Loop through all other channels to see if anyone points at the same
|
||||||
// ViEEncoder.
|
// ViEEncoder.
|
||||||
MapItem* map_item = vie_encoder_map_.First();
|
for (EncoderMap::const_iterator comp_it = vie_encoder_map_.begin();
|
||||||
while (map_item) {
|
comp_it != vie_encoder_map_.end(); ++comp_it) {
|
||||||
if (map_item->GetId() != channel_id) {
|
// Make sure we're not comparing the same channel with itself.
|
||||||
if (channel_encoder == static_cast<ViEEncoder*>(map_item->GetItem())) {
|
if (comp_it->first != channel_id) {
|
||||||
|
if (comp_it->second == orig_it->second) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
map_item = vie_encoder_map_.Next(map_item);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -474,21 +444,15 @@ bool ViEChannelManager::ChannelUsingViEEncoder(int channel_id) const {
|
|||||||
void ViEChannelManager::ChannelsUsingViEEncoder(int channel_id,
|
void ViEChannelManager::ChannelsUsingViEEncoder(int channel_id,
|
||||||
ChannelList* channels) const {
|
ChannelList* channels) const {
|
||||||
CriticalSectionScoped cs(*channel_id_critsect_);
|
CriticalSectionScoped cs(*channel_id_critsect_);
|
||||||
MapItem* original_item = vie_encoder_map_.Find(channel_id);
|
EncoderMap::const_iterator orig_it = vie_encoder_map_.find(channel_id);
|
||||||
assert(original_item);
|
|
||||||
ViEEncoder* original_encoder =
|
|
||||||
static_cast<ViEEncoder*>(original_item->GetItem());
|
|
||||||
|
|
||||||
MapItem* channel_item = channel_map_.First();
|
for (ChannelMap::const_iterator c_it = channel_map_.begin();
|
||||||
while (channel_item) {
|
c_it != channel_map_.end(); ++c_it) {
|
||||||
MapItem* compare_item = vie_encoder_map_.Find(channel_item->GetId());
|
EncoderMap::const_iterator comp_it = vie_encoder_map_.find(c_it->first);
|
||||||
assert(compare_item);
|
assert(comp_it != vie_encoder_map_.end());
|
||||||
ViEEncoder* compare_encoder =
|
if (comp_it->second == orig_it->second) {
|
||||||
static_cast<ViEEncoder*>(compare_item->GetItem());
|
channels->push_back(c_it->second);
|
||||||
if (compare_encoder == original_encoder) {
|
|
||||||
channels->push_back(static_cast<ViEChannel*>(channel_item->GetItem()));
|
|
||||||
}
|
}
|
||||||
channel_item = channel_map_.Next(channel_item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
|
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by a BSD-style license
|
* Use of this source code is governed by a BSD-style license
|
||||||
* that can be found in the LICENSE file in the root of the source
|
* that can be found in the LICENSE file in the root of the source
|
||||||
@ -12,9 +12,9 @@
|
|||||||
#define WEBRTC_VIDEO_ENGINE_VIE_CHANNEL_MANAGER_H_
|
#define WEBRTC_VIDEO_ENGINE_VIE_CHANNEL_MANAGER_H_
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include "engine_configurations.h"
|
#include "engine_configurations.h"
|
||||||
#include "system_wrappers/interface/map_wrapper.h"
|
|
||||||
#include "system_wrappers/interface/scoped_ptr.h"
|
#include "system_wrappers/interface/scoped_ptr.h"
|
||||||
#include "typedefs.h"
|
#include "typedefs.h"
|
||||||
#include "video_engine/vie_defines.h"
|
#include "video_engine/vie_defines.h"
|
||||||
@ -23,6 +23,7 @@
|
|||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
class CriticalSectionWrapper;
|
class CriticalSectionWrapper;
|
||||||
|
class MapWrapper;
|
||||||
class ProcessThread;
|
class ProcessThread;
|
||||||
class ViEChannel;
|
class ViEChannel;
|
||||||
class ViEEncoder;
|
class ViEEncoder;
|
||||||
@ -32,6 +33,8 @@ class VoEVideoSync;
|
|||||||
class VoiceEngine;
|
class VoiceEngine;
|
||||||
|
|
||||||
typedef std::list<ViEChannel*> ChannelList;
|
typedef std::list<ViEChannel*> ChannelList;
|
||||||
|
typedef std::map<int, ViEChannel*> ChannelMap;
|
||||||
|
typedef std::map<int, ViEEncoder*> EncoderMap;
|
||||||
|
|
||||||
class ViEChannelManager: private ViEManagerBase {
|
class ViEChannelManager: private ViEManagerBase {
|
||||||
friend class ViEChannelManagerScoped;
|
friend class ViEChannelManagerScoped;
|
||||||
@ -95,12 +98,12 @@ class ViEChannelManager: private ViEManagerBase {
|
|||||||
int engine_id_;
|
int engine_id_;
|
||||||
int number_of_cores_;
|
int number_of_cores_;
|
||||||
ViEPerformanceMonitor& vie_performance_monitor_;
|
ViEPerformanceMonitor& vie_performance_monitor_;
|
||||||
MapWrapper channel_map_;
|
ChannelMap channel_map_;
|
||||||
bool* free_channel_ids_;
|
bool* free_channel_ids_;
|
||||||
int free_channel_ids_size_;
|
int free_channel_ids_size_;
|
||||||
|
|
||||||
// Maps Channel id -> ViEEncoder.
|
// Maps Channel id -> ViEEncoder.
|
||||||
MapWrapper vie_encoder_map_;
|
EncoderMap vie_encoder_map_;
|
||||||
VoEVideoSync* voice_sync_interface_;
|
VoEVideoSync* voice_sync_interface_;
|
||||||
scoped_ptr<VieRemb> remb_;
|
scoped_ptr<VieRemb> remb_;
|
||||||
VoiceEngine* voice_engine_;
|
VoiceEngine* voice_engine_;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user