WebRTC VoiceEngine綜合應用示例(二)——音頻通話的基本流程

下面將以實現一個音頻通話功能爲示例詳細介紹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音頻通話”,查看源碼地址

關注公衆號,掌握更多多媒體領域知識與資訊

文章幫到你了?可以掃描如下二維碼進行打賞~,打賞多少您隨意~


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章