/* Copyright (C) 2012 Doubango Telecom * * This file is part of Open Source Doubango Framework. * * DOUBANGO is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DOUBANGO is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with DOUBANGO. */ #include "audio_webrtc.h" #include "audio_webrtc_consumer.h" #include "audio_webrtc_producer.h" #include "audio_webrtc_transport.h" #include #include #include "tinymedia/tmedia_consumer.h" #include "tinymedia/tmedia_producer.h" #include "tsk_list.h" #include "tsk_safeobj.h" #include "tsk_debug.h" using namespace webrtc; #define kAudioDeviceModuleId 444 #if DOUBANGO_AUDIO_WEBRTC_UNDER_ANDROID // https://groups.google.com/group/android-ndk/browse_thread/thread/a1667f28162cf69b/8ef3a171df7f8dfe extern "C" { void *__dso_handle = NULL; } #endif typedef enum PLUGIN_INDEX_E { PLUGIN_INDEX_AUDIO_CONSUMER, PLUGIN_INDEX_AUDIO_PRODUCER, PLUGIN_INDEX_COUNT } PLUGIN_INDEX_T; int __plugin_get_def_count() { return PLUGIN_INDEX_COUNT; } tsk_plugin_def_type_t __plugin_get_def_type_at(int index) { switch(index) { case PLUGIN_INDEX_AUDIO_CONSUMER: return tsk_plugin_def_type_consumer; case PLUGIN_INDEX_AUDIO_PRODUCER: return tsk_plugin_def_type_producer; default: { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("No plugin at index %d", index); return tsk_plugin_def_type_none; } } } tsk_plugin_def_media_type_t __plugin_get_def_media_type_at(int index) { switch(index) { case PLUGIN_INDEX_AUDIO_CONSUMER: case PLUGIN_INDEX_AUDIO_PRODUCER: { return tsk_plugin_def_media_type_audio; } default: { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("No plugin at index %d", index); return tsk_plugin_def_media_type_none; } } } tsk_plugin_def_ptr_const_t __plugin_get_def_at(int index) { switch(index) { case PLUGIN_INDEX_AUDIO_CONSUMER: { return audio_consumer_webrtc_plugin_def_t; } case PLUGIN_INDEX_AUDIO_PRODUCER: { return audio_producer_webrtc_plugin_def_t; } default: { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("No plugin at index %d", index); return tsk_null; } } } // // WebRTC AudioInstance // typedef struct audio_webrtc_instance_s { TSK_DECLARE_OBJECT; uint64_t sessionId; bool isStarted; bool isConsumerPrepared; bool isConsumerStarted; bool isProducerPrepared; bool isProducerStarted; bool isSpeakerAvailable; bool isPlayoutAvailable; bool isRecordingAvailable; AudioDeviceModule* device; AudioTransportImpl* transport; TSK_DECLARE_SAFEOBJ; } audio_webrtc_instance_t; typedef tsk_list_t audio_webrtc_instances_L_t; static audio_webrtc_instances_L_t* __audioInstances = tsk_null; static tsk_object_t* audio_webrtc_instance_ctor(tsk_object_t * self, va_list * app) { audio_webrtc_instance_t* audioInstance = (audio_webrtc_instance_t*)self; if(audioInstance) { tsk_safeobj_init(audioInstance); } return self; } static tsk_object_t* audio_webrtc_instance_dtor(tsk_object_t * self) { DOUBANGO_AUDIO_WEBRTC_DEBUG_INFO("Audio Instance destroyed"); audio_webrtc_instance_t* audioInstance = (audio_webrtc_instance_t*)self; if(audioInstance) { tsk_safeobj_lock(audioInstance); if(audioInstance->device) { audioInstance->device->RegisterAudioCallback(tsk_null); audioInstance->device->Terminate(); audioInstance->device->Release();//FIXME: must be deleted? audioInstance->device = tsk_null; } if(audioInstance->transport) { delete audioInstance->transport; audioInstance->transport = tsk_null; } tsk_safeobj_unlock(audioInstance); tsk_safeobj_deinit(audioInstance); } return self; } static int audio_webrtc_instance_cmp(const tsk_object_t *_ai1, const tsk_object_t *_ai2) { return ((int)_ai1 - (int)_ai2); } static const tsk_object_def_t audio_webrtc_instance_def_s = { sizeof(audio_webrtc_instance_t), audio_webrtc_instance_ctor, audio_webrtc_instance_dtor, audio_webrtc_instance_cmp, }; const tsk_object_def_t *audio_webrtc_instance_def_t = &audio_webrtc_instance_def_s; audio_webrtc_instance_handle_t* audio_webrtc_instance_create(uint64_t sessionId) { audio_webrtc_instance_t* audioInstance = tsk_null; // create list used to hold instances if(!__audioInstances && !(__audioInstances = tsk_list_create())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("Failed to create new list"); return tsk_null; } //= lock the list tsk_list_lock(__audioInstances); // find the instance from the list const tsk_list_item_t* item; tsk_list_foreach(item, __audioInstances) { if(((audio_webrtc_instance_t*)item->data)->sessionId == sessionId) { audioInstance = (audio_webrtc_instance_t*)tsk_object_ref(item->data); break; } } if(!audioInstance) { audio_webrtc_instance_t* _audioInstance; if(!(_audioInstance = (audio_webrtc_instance_t*)tsk_object_new(&audio_webrtc_instance_def_s))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("Failed to create new audio instance"); goto done; } if(!(_audioInstance->device = AudioDeviceModuleImpl::Create(kAudioDeviceModuleId))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("Failed to create audio device"); TSK_OBJECT_SAFE_FREE(_audioInstance); goto done; } _audioInstance->device->AddRef(); if(!(_audioInstance->transport = new AudioTransportImpl(_audioInstance->device))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("Failed to create audio transport"); TSK_OBJECT_SAFE_FREE(_audioInstance); goto done; } if((_audioInstance->device->RegisterAudioCallback(_audioInstance->transport))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("AudioDeviceModule::RegisterAudioCallback() failed"); TSK_OBJECT_SAFE_FREE(_audioInstance); goto done; } if((_audioInstance->device->Init())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("AudioDeviceModule::Init() failed"); TSK_OBJECT_SAFE_FREE(_audioInstance); goto done; } _audioInstance->sessionId = sessionId; audioInstance = _audioInstance; tsk_list_push_back_data(__audioInstances, (void**)&_audioInstance); } done: //= unlock the list tsk_list_unlock(__audioInstances); return audioInstance; } int audio_webrtc_instance_prepare_consumer(audio_webrtc_instance_handle_t* _self, tmedia_consumer_t** _consumer) { audio_webrtc_instance_t* self = (audio_webrtc_instance_t*)_self; if(!self || !self->device || !self->transport || !_consumer || !*_consumer) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("invalid parameter"); return -1; } if(self->isConsumerPrepared) { DOUBANGO_AUDIO_WEBRTC_DEBUG_WARN("Consumer already prepared"); return 0; } int ret; bool _bool; tsk_safeobj_lock(self); self->transport->SetConsumer((const struct audio_consumer_webrtc_s*)*_consumer); if((ret = self->device->SetPlayoutDevice(DOUBANGO_AUDIO_WEBRTC_DEVICE_DEFAULT))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("AudioDeviceModule->SetPlayoutDevice(%d) failed", DOUBANGO_AUDIO_WEBRTC_DEVICE_DEFAULT); } if((ret = self->device->SpeakerIsAvailable(&_bool))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("SpeakerIsAvailable() failed with error code=%d", ret); } else { if(!_bool) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("SpeakerIsAvailable() returned false"); } self->isSpeakerAvailable = _bool; } if((ret = self->device->InitSpeaker())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("InitSpeaker() failed with error code=%d", ret); } if((ret = self->device->PlayoutIsAvailable(&_bool))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("PlayoutIsAvailable() failed with error code =%d", ret); } else { if(!_bool) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("PlayoutIsAvailable() returned false"); } self->isPlayoutAvailable = _bool; } if((ret = self->device->SetStereoPlayout(((*_consumer)->audio.in.channels == 2)))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("SetStereoPlayout(%d==2) failed with error code=%d", (*_consumer)->audio.in.channels, ret); } //if((ret = self->device->SetPlayoutBuffer(AudioDeviceModule::kFixedBufferSize, (*_consumer)->audio.ptime))){ // DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("SetPlayoutBuffer(%d ms) failed with error code=%d", (*_consumer)->audio.ptime, ret); //} // always request 10ms buffers. In all cases WebRTC don't support anything else if((ret = self->device->SetPlayoutBuffer(AudioDeviceModule::kFixedBufferSize, 10))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("SetPlayoutBuffer(%d ms) failed with error code=%d", 10, ret); } uint32_t playoutSampleRate = (*_consumer)->audio.out.rate ? (*_consumer)->audio.out.rate : (*_consumer)->audio.in.rate; if((ret = self->device->SetPlayoutSampleRate(playoutSampleRate))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("SetPlayoutSampleRate(%d) failed with error code=%d", playoutSampleRate, ret); } if((ret = self->device->InitPlayout())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("AudioDeviceModule::InitPlayout() failed with error code = %d", ret); goto done; } // init output parameters if((ret = self->device->StereoPlayout(&_bool))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("StereoPlayout() failed with error code=%d", ret); } else { (*_consumer)->audio.out.channels = (_bool ? 2 : 1); } if((ret = self->device->PlayoutSampleRate(&playoutSampleRate))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("PlayoutSampleRate() failed with error code=%d", ret); } else { (*_consumer)->audio.out.rate = playoutSampleRate; } done: tsk_safeobj_unlock(self); self->isConsumerPrepared = (ret == 0); return ret; } int audio_webrtc_instance_prepare_producer(audio_webrtc_instance_handle_t* _self, tmedia_producer_t** _producer) { audio_webrtc_instance_t* self = (audio_webrtc_instance_t*)_self; if(!self || !self->device || !self->transport || !_producer || !*_producer) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("invalid parameter"); return -1; } if(self->isProducerPrepared) { DOUBANGO_AUDIO_WEBRTC_DEBUG_WARN("Producer already prepared"); return 0; } int ret; bool _bool; tsk_safeobj_lock(self); self->transport->SetProducer((const struct audio_producer_webrtc_s*)*_producer); if((ret = self->device->SetRecordingDevice(DOUBANGO_AUDIO_WEBRTC_DEVICE_DEFAULT))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("AudioDeviceModule->SetRecordingDevice(%d) failed", DOUBANGO_AUDIO_WEBRTC_DEVICE_DEFAULT); } if((ret = self->device->RecordingIsAvailable(&_bool))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("RecordingIsAvailable() failed with error code =%d", ret); } else { if(!_bool) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("RecordingIsAvailable() returned false"); } self->isRecordingAvailable = _bool; } if((ret = self->device->MicrophoneIsAvailable(&_bool))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("MicrophoneIsAvailable() failed with error code =%d", ret); } else { if(!_bool) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("MicrophoneIsAvailable() returned false"); } else { if((ret = self->device->InitMicrophone())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("InitMicrophone() failed with error code =%d", ret); } } } if((ret = self->device->SetStereoRecording(((*_producer)->audio.channels == 2)))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("SetStereoRecording(%d==2) failed with error code=%d", (*_producer)->audio.channels, ret); } uint32_t recordingSampleRate = (*_producer)->audio.rate; if((ret = self->device->SetRecordingSampleRate(recordingSampleRate))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("SetRecordingSampleRate(%d) failed with error code=%d", recordingSampleRate, ret); } if((ret = self->device->InitRecording())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("AudioDeviceModule::InitRecording() failed with error code = %d", ret); goto done; } // init output parameters if((ret = self->device->StereoRecording(&_bool))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("StereoRecording() failed with error code=%d", ret); } else { (*_producer)->audio.channels = (_bool ? 2 : 1); } if((ret = self->device->RecordingSampleRate(&recordingSampleRate))) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("RecordingSampleRate() failed with error code=%d", ret); } else { (*_producer)->audio.rate = recordingSampleRate; } done: tsk_safeobj_unlock(self); self->isProducerPrepared = (ret == 0); return ret; } int audio_webrtc_instance_start_consumer(audio_webrtc_instance_handle_t* _self) { audio_webrtc_instance_t* self = (audio_webrtc_instance_t*)_self; if(!self || !self->device || !self->transport) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("invalid parameter"); return -1; } tsk_safeobj_lock(self); if(!self->isConsumerPrepared) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("Consumer not prepared"); goto done; } if(self->isConsumerStarted) { DOUBANGO_AUDIO_WEBRTC_DEBUG_WARN("Consumer already started"); goto done; } if(self->isPlayoutAvailable) { int ret; if((ret = self->device->StartPlayout())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("StartPlayout() failed with error code = %d", ret); } self->isConsumerStarted = self->device->Playing(); DOUBANGO_AUDIO_WEBRTC_DEBUG_INFO("isPlaying=%s", (self->isConsumerPrepared ? "true" : "false")); } done: tsk_safeobj_unlock(self); return (self->isConsumerStarted ? 0 : -1); } int audio_webrtc_instance_start_producer(audio_webrtc_instance_handle_t* _self) { audio_webrtc_instance_t* self = (audio_webrtc_instance_t*)_self; if(!self || !self->device || !self->transport) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("invalid parameter"); return -1; } tsk_safeobj_lock(self); if(!self->isProducerPrepared) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("Producer not prepared"); goto done; } if(self->isProducerStarted) { DOUBANGO_AUDIO_WEBRTC_DEBUG_WARN("Consumer already started"); goto done; } if(self->isRecordingAvailable) { int ret; if((ret = self->device->StartRecording())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("StartRecording() failed with error code = %d", ret); } self->isProducerStarted = self->device->Recording(); DOUBANGO_AUDIO_WEBRTC_DEBUG_INFO("isRecording=%s", (self->isProducerStarted ? "true" : "false")); } done: tsk_safeobj_unlock(self); return (self->isProducerStarted ? 0 : -1); return 0; } int audio_webrtc_instance_stop_consumer(audio_webrtc_instance_handle_t* _self) { audio_webrtc_instance_t* self = (audio_webrtc_instance_t*)_self; if(!self || !self->device || !self->transport) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("invalid parameter"); return -1; } tsk_safeobj_lock(self); if(!self->isConsumerStarted) { goto done; } int ret; if((ret = self->device->StopPlayout())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("StopPlayout() failed with error code = %d", ret); } else { self->isConsumerStarted = self->device->Playing(); } done: tsk_safeobj_unlock(self); return (self->isConsumerStarted ? -1 : 0); } int audio_webrtc_instance_stop_producer(audio_webrtc_instance_handle_t* _self) { audio_webrtc_instance_t* self = (audio_webrtc_instance_t*)_self; if(!self || !self->device || !self->transport) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("invalid parameter"); return -1; } tsk_safeobj_lock(self); if(!self->isProducerStarted) { goto done; } int ret; if((ret = self->device->StopRecording())) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("StopRecording() failed with error code = %d", ret); } else { self->isProducerStarted = self->device->Recording(); } done: tsk_safeobj_unlock(self); return (self->isProducerStarted ? -1 : 0); } int audio_webrtc_instance_destroy(audio_webrtc_instance_handle_t** _self) { if(!_self || !*_self) { DOUBANGO_AUDIO_WEBRTC_DEBUG_ERROR("Invalid parameter"); return -1; } tsk_list_lock(__audioInstances); if(tsk_object_get_refcount(*_self)==1) { tsk_list_remove_item_by_data(__audioInstances, *_self); } else { tsk_object_unref(*_self); } tsk_list_unlock(__audioInstances); *_self = tsk_null; return 0; }