PageRenderTime 31ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/quakeforge/trunk/libs/audio/targets/snd_alsa.c

#
C | 475 lines | 394 code | 48 blank | 33 comment | 61 complexity | c0d8272434d65da98c7fd49846906904 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, AGPL-3.0, AGPL-1.0, Unlicense
  1. /*
  2. snd_alsa.c
  3. Support for the ALSA 1.0.1 sound driver
  4. Copyright (C) 1999,2000 contributors of the QuakeForge project
  5. Please see the file "AUTHORS" for a list of contributors
  6. This program is free software; you can redistribute it and/or
  7. modify it under the terms of the GNU General Public License
  8. as published by the Free Software Foundation; either version 2
  9. of the License, or (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  13. See the GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program; if not, write to:
  16. Free Software Foundation, Inc.
  17. 59 Temple Place - Suite 330
  18. Boston, MA 02111-1307, USA
  19. */
  20. #ifdef HAVE_CONFIG_H
  21. # include "config.h"
  22. #endif
  23. static __attribute__ ((used)) const char rcsid[] =
  24. "$Id: snd_alsa.c 11946 2010-08-15 05:15:47Z taniwha $";
  25. #include <stdio.h>
  26. #include <dlfcn.h>
  27. #include <alsa/asoundlib.h>
  28. #include "QF/cvar.h"
  29. #include "QF/plugin.h"
  30. #include "QF/qargs.h"
  31. #include "QF/sys.h"
  32. #include "snd_render.h"
  33. static int snd_inited;
  34. static int snd_blocked = 0;
  35. static volatile dma_t sn;
  36. static snd_pcm_uframes_t buffer_size;
  37. static void *alsa_handle;
  38. static const char *pcmname = NULL;
  39. static snd_pcm_t *pcm;
  40. static plugin_t plugin_info;
  41. static plugin_data_t plugin_info_data;
  42. static plugin_funcs_t plugin_info_funcs;
  43. static general_data_t plugin_info_general_data;
  44. static general_funcs_t plugin_info_general_funcs;
  45. static snd_output_data_t plugin_info_snd_output_data;
  46. static snd_output_funcs_t plugin_info_snd_output_funcs;
  47. static cvar_t *snd_bits;
  48. static cvar_t *snd_device;
  49. static cvar_t *snd_rate;
  50. static cvar_t *snd_stereo;
  51. #define QF_ALSA_NEED(ret, func, params) \
  52. static ret (*qf##func) params;
  53. #include "alsa_funcs_list.h"
  54. #undef QF_ALSA_NEED
  55. static qboolean
  56. load_libasound (void)
  57. {
  58. if (!(alsa_handle = dlopen ("libasound.so.2", RTLD_GLOBAL | RTLD_NOW))) {
  59. Sys_Printf ("Couldn't load libasound.so.2: %s\n", dlerror ());
  60. return false;
  61. }
  62. #define QF_ALSA_NEED(ret, func, params) \
  63. if (!(qf##func = dlsym (alsa_handle, #func))) { \
  64. Sys_Printf ("Couldn't load ALSA function %s\n", #func); \
  65. dlclose (alsa_handle); \
  66. alsa_handle = 0; \
  67. return false; \
  68. }
  69. #include "alsa_funcs_list.h"
  70. #undef QF_ALSA_NEED
  71. return true;
  72. }
  73. #define snd_pcm_hw_params_sizeof qfsnd_pcm_hw_params_sizeof
  74. #define snd_pcm_sw_params_sizeof qfsnd_pcm_sw_params_sizeof
  75. static void
  76. SNDDMA_Init_Cvars (void)
  77. {
  78. snd_stereo = Cvar_Get ("snd_stereo", "1", CVAR_ROM, NULL,
  79. "sound stereo output");
  80. snd_rate = Cvar_Get ("snd_rate", "0", CVAR_ROM, NULL,
  81. "sound playback rate. 0 is system default");
  82. snd_device = Cvar_Get ("snd_device", "", CVAR_ROM, NULL,
  83. "sound device. \"\" is system default");
  84. snd_bits = Cvar_Get ("snd_bits", "0", CVAR_ROM, NULL,
  85. "sound sample depth. 0 is system default");
  86. }
  87. static int SNDDMA_GetDMAPos (void);
  88. static snd_pcm_uframes_t
  89. round_buffer_size (snd_pcm_uframes_t sz)
  90. {
  91. snd_pcm_uframes_t mask = ~0;
  92. while (sz & mask) {
  93. sz &= mask;
  94. mask <<= 1;
  95. }
  96. return sz;
  97. }
  98. static volatile dma_t *
  99. SNDDMA_Init (void)
  100. {
  101. int err;
  102. int bps = -1, stereo = -1;
  103. unsigned int rate = 0;
  104. snd_pcm_hw_params_t *hw;
  105. snd_pcm_hw_params_t **_hw = &hw;
  106. snd_pcm_sw_params_t *sw;
  107. snd_pcm_sw_params_t **_sw = &sw;
  108. snd_pcm_uframes_t frag_size;
  109. if (!load_libasound ())
  110. return false;
  111. snd_pcm_hw_params_alloca (_hw);
  112. snd_pcm_sw_params_alloca (_sw);
  113. if (snd_device->string[0])
  114. pcmname = snd_device->string;
  115. if (snd_bits->int_val) {
  116. bps = snd_bits->int_val;
  117. if (bps != 16 && bps != 8) {
  118. Sys_Printf ("Error: invalid sample bits: %d\n", bps);
  119. return 0;
  120. }
  121. }
  122. if (snd_rate->int_val) {
  123. rate = snd_rate->int_val;
  124. if (rate != 44100 && rate != 22050 && rate != 11025) {
  125. Sys_Printf ("Error: invalid sample rate: %d\n", rate);
  126. return 0;
  127. }
  128. }
  129. stereo = snd_stereo->int_val;
  130. if (!pcmname)
  131. pcmname = "default";
  132. err = qfsnd_pcm_open (&pcm, pcmname, SND_PCM_STREAM_PLAYBACK,
  133. SND_PCM_NONBLOCK);
  134. if (0 > err) {
  135. Sys_Printf ("Error: audio open error: %s\n", qfsnd_strerror (err));
  136. return 0;
  137. }
  138. Sys_Printf ("Using PCM %s.\n", pcmname);
  139. err = qfsnd_pcm_hw_params_any (pcm, hw);
  140. if (0 > err) {
  141. Sys_Printf ("ALSA: error setting hw_params_any. %s\n",
  142. qfsnd_strerror (err));
  143. goto error;
  144. }
  145. err = qfsnd_pcm_hw_params_set_access (pcm, hw,
  146. SND_PCM_ACCESS_MMAP_INTERLEAVED);
  147. if (0 > err) {
  148. Sys_Printf ("ALSA: Failure to set noninterleaved PCM access. %s\n"
  149. "Note: Interleaved is not supported\n",
  150. qfsnd_strerror (err));
  151. goto error;
  152. }
  153. switch (bps) {
  154. case -1:
  155. err = qfsnd_pcm_hw_params_set_format (pcm, hw,
  156. SND_PCM_FORMAT_S16_LE);
  157. if (0 <= err) {
  158. bps = 16;
  159. } else if (0 <= (err = qfsnd_pcm_hw_params_set_format (pcm, hw,
  160. SND_PCM_FORMAT_U8))) {
  161. bps = 8;
  162. } else {
  163. Sys_Printf ("ALSA: no useable formats. %s\n",
  164. qfsnd_strerror (err));
  165. goto error;
  166. }
  167. break;
  168. case 8:
  169. case 16:
  170. err = qfsnd_pcm_hw_params_set_format (pcm, hw, bps == 8 ?
  171. SND_PCM_FORMAT_U8 :
  172. SND_PCM_FORMAT_S16);
  173. if (0 > err) {
  174. Sys_Printf ("ALSA: no usable formats. %s\n",
  175. qfsnd_strerror (err));
  176. goto error;
  177. }
  178. break;
  179. default:
  180. Sys_Printf ("ALSA: desired format not supported\n");
  181. goto error;
  182. }
  183. switch (stereo) {
  184. case -1:
  185. err = qfsnd_pcm_hw_params_set_channels (pcm, hw, 2);
  186. if (0 <= err) {
  187. stereo = 1;
  188. } else if (0 <= (err = qfsnd_pcm_hw_params_set_channels (pcm, hw,
  189. 1))) {
  190. stereo = 0;
  191. } else {
  192. Sys_Printf ("ALSA: no usable channels. %s\n",
  193. qfsnd_strerror (err));
  194. goto error;
  195. }
  196. break;
  197. case 0:
  198. case 1:
  199. err = qfsnd_pcm_hw_params_set_channels (pcm, hw, stereo ? 2 : 1);
  200. if (0 > err) {
  201. Sys_Printf ("ALSA: no usable channels. %s\n",
  202. qfsnd_strerror (err));
  203. goto error;
  204. }
  205. break;
  206. default:
  207. Sys_Printf ("ALSA: desired channels not supported\n");
  208. goto error;
  209. }
  210. switch (rate) {
  211. case 0:
  212. rate = 44100;
  213. err = qfsnd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0);
  214. if (0 <= err) {
  215. frag_size = 32 * bps;
  216. } else {
  217. rate = 22050;
  218. err = qfsnd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0);
  219. if (0 <= err) {
  220. frag_size = 16 * bps;
  221. } else {
  222. rate = 11025;
  223. err = qfsnd_pcm_hw_params_set_rate_near (pcm, hw, &rate,
  224. 0);
  225. if (0 <= err) {
  226. frag_size = 8 * bps;
  227. } else {
  228. Sys_Printf ("ALSA: no usable rates. %s\n",
  229. qfsnd_strerror (err));
  230. goto error;
  231. }
  232. }
  233. }
  234. break;
  235. case 11025:
  236. case 22050:
  237. case 44100:
  238. err = qfsnd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0);
  239. if (0 > err) {
  240. Sys_Printf ("ALSA: desired rate %i not supported. %s\n", rate,
  241. qfsnd_strerror (err));
  242. goto error;
  243. }
  244. frag_size = 8 * bps * rate / 11025;
  245. break;
  246. default:
  247. Sys_Printf ("ALSA: desired rate %i not supported.\n", rate);
  248. goto error;
  249. }
  250. err = qfsnd_pcm_hw_params_set_period_size_near (pcm, hw, &frag_size, 0);
  251. if (0 > err) {
  252. Sys_Printf ("ALSA: unable to set period size near %i. %s\n",
  253. (int) frag_size, qfsnd_strerror (err));
  254. goto error;
  255. }
  256. err = qfsnd_pcm_hw_params (pcm, hw);
  257. if (0 > err) {
  258. Sys_Printf ("ALSA: unable to install hw params: %s\n",
  259. qfsnd_strerror (err));
  260. goto error;
  261. }
  262. err = qfsnd_pcm_sw_params_current (pcm, sw);
  263. if (0 > err) {
  264. Sys_Printf ("ALSA: unable to determine current sw params. %s\n",
  265. qfsnd_strerror (err));
  266. goto error;
  267. }
  268. err = qfsnd_pcm_sw_params_set_start_threshold (pcm, sw, ~0U);
  269. if (0 > err) {
  270. Sys_Printf ("ALSA: unable to set playback threshold. %s\n",
  271. qfsnd_strerror (err));
  272. goto error;
  273. }
  274. err = qfsnd_pcm_sw_params_set_stop_threshold (pcm, sw, ~0U);
  275. if (0 > err) {
  276. Sys_Printf ("ALSA: unable to set playback stop threshold. %s\n",
  277. qfsnd_strerror (err));
  278. goto error;
  279. }
  280. err = qfsnd_pcm_sw_params (pcm, sw);
  281. if (0 > err) {
  282. Sys_Printf ("ALSA: unable to install sw params. %s\n",
  283. qfsnd_strerror (err));
  284. goto error;
  285. }
  286. memset ((dma_t *) &sn, 0, sizeof (sn));
  287. sn.channels = stereo + 1;
  288. // don't mix less than this in frames:
  289. err = qfsnd_pcm_hw_params_get_period_size (hw, (snd_pcm_uframes_t *)
  290. (char *)
  291. &sn.submission_chunk, 0);
  292. if (0 > err) {
  293. Sys_Printf ("ALSA: unable to get period size. %s\n",
  294. qfsnd_strerror (err));
  295. goto error;
  296. }
  297. sn.framepos = 0;
  298. sn.samplebits = bps;
  299. err = qfsnd_pcm_hw_params_get_buffer_size (hw, &buffer_size);
  300. if (0 > err) {
  301. Sys_Printf ("ALSA: unable to get buffer size. %s\n",
  302. qfsnd_strerror (err));
  303. goto error;
  304. }
  305. if (buffer_size != round_buffer_size (buffer_size)) {
  306. Sys_Printf ("ALSA: WARNING: non-power of 2 buffer size. sound may be unsatisfactory\n");
  307. Sys_Printf ("recommend using either the plughw, or hw devices or adjusting dmix\n");
  308. Sys_Printf ("to have a power of 2 buffer size\n");
  309. }
  310. sn.frames = buffer_size;
  311. sn.speed = rate;
  312. SNDDMA_GetDMAPos (); //XXX sets sn.buffer
  313. Sys_Printf ("%5d channels\n", sn.channels);
  314. Sys_Printf ("%5d samples\n", sn.frames);
  315. Sys_Printf ("%5d samplepos\n", sn.framepos);
  316. Sys_Printf ("%5d samplebits\n", sn.samplebits);
  317. Sys_Printf ("%5d submission_chunk\n", sn.submission_chunk);
  318. Sys_Printf ("%5d speed\n", sn.speed);
  319. Sys_Printf ("0x%lx dma buffer\n", (long) sn.buffer);
  320. snd_inited = 1;
  321. return &sn;
  322. error:
  323. qfsnd_pcm_close (pcm);
  324. return 0;
  325. }
  326. static int
  327. SNDDMA_GetDMAPos (void)
  328. {
  329. const snd_pcm_channel_area_t *areas;
  330. snd_pcm_uframes_t offset;
  331. snd_pcm_uframes_t nframes = sn.frames;
  332. qfsnd_pcm_avail_update (pcm);
  333. qfsnd_pcm_mmap_begin (pcm, &areas, &offset, &nframes);
  334. sn.framepos = offset;
  335. sn.buffer = areas->addr; //XXX FIXME there's an area per channel
  336. return sn.framepos;
  337. }
  338. static void
  339. SNDDMA_Shutdown (void)
  340. {
  341. if (snd_inited) {
  342. qfsnd_pcm_close (pcm);
  343. snd_inited = 0;
  344. }
  345. }
  346. /*
  347. SNDDMA_Submit
  348. Send sound to device if buffer isn't really the dma buffer
  349. */
  350. static void
  351. SNDDMA_Submit (void)
  352. {
  353. int state;
  354. int count = (*plugin_info_snd_output_data.paintedtime -
  355. *plugin_info_snd_output_data.soundtime);
  356. const snd_pcm_channel_area_t *areas;
  357. snd_pcm_uframes_t nframes;
  358. snd_pcm_uframes_t offset;
  359. if (snd_blocked)
  360. return;
  361. nframes = count;
  362. qfsnd_pcm_avail_update (pcm);
  363. qfsnd_pcm_mmap_begin (pcm, &areas, &offset, &nframes);
  364. state = qfsnd_pcm_state (pcm);
  365. switch (state) {
  366. case SND_PCM_STATE_PREPARED:
  367. qfsnd_pcm_mmap_commit (pcm, offset, nframes);
  368. qfsnd_pcm_start (pcm);
  369. break;
  370. case SND_PCM_STATE_RUNNING:
  371. qfsnd_pcm_mmap_commit (pcm, offset, nframes);
  372. break;
  373. default:
  374. break;
  375. }
  376. }
  377. static void
  378. SNDDMA_BlockSound (void)
  379. {
  380. if (snd_inited && ++snd_blocked == 1)
  381. qfsnd_pcm_pause (pcm, 1);
  382. }
  383. static void
  384. SNDDMA_UnblockSound (void)
  385. {
  386. if (!snd_inited || !snd_blocked)
  387. return;
  388. if (!--snd_blocked)
  389. qfsnd_pcm_pause (pcm, 0);
  390. }
  391. PLUGIN_INFO(snd_output, alsa)
  392. {
  393. plugin_info.type = qfp_snd_output;
  394. plugin_info.api_version = QFPLUGIN_VERSION;
  395. plugin_info.plugin_version = "0.1";
  396. plugin_info.description = "ALSA digital output";
  397. plugin_info.copyright = "Copyright (C) 1996-1997 id Software, Inc.\n"
  398. "Copyright (C) 1999,2000,2001 contributors of the QuakeForge "
  399. "project\n"
  400. "Please see the file \"AUTHORS\" for a list of contributors";
  401. plugin_info.functions = &plugin_info_funcs;
  402. plugin_info.data = &plugin_info_data;
  403. plugin_info_data.general = &plugin_info_general_data;
  404. plugin_info_data.input = NULL;
  405. plugin_info_data.snd_output = &plugin_info_snd_output_data;
  406. plugin_info_funcs.general = &plugin_info_general_funcs;
  407. plugin_info_funcs.input = NULL;
  408. plugin_info_funcs.snd_output = &plugin_info_snd_output_funcs;
  409. plugin_info_general_funcs.p_Init = SNDDMA_Init_Cvars;
  410. plugin_info_general_funcs.p_Shutdown = NULL;
  411. plugin_info_snd_output_funcs.pS_O_Init = SNDDMA_Init;
  412. plugin_info_snd_output_funcs.pS_O_Shutdown = SNDDMA_Shutdown;
  413. plugin_info_snd_output_funcs.pS_O_GetDMAPos = SNDDMA_GetDMAPos;
  414. plugin_info_snd_output_funcs.pS_O_Submit = SNDDMA_Submit;
  415. plugin_info_snd_output_funcs.pS_O_BlockSound = SNDDMA_BlockSound;
  416. plugin_info_snd_output_funcs.pS_O_UnblockSound = SNDDMA_UnblockSound;
  417. return &plugin_info;
  418. }