下面将以实现一个音频通话功能为示例详细介绍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 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。
至此,就实现了一个音频通话的功能。
本项目源代码下载地址。github地址。