AudioOutputALSA.cpp 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. //
  2. // libtgvoip is free and unencumbered public domain software.
  3. // For more information, see http://unlicense.org or the UNLICENSE file
  4. // you should have received with this source code distribution.
  5. //
  6. #include <assert.h>
  7. #include <dlfcn.h>
  8. #include "AudioOutputALSA.h"
  9. #include "../../logging.h"
  10. #include "../../VoIPController.h"
  11. #define BUFFER_SIZE 960
  12. #define CHECK_ERROR(res, msg) if(res<0){LOGE(msg ": %s", _snd_strerror(res)); failed=true; return;}
  13. #define CHECK_DL_ERROR(res, msg) if(!res){LOGE(msg ": %s", dlerror()); failed=true; return;}
  14. #define LOAD_FUNCTION(lib, name, ref) {ref=(typeof(ref))dlsym(lib, name); CHECK_DL_ERROR(ref, "Error getting entry point for " name);}
  15. using namespace tgvoip::audio;
  16. AudioOutputALSA::AudioOutputALSA(std::string devID){
  17. isPlaying=false;
  18. handle=NULL;
  19. lib=dlopen("libasound.so.2", RTLD_LAZY);
  20. if(!lib)
  21. lib=dlopen("libasound.so", RTLD_LAZY);
  22. if(!lib){
  23. LOGE("Error loading libasound: %s", dlerror());
  24. failed=true;
  25. return;
  26. }
  27. LOAD_FUNCTION(lib, "snd_pcm_open", _snd_pcm_open);
  28. LOAD_FUNCTION(lib, "snd_pcm_set_params", _snd_pcm_set_params);
  29. LOAD_FUNCTION(lib, "snd_pcm_close", _snd_pcm_close);
  30. LOAD_FUNCTION(lib, "snd_pcm_writei", _snd_pcm_writei);
  31. LOAD_FUNCTION(lib, "snd_pcm_recover", _snd_pcm_recover);
  32. LOAD_FUNCTION(lib, "snd_strerror", _snd_strerror);
  33. SetCurrentDevice(devID);
  34. }
  35. AudioOutputALSA::~AudioOutputALSA(){
  36. if(handle)
  37. _snd_pcm_close(handle);
  38. if(lib)
  39. dlclose(lib);
  40. }
  41. void AudioOutputALSA::Start(){
  42. if(failed || isPlaying)
  43. return;
  44. isPlaying=true;
  45. thread=new Thread(std::bind(&AudioOutputALSA::RunThread, this));
  46. thread->SetName("AudioOutputALSA");
  47. thread->Start();
  48. }
  49. void AudioOutputALSA::Stop(){
  50. if(!isPlaying)
  51. return;
  52. isPlaying=false;
  53. thread->Join();
  54. delete thread;
  55. thread=NULL;
  56. }
  57. bool AudioOutputALSA::IsPlaying(){
  58. return isPlaying;
  59. }
  60. void AudioOutputALSA::RunThread(){
  61. unsigned char buffer[BUFFER_SIZE*2];
  62. snd_pcm_sframes_t frames;
  63. while(isPlaying){
  64. InvokeCallback(buffer, sizeof(buffer));
  65. frames=_snd_pcm_writei(handle, buffer, BUFFER_SIZE);
  66. if (frames < 0){
  67. frames = _snd_pcm_recover(handle, frames, 0);
  68. }
  69. if (frames < 0) {
  70. LOGE("snd_pcm_writei failed: %s\n", _snd_strerror(frames));
  71. break;
  72. }
  73. }
  74. }
  75. void AudioOutputALSA::SetCurrentDevice(std::string devID){
  76. bool wasPlaying=isPlaying;
  77. isPlaying=false;
  78. if(handle){
  79. thread->Join();
  80. _snd_pcm_close(handle);
  81. }
  82. currentDevice=devID;
  83. int res=_snd_pcm_open(&handle, devID.c_str(), SND_PCM_STREAM_PLAYBACK, 0);
  84. if(res<0)
  85. res=_snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
  86. CHECK_ERROR(res, "snd_pcm_open failed");
  87. res=_snd_pcm_set_params(handle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1, 100000);
  88. CHECK_ERROR(res, "snd_pcm_set_params failed");
  89. if(wasPlaying){
  90. isPlaying=true;
  91. thread->Start();
  92. }
  93. }
  94. void AudioOutputALSA::EnumerateDevices(std::vector<AudioOutputDevice>& devs){
  95. int (*_snd_device_name_hint)(int card, const char* iface, void*** hints);
  96. char* (*_snd_device_name_get_hint)(const void* hint, const char* id);
  97. int (*_snd_device_name_free_hint)(void** hinst);
  98. void* lib=dlopen("libasound.so.2", RTLD_LAZY);
  99. if(!lib)
  100. dlopen("libasound.so", RTLD_LAZY);
  101. if(!lib)
  102. return;
  103. _snd_device_name_hint=(typeof(_snd_device_name_hint))dlsym(lib, "snd_device_name_hint");
  104. _snd_device_name_get_hint=(typeof(_snd_device_name_get_hint))dlsym(lib, "snd_device_name_get_hint");
  105. _snd_device_name_free_hint=(typeof(_snd_device_name_free_hint))dlsym(lib, "snd_device_name_free_hint");
  106. if(!_snd_device_name_hint || !_snd_device_name_get_hint || !_snd_device_name_free_hint){
  107. dlclose(lib);
  108. return;
  109. }
  110. char** hints;
  111. int err=_snd_device_name_hint(-1, "pcm", (void***)&hints);
  112. if(err!=0){
  113. dlclose(lib);
  114. return;
  115. }
  116. char** n=hints;
  117. while(*n){
  118. char* name=_snd_device_name_get_hint(*n, "NAME");
  119. if(strncmp(name, "surround", 8)==0 || strcmp(name, "null")==0){
  120. free(name);
  121. n++;
  122. continue;
  123. }
  124. char* desc=_snd_device_name_get_hint(*n, "DESC");
  125. char* ioid=_snd_device_name_get_hint(*n, "IOID");
  126. if(!ioid || strcmp(ioid, "Output")==0){
  127. char* l1=strtok(desc, "\n");
  128. char* l2=strtok(NULL, "\n");
  129. char* tmp=strtok(l1, ",");
  130. char* actualName=tmp;
  131. while((tmp=strtok(NULL, ","))){
  132. actualName=tmp;
  133. }
  134. if(actualName[0]==' ')
  135. actualName++;
  136. AudioOutputDevice dev;
  137. dev.id=std::string(name);
  138. if(l2){
  139. char buf[256];
  140. snprintf(buf, sizeof(buf), "%s (%s)", actualName, l2);
  141. dev.displayName=std::string(buf);
  142. }else{
  143. dev.displayName=std::string(actualName);
  144. }
  145. devs.push_back(dev);
  146. }
  147. free(name);
  148. free(desc);
  149. free(ioid);
  150. n++;
  151. }
  152. dlclose(lib);
  153. }