📄 audio.c
字号:
/* XMMS - ALSA output plugin * Copyright (C) 2001-2003 Matthieu Sozeau <mattam@altern.org> * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, * Thomas Nilsson and 4Front Technologies * Copyright (C) 1999-2004 Haavard Kvaalen * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */#include "alsa.h"#include <ctype.h>#include <libxmms/xconvert.h>static snd_pcm_t *alsa_pcm = NULL;static snd_pcm_status_t *alsa_status = NULL;static snd_pcm_channel_area_t *areas = NULL;static snd_output_t *logs = NULL;static int alsa_bps = 0;static guint64 alsa_total_written = 0;/* Set/Get volume */static snd_mixer_elem_t *pcm_element = NULL;static snd_mixer_t *mixer = NULL;static gboolean mmap, force_start, going, paused;static gpointer buffer;static int alsa_can_pause;struct snd_format { unsigned int rate; unsigned int channels; snd_pcm_format_t format; AFormat xmms_format;};static struct snd_format *inputf = NULL;static struct snd_format *effectf = NULL;static struct snd_format *outputf = NULL;static int alsa_setup(struct snd_format *f);static void alsa_mmap_audio(char *data, int length);static void alsa_write_audio(gpointer data, int length);static struct snd_format * snd_format_from_xmms(AFormat fmt, int rate, int channels);static struct xmms_convert_buffers *convertb;static convert_func_t alsa_convert_func;static convert_channel_func_t alsa_stereo_convert_func;static convert_freq_func_t alsa_frequency_convert_func;static const struct { AFormat xmms; snd_pcm_format_t alsa;} format_table[] ={{FMT_S16_LE, SND_PCM_FORMAT_S16_LE}, {FMT_S16_BE, SND_PCM_FORMAT_S16_BE}, {FMT_S16_NE,#ifdef WORDS_BIGENDIAN SND_PCM_FORMAT_S16_BE#else SND_PCM_FORMAT_S16_LE#endif }, {FMT_U16_LE, SND_PCM_FORMAT_U16_LE}, {FMT_U16_BE, SND_PCM_FORMAT_U16_BE}, {FMT_U16_NE,#ifdef WORDS_BIGENDIAN SND_PCM_FORMAT_U16_BE#else SND_PCM_FORMAT_U16_LE#endif }, {FMT_U8, SND_PCM_FORMAT_U8}, {FMT_S8, SND_PCM_FORMAT_S8},};static void debug(char *str, ...) G_GNUC_PRINTF(1, 2);static void debug(char *str, ...){ va_list args; if (alsa_cfg.debug) { va_start(args, str); g_logv(NULL, G_LOG_LEVEL_MESSAGE, str, args); va_end(args); }}int alsa_playing(void){ if (!going || paused) return FALSE; return(snd_pcm_state(alsa_pcm) == SND_PCM_STATE_RUNNING);}static void xrun_recover(void){ int err; if (alsa_cfg.debug) { snd_pcm_status_alloca(&alsa_status); if ((err = snd_pcm_status(alsa_pcm, alsa_status)) < 0) g_warning("xrun_recover(): snd_pcm_status() failed"); else { printf("Status:\n"); snd_pcm_status_dump(alsa_status, logs); } } if (snd_pcm_state(alsa_pcm) == SND_PCM_STATE_XRUN) { if ((err = snd_pcm_prepare(alsa_pcm)) < 0) g_warning("xrun_recover(): snd_pcm_prepare() failed."); }}static snd_pcm_sframes_t alsa_get_avail(void){ snd_pcm_sframes_t ret; if ((ret = snd_pcm_avail_update(alsa_pcm)) == -EPIPE) xrun_recover(); else if (ret < 0) { g_warning("alsa_get_avail(): snd_pcm_avail_update() failed: %s", snd_strerror(-ret)); return 0; } else return ret; if ((ret = snd_pcm_avail_update(alsa_pcm)) < 0) { g_warning("alsa_get_avail(): snd_pcm_avail_update() failed: %s", snd_strerror(-ret)); return 0; } return ret;}int alsa_free(void){ if (paused) return 0; else { int err; if (force_start && snd_pcm_state(alsa_pcm) == SND_PCM_STATE_PREPARED) { if ((err = snd_pcm_start(alsa_pcm)) < 0) g_warning("alsa_free(): snd_pcm_start() " "failed: %s", snd_strerror(-err)); else debug("Stream started"); } force_start = TRUE; return snd_pcm_frames_to_bytes(alsa_pcm, alsa_get_avail()); }}void alsa_pause(short p){ debug("alsa_pause"); if (p) paused = TRUE; if (alsa_can_pause) snd_pcm_pause(alsa_pcm, p); else if (p) snd_pcm_drop(alsa_pcm); if (!p) paused = FALSE;}void alsa_close(void){ int err, started; debug("Closing device"); started = going; going = 0; pcm_element = NULL; if (mixer) { snd_mixer_close(mixer); mixer = NULL; } if (alsa_pcm != NULL) { if (started) if ((err = snd_pcm_drop(alsa_pcm)) < 0) g_warning("alsa_pcm_drop() failed: %s", snd_strerror(-err)); if ((err = snd_pcm_close(alsa_pcm)) < 0) g_warning("alsa_pcm_close() failed: %s", snd_strerror(-err)); alsa_pcm = NULL; } if (mmap) { g_free(buffer); buffer = NULL; g_free(areas); areas = NULL; } xmms_convert_buffers_destroy(convertb); convertb = NULL; g_free(inputf); inputf = NULL; g_free(effectf); effectf = NULL; alsa_save_config(); debug("Device closed");}static void alsa_reopen(struct snd_format *f){ unsigned int tmp = alsa_get_written_time(); if (alsa_pcm != NULL) { snd_pcm_close(alsa_pcm); alsa_pcm = NULL; } if (mmap) { g_free(buffer); buffer = NULL; g_free(areas); areas = NULL; } if (alsa_setup(f) < 0) g_warning("Failed to reopen the audio device"); alsa_total_written = tmp; snd_pcm_prepare(alsa_pcm);}void alsa_flush(int time){ alsa_total_written = (guint64) time * alsa_bps / 1000;}static void parse_mixer_name(char *str, char **name, int *index){ char *end; while (isspace(*str)) str++; if ((end = strchr(str, ',')) != NULL) { *name = g_strndup(str, end - str); end++; *index = atoi(end); } else { *name = g_strdup(str); *index = 0; }}int alsa_get_mixer(snd_mixer_t **mixer, int card) { char *dev; int err; debug("alsa_get_mixer"); dev = g_strdup_printf("hw:%i", card); if ((err = snd_mixer_open(mixer, 0)) < 0) { g_warning("alsa_get_mixer(): Failed to open empty mixer: %s", snd_strerror(-err)); mixer = NULL; return -1; } if ((err = snd_mixer_attach(*mixer, dev)) < 0) { g_warning("alsa_get_mixer(): Attaching to mixer %s failed: %s", dev, snd_strerror(-err)); return -1; } if ((err = snd_mixer_selem_register(*mixer, NULL, NULL)) < 0) { g_warning("alsa_get_mixer(): Failed to register mixer: %s", snd_strerror(-err)); return -1; } if ((err = snd_mixer_load(*mixer)) < 0) { g_warning("alsa_get_mixer(): Failed to load mixer: %s", snd_strerror(-err)); return -1; } g_free(dev); return (*mixer != NULL);} snd_mixer_elem_t* alsa_get_mixer_elem(snd_mixer_t *mixer, char *name, int index){ snd_mixer_selem_id_t *selem_id; snd_mixer_elem_t* elem; snd_mixer_selem_id_alloca(&selem_id); if (index != -1) snd_mixer_selem_id_set_index(selem_id, index); if (name != NULL) snd_mixer_selem_id_set_name(selem_id, name); elem = snd_mixer_find_selem(mixer, selem_id); return elem;}int alsa_setup_mixer(void){ char *name; long int a, b; long alsa_min_vol, alsa_max_vol; int err, index; debug("alsa_setup_mixer"); if ((err = alsa_get_mixer(&mixer, alsa_cfg.mixer_card)) < 0) return err; parse_mixer_name(alsa_cfg.mixer_device, &name, &index); pcm_element = alsa_get_mixer_elem(mixer, name, index); g_free(name); if (!pcm_element) { g_warning("alsa_setup_mixer(): Failed to find mixer element: %s", alsa_cfg.mixer_device); return -1; } /* * Work around a bug in alsa-lib up to 1.0.0rc2 where the * new range don't take effect until the volume is changed. * This hack should be removed once we depend on Alsa 1.0.0. */ snd_mixer_selem_get_playback_volume(pcm_element, SND_MIXER_SCHN_FRONT_LEFT, &a); snd_mixer_selem_get_playback_volume(pcm_element, SND_MIXER_SCHN_FRONT_RIGHT, &b); snd_mixer_selem_get_playback_volume_range(pcm_element, &alsa_min_vol, &alsa_max_vol); snd_mixer_selem_set_playback_volume_range(pcm_element, 0, 100); if (alsa_max_vol == 0) { pcm_element = NULL; return -1; } if (!alsa_cfg.soft_volume) alsa_set_volume(a * 100 / alsa_max_vol, b * 100 / alsa_max_vol); debug("alsa_setup_mixer: end"); return 0;}void alsa_get_volume(int *l, int *r){ static gboolean first = TRUE; long ll = *l, lr = *r; if (first) { alsa_setup_mixer(); first = !first; } if (!pcm_element) return; snd_mixer_handle_events(mixer); if (alsa_cfg.soft_volume) { *l = alsa_cfg.vol.left; *r = alsa_cfg.vol.right; } else { snd_mixer_selem_get_playback_volume(pcm_element, SND_MIXER_SCHN_FRONT_LEFT, &ll); snd_mixer_selem_get_playback_volume(pcm_element, SND_MIXER_SCHN_FRONT_RIGHT, &lr); *l = ll; *r = lr; }}void alsa_set_volume(int l, int r){ if (!pcm_element) return; if (alsa_cfg.soft_volume) { alsa_cfg.vol.left = l; alsa_cfg.vol.right = r; } else { snd_mixer_selem_set_playback_volume(pcm_element, SND_MIXER_SCHN_FRONT_LEFT, l); snd_mixer_selem_set_playback_volume(pcm_element, SND_MIXER_SCHN_FRONT_RIGHT, r); }}int alsa_get_output_time(void){ snd_pcm_sframes_t delay; ssize_t db = 0; if (!going) return 0; if (!snd_pcm_delay(alsa_pcm, &delay)) db = snd_pcm_frames_to_bytes(alsa_pcm, delay); if (db < alsa_total_written) return ((alsa_total_written - db) * 1000 / alsa_bps); return 0;}int alsa_get_written_time(void){ return (alsa_total_written * 1000 / alsa_bps);}#define STEREO_ADJUST(type, type2, endian) \do { \ type *ptr = data; \ for (i = 0; i < length; i += 4) \ { \ *ptr = type2##_TO_##endian(type2##_FROM_## endian(*ptr) * \ alsa_cfg.vol.left / 100); \ ptr++; \ *ptr = type2##_TO_##endian(type2##_FROM_##endian(*ptr) * \ alsa_cfg.vol.right / 100); \ ptr++; \ } \} while (0)#define MONO_ADJUST(type, type2, endian) \do { \ type *ptr = data; \ for (i = 0; i < length; i += 4) \ { \ *ptr = type2##_TO_##endian(type2##_FROM_## endian(*ptr) * \ vol / 100); \ ptr++; \ } \} while (0)#define VOLUME_ADJUST(type, type2, endian) \do { \ if (channels == 2) \ STEREO_ADJUST(type, type2, endian); \ else \ MONO_ADJUST(type, type2, endian); \} while (0) #define STEREO_ADJUST8(type) \do { \
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -