下面将以实现一个音频通话功能为示例详细介绍VoiceEngine的使用,在文末将附上相应源码的下载地址。这里参考的是voiceengine\voe_cmd_test。
第一步是创建VoiceEngine和相关的sub-apis
//
// Create VoiceEngine related instance
//
webrtc::VoiceEngine* ptrVoE = NULL;
ptrVoE = webrtc::VoiceEngine::Create();
webrtc::VoEBase* ptrVoEBase = NULL;
ptrVoEBase = webrtc::VoEBase::GetInterface(ptrVoE);
webrtc::VoECodec* ptrVoECodec = NULL;
ptrVoECodec = webrtc::VoECodec::GetInterface(ptrVoE);
webrtc::VoEAudioProcessing* ptrVoEAp = NULL;
ptrVoEAp = webrtc::VoEAudioProcessing::GetInterface(ptrVoE);
webrtc::VoEVolumeControl* ptrVoEVolume = NULL;
ptrVoEVolume = webrtc::VoEVolumeControl::GetInterface(ptrVoE);
webrtc::VoENetwork* ptrVoENetwork = NULL;
ptrVoENetwork = webrtc::VoENetwork::GetInterface(ptrVoE);
webrtc::VoEFile* ptrVoEFile = NULL;
ptrVoEFile = webrtc::VoEFile::GetInterface(ptrVoE);
webrtc::VoEHardware* ptrVoEHardware = NULL;
ptrVoEHardware = webrtc::VoEHardware::GetInterface(ptrVoE);
然后可以选择设置tracefile的路径,这里我们还会对麦克风以及回放的声音做一个录制,所以也一并指明路径。
//
//Set Trace File and Record File
//
const std::string trace_filename = "webrtc_trace.txt";
VoiceEngine::SetTraceFilter(kTraceAll);
error = VoiceEngine::SetTraceFile(trace_filename.c_str());
if (error != 0)
{
printf("ERROR in VoiceEngine::SetTraceFile\n");
return error;
}
error = VoiceEngine::SetTraceCallback(NULL);
if (error != 0)
{
printf("ERROR in VoiceEngine::SetTraceCallback\n");
return error;
}
const std::string play_filename = "recorded_playout.wav";
const std::string mic_filename = "recorded_mic.wav";
接下来是初始化,获取VoiceEngine的版本号
//
//Init
//
error = ptrVoEBase->Init();
if (error != 0)
{
printf("ERROR in VoEBase::Init\n");
return error;
}
error = ptrVoEBase->RegisterVoiceEngineObserver(my_observer);
if (error != 0)
{
printf("ERROR in VoEBase:;RegisterVoiceEngineObserver\n");
return error;
}
printf("Version\n");
char tmp[1024];
error = ptrVoEBase->GetVersion(tmp);
if (error != 0)
{
printf("ERROR in VoEBase::GetVersion\n");
return error;
}
printf("%s\n", tmp);
这里同时还注册了一个VoiceEngineObserver对象,可以根据相应的error code输出信息,比如当检测到键盘敲击的噪音时会给出提示。这个类的定义如下
class MyObserver : public VoiceEngineObserver {
public:
virtual void CallbackOnError(int channel, int err_code);
};
void MyObserver::CallbackOnError(int channel, int err_code) {
// Add printf for other error codes here
if (err_code == VE_TYPING_NOISE_WARNING) {
printf(" TYPING NOISE DETECTED \n");
}
else if (err_code == VE_TYPING_NOISE_OFF_WARNING) {
printf(" TYPING NOISE OFF DETECTED \n");
}
else if (err_code == VE_RECEIVE_PACKET_TIMEOUT) {
printf(" RECEIVE PACKET TIMEOUT \n");
}
else if (err_code == VE_PACKET_RECEIPT_RESTARTED) {
printf(" PACKET RECEIPT RESTARTED \n");
}
else if (err_code == VE_RUNTIME_PLAY_WARNING) {
printf(" RUNTIME PLAY WARNING \n");
}
else if (err_code == VE_RUNTIME_REC_WARNING) {
printf(" RUNTIME RECORD WARNING \n");
}
else if (err_code == VE_SATURATION_WARNING) {
printf(" SATURATION WARNING \n");
}
else if (err_code == VE_RUNTIME_PLAY_ERROR) {
printf(" RUNTIME PLAY ERROR \n");
}
else if (err_code == VE_RUNTIME_REC_ERROR) {
printf(" RUNTIME RECORD ERROR \n");
}
else if (err_code == VE_REC_DEVICE_REMOVED) {
printf(" RECORD DEVICE REMOVED \n");
}
}
以上完成了前期准备的工作,下面首先开始对网络的设置。如果是在本机上进行测试的话,ip地址直接写127.0.0.1即可,同时要注意的是,remote port和local port要一致。
//
//Network Settings
//
int audiochannel;
audiochannel = ptrVoEBase->CreateChannel();
if (audiochannel < 0)
{
printf("ERROR in VoEBase::CreateChannel\n");
return audiochannel;
}
VoiceChannelTransport* voice_channel_transport = new VoiceChannelTransport(ptrVoENetwork, audiochannel);
char ip[64] = "127.0.0.1";
int rPort = 800;//remote port
int lPort = 800;//local port
error = voice_channel_transport->SetSendDestination(ip, rPort);
if (error != 0)
{
printf("ERROR in set send ip and port\n");
return error;
}
error = voice_channel_transport->SetLocalReceiver(lPort);
if (error != 0)
{
printf("ERROR in set receiver and port\n");
return error;
}
上面出现的VoiceChannelTransport类的定义如下
// Helper class for VoiceEngine tests.
class VoiceChannelTransport : public webrtc::test::UdpTransportData {
public:
VoiceChannelTransport(VoENetwork* voe_network, int channel);
virtual ~VoiceChannelTransport();
// Start implementation of UdpTransportData.
void IncomingRTPPacket(const int8_t* incoming_rtp_packet,
const size_t packet_length,
const char* /*from_ip*/,
const uint16_t /*from_port*/) override;
void IncomingRTCPPacket(const int8_t* incoming_rtcp_packet,
const size_t packet_length,
const char* /*from_ip*/,
const uint16_t /*from_port*/) override;
// End implementation of UdpTransportData.
// Specifies the ports to receive RTP packets on.
int SetLocalReceiver(uint16_t rtp_port);
// Specifies the destination port and IP address for a specified channel.
int SetSendDestination(const char* ip_address, uint16_t rtp_port);
private:
int channel_;
VoENetwork* voe_network_;
webrtc::test::UdpTransport* socket_transport_;
};
VoiceChannelTransport::VoiceChannelTransport(VoENetwork* voe_network,
int channel)
: channel_(channel),
voe_network_(voe_network) {
uint8_t socket_threads = 1;
socket_transport_ = webrtc::test::UdpTransport::Create(channel, socket_threads);
int registered = voe_network_->RegisterExternalTransport(channel,
*socket_transport_);
#if !defined(WEBRTC_ANDROID) && !defined(WEBRTC_IOS)
if (registered != 0)
return;
#else
assert(registered == 0);
#endif
}
VoiceChannelTransport::~VoiceChannelTransport() {
voe_network_->DeRegisterExternalTransport(channel_);
webrtc::test::UdpTransport::Destroy(socket_transport_);
}
void VoiceChannelTransport::IncomingRTPPacket(
const int8_t* incoming_rtp_packet,
const size_t packet_length,
const char* /*from_ip*/,
const uint16_t /*from_port*/) {
voe_network_->ReceivedRTPPacket(
channel_, incoming_rtp_packet, packet_length, PacketTime());
}
void VoiceChannelTransport::IncomingRTCPPacket(
const int8_t* incoming_rtcp_packet,
const size_t packet_length,
const char* /*from_ip*/,
const uint16_t /*from_port*/) {
voe_network_->ReceivedRTCPPacket(channel_, incoming_rtcp_packet,
packet_length);
}
int VoiceChannelTransport::SetLocalReceiver(uint16_t rtp_port) {
static const int kNumReceiveSocketBuffers = 500;
int return_value = socket_transport_->InitializeReceiveSockets(this,
rtp_port);
if (return_value == 0) {
return socket_transport_->StartReceiving(kNumReceiveSocketBuffers);
}
return return_value;
}
int VoiceChannelTransport::SetSendDestination(const char* ip_address,
uint16_t rtp_port) {
return socket_transport_->InitializeSendSockets(ip_address, rtp_port);
}
完成了网络的设置后,进行编解码器的设置。这里简单的由用户选择使用哪一个编码器,当然还可以进一步对编码器的参数进行设置
//
//Setup Codecs
//
CodecInst codec_params;
CodecInst cinst;
for (int i = 0; i < ptrVoECodec->NumOfCodecs(); ++i) {
int error = ptrVoECodec->GetCodec(i, codec_params);
if (error != 0)
{
printf("ERROR in VoECodec::GetCodec\n");
return error;
}
printf("%2d. %3d %s/%d/%d \n", i, codec_params.pltype, codec_params.plname,
codec_params.plfreq, codec_params.channels);
}
printf("Select send codec: ");
int codec_selection;
scanf("%i", &codec_selection);
ptrVoECodec->GetCodec(codec_selection, cinst);
error = ptrVoECodec->SetSendCodec(audiochannel, cinst);
if (error != 0)
{
printf("ERROR in VoECodec::SetSendCodec\n");
return error;
}
接下来进行录制设备和播放设备的设置
//
//Setup Devices
//
int rd(-1), pd(-1);
error = ptrVoEHardware->GetNumOfRecordingDevices(rd);
if (error != 0)
{
printf("ERROR in VoEHardware::GetNumOfRecordingDevices\n");
return error;
}
error = ptrVoEHardware->GetNumOfPlayoutDevices(pd);
if (error != 0)
{
printf("ERROR in VoEHardware::GetNumOfPlayoutDevices\n");
return error;
}
char dn[128] = { 0 };
char guid[128] = { 0 };
printf("\nPlayout devices (%d): \n", pd);
for (int j = 0; j < pd; ++j) {
error = ptrVoEHardware->GetPlayoutDeviceName(j, dn, guid);
if (error != 0)
{
printf("ERROR in VoEHardware::GetPlayoutDeviceName\n");
return error;
}
printf(" %d: %s \n", j, dn);
}
printf("Recording devices (%d): \n", rd);
for (int j = 0; j < rd; ++j) {
error = ptrVoEHardware->GetRecordingDeviceName(j, dn, guid);
if (error != 0)
{
printf("ERROR in VoEHardware::GetRecordingDeviceName\n");
return error;
}
printf(" %d: %s \n", j, dn);
}
printf("Select playout device: ");
scanf("%d", &pd);
error = ptrVoEHardware->SetPlayoutDevice(pd);
if (error != 0)
{
printf("ERROR in VoEHardware::SetPlayoutDevice\n");
return error;
}
printf("Select recording device: ");
scanf("%d", &rd);
getchar();
error = ptrVoEHardware->SetRecordingDevice(rd);
if (error != 0)
{
printf("ERROR in VoEHardware::SetRecordingDevice\n");
return error;
}
然后对音频预处理功能进行设置,这里作为示例,把各种预处理功能都enable了
//
//Audio Processing
//
error = ptrVoECodec->SetVADStatus(0, 1);//FIX:why not use audio channel
if (error != 0)
{
printf("ERROR in VoECodec::SetVADStatus\n");
return error;
}
error = ptrVoEAp->SetAgcStatus(1);
if (error != 0)
{
printf("ERROR in VoEAudioProcess::SetAgcStatus\n");
return error;
}
error = ptrVoEAp->SetEcStatus(1);
if (error != 0)
{
printf("ERROR in VoEAudioProcess::SetEcStatus\n");
return error;
}
error = ptrVoEAp->SetNsStatus(1);
if (error != 0)
{
printf("ERROR in VoEAudioProcess::SetNsStatus\n");
return error;
}
error = ptrVoEAp->SetRxAgcStatus(audiochannel, 1);
if (error != 0)
{
printf("ERROR in VoEAudioProcess::SetRxAgcStatus\n");
return error;
}
error = ptrVoEAp->SetRxNsStatus(audiochannel, 1);
if (error != 0)
{
printf("ERROR in VoEAudioProcess::SetRxNsStatus\n");
return error;
}
至此,就可以开始发送、接收、录制了
//Start Receive
error = ptrVoEBase->StartReceive(audiochannel);
if (error != 0)
{
printf("ERROR in VoEBase::StartReceive\n");
return error;
}
//Start Playout
error = ptrVoEBase->StartPlayout(audiochannel);
if (error != 0)
{
printf("ERROR in VoEBase::StartPlayout\n");
return error;
}
//Start Send
error = ptrVoEBase->StartSend(audiochannel);
if (error != 0)
{
printf("ERROR in VoEBase::StartSend\n");
return error;
}
//Start Record
error = ptrVoEFile->StartRecordingMicrophone(mic_filename.c_str());
if (error != 0)
{
printf("ERROR in VoEFile::StartRecordingMicrophone\n");
return error;
}
error = ptrVoEFile->StartRecordingPlayout(audiochannel, play_filename.c_str());
if (error != 0)
{
printf("ERROR in VoEFile::StartRecordingPlayout\n");
return error;
}
在通话结束之后,还需要进行相应的stop\release
//Stop Record
error = ptrVoEFile->StopRecordingMicrophone();
if (error != 0)
{
printf("ERROR in VoEFile::StopRecordingMicrophone\n");
return error;
}
error = ptrVoEFile->StopRecordingPlayout(audiochannel);
if (error != 0)
{
printf("ERROR in VoEFile::StopRecordingPlayout\n");
return error;
}
//Stop Receive
error = ptrVoEBase->StopReceive(audiochannel);
if (error != 0)
{
printf("ERROR in VoEBase::StopReceive\n");
return error;
}
//Stop Send
error = ptrVoEBase->StopSend(audiochannel);
if (error != 0)
{
printf("ERROR in VoEBase::StopSend\n");
return error;
}
//Stop Playout
error = ptrVoEBase->StopPlayout(audiochannel);
if (error != 0)
{
printf("ERROR in VoEBase::StopPlayout\n");
return error;
}
//Delete Channel
error = ptrVoEBase->DeleteChannel(audiochannel);
if (error != 0)
{
printf("ERROR in VoEBase::DeleteChannel\n");
return error;
}
delete voice_channel_transport;
ptrVoEBase->DeRegisterVoiceEngineObserver();
error = ptrVoEBase->Terminate();
if (error != 0)
{
printf("ERROR in VoEBase::Terminate\n");
return error;
}
int remainingInterfaces = 0;
remainingInterfaces += ptrVoEBase->Release();
remainingInterfaces = ptrVoECodec->Release();
remainingInterfaces += ptrVoEVolume->Release();
remainingInterfaces += ptrVoEFile->Release();
remainingInterfaces += ptrVoEAp->Release();
remainingInterfaces += ptrVoEHardware->Release();
remainingInterfaces += ptrVoENetwork->Release();
/*if (remainingInterfaces > 0)
{
printf("ERROR: Could not release all interfaces\n");
return -1;
}*/
bool deleted = webrtc::VoiceEngine::Delete(ptrVoE);
if (deleted == false)
{
printf("ERROR in VoiceEngine::Delete\n");
return -1;
}
需要注意的是,这里remainingInterfaces最后不会为0,因为我们没有用到VoiceEngine的全部sub-apis。
至此,就实现了一个音频通话的功能。
关注下方公众号,回复“webrtc音频通话”,查看源码地址
关注公众号,掌握更多多媒体领域知识与资讯
文章帮到你了?可以扫描如下二维码进行打赏~,打赏多少您随意~