📄 alsa.c
字号:
/*mediastreamer2 library - modular sound and video processing and streamingCopyright (C) 2006 Simon MORLAT (simon.morlat@linphone.org)This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public Licenseas published by the Free Software Foundation; either version 2of 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 ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/#include <alsa/asoundlib.h>#include "mediastreamer2/msfilter.h"#include "mediastreamer2/mssndcard.h"//#define THREADED_VERSION/*in case of troubles with a particular driver, try incrementing ALSA_PERIOD_SIZEto 512, 1024, 2048, 4096...then try incrementing the number of periods*/#define ALSA_PERIODS 8#define ALSA_PERIOD_SIZE 256/*uncomment the following line if you have problems with an alsa driverhaving sound quality trouble:*//*#define EPIPE_BUGFIX 1*/static MSSndCard * alsa_card_new(int id);static MSSndCard *alsa_card_duplicate(MSSndCard *obj);static MSFilter * ms_alsa_read_new(const char *dev);static MSFilter * ms_alsa_write_new(const char *dev);struct _AlsaData{ char *pcmdev; char *mixdev;};typedef struct _AlsaData AlsaData;static int alsa_set_params(snd_pcm_t *pcm_handle, int rw, int bits, int stereo, int rate){ snd_pcm_hw_params_t *hwparams=NULL; snd_pcm_sw_params_t *swparams=NULL; int dir; uint exact_uvalue; unsigned long exact_ulvalue; int channels; int periods=ALSA_PERIODS; int periodsize=ALSA_PERIOD_SIZE; int err; int format; /* Allocate the snd_pcm_hw_params_t structure on the stack. */ snd_pcm_hw_params_alloca(&hwparams); /* Init hwparams with full configuration space */ if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) { ms_warning("alsa_set_params: Cannot configure this PCM device."); return -1; } if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { ms_warning("alsa_set_params: Error setting access."); return -1; } /* Set sample format */ format=SND_PCM_FORMAT_S16; if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) < 0) { ms_warning("alsa_set_params: Error setting format."); return -1; } /* Set number of channels */ if (stereo) channels=2; else channels=1; if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, channels) < 0) { ms_warning("alsa_set_params: Error setting channels."); return -1; } /* Set sample rate. If the exact rate is not supported */ /* by the hardware, use nearest possible rate. */ exact_uvalue=rate; dir=0; if ((err=snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_uvalue, &dir))<0){ ms_warning("alsa_set_params: Error setting rate to %i:%s",rate,snd_strerror(err)); return -1; } if (dir != 0) { ms_warning("alsa_set_params: The rate %d Hz is not supported by your hardware.\n " "==> Using %d Hz instead.", rate, exact_uvalue); } /* choose greater period size when rate is high */ periodsize=periodsize*(rate/8000); /* Set buffer size (in frames). The resulting latency is given by */ /* latency = periodsize * periods / (rate * bytes_per_frame) */ /* set period size */ exact_ulvalue=periodsize; dir=0; if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &exact_ulvalue, &dir) < 0) { ms_warning("alsa_set_params: Error setting period size."); return -1; } if (dir != 0) { ms_warning("alsa_set_params: The period size %d is not supported by your hardware.\n " "==> Using %d instead.", periodsize, (int)exact_ulvalue); } ms_warning("alsa_set_params: periodsize:%d Using %d", periodsize, (int)exact_ulvalue); periodsize=exact_ulvalue; /* Set number of periods. Periods used to be called fragments. */ exact_uvalue=periods; dir=0; if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &exact_uvalue, &dir) < 0) { ms_warning("alsa_set_params: Error setting periods."); return -1; } ms_warning("alsa_set_params: period:%d Using %d", periods, exact_uvalue); if (dir != 0) { ms_warning("alsa_set_params: The number of periods %d is not supported by your hardware.\n " "==> Using %d instead.", periods, exact_uvalue); } /* Apply HW parameter settings to */ /* PCM device and prepare device */ if ((err=snd_pcm_hw_params(pcm_handle, hwparams)) < 0) { ms_warning("alsa_set_params: Error setting HW params:%s",snd_strerror(err)); return -1; } /*prepare sw params */ if (rw){ snd_pcm_sw_params_alloca(&swparams); snd_pcm_sw_params_current(pcm_handle, swparams); if ((err=snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams,periodsize*2 ))<0){ ms_warning("alsa_set_params: Error setting start threshold:%s",snd_strerror(err)); } if ((err=snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams,periodsize*periods ))<0){ ms_warning("alsa_set_params: Error setting stop threshold:%s",snd_strerror(err)); } if ((err=snd_pcm_sw_params(pcm_handle, swparams))<0){ ms_warning("alsa_set_params: Error setting SW params:%s",snd_strerror(err)); return -1; } } return 0; }#ifdef EPIPE_BUGFIXstatic void alsa_fill_w (snd_pcm_t *pcm_handle){ snd_pcm_hw_params_t *hwparams=NULL; int channels; snd_pcm_uframes_t buffer_size; int buffer_size_bytes; void *buffer; /* Allocate the snd_pcm_hw_params_t structure on the stack. */ snd_pcm_hw_params_alloca(&hwparams); snd_pcm_hw_params_current(pcm_handle, hwparams); /* get channels */ snd_pcm_hw_params_get_channels (hwparams, &channels); /* get buffer size */ snd_pcm_hw_params_get_buffer_size (hwparams, &buffer_size); /* fill half */ buffer_size /= 2; /* allocate buffer assuming 2 bytes per sample */ buffer_size_bytes = buffer_size * channels * 2; buffer = alloca (buffer_size_bytes); memset (buffer, 0, buffer_size_bytes); /* write data */ snd_pcm_writei(pcm_handle, buffer, buffer_size);}#endifstatic snd_pcm_t * alsa_open_r(const char *pcmdev,int bits,int stereo,int rate){ snd_pcm_t *pcm_handle; int err; ms_message("alsa_open_r: opening %s at %iHz, bits=%i, stereo=%i",pcmdev,rate,bits,stereo);#ifndef THREADED_VERSION if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_CAPTURE,SND_PCM_NONBLOCK) < 0) { ms_warning("alsa_open_r: Error opening PCM device %s",pcmdev ); return NULL; }#else /* want blocking mode for threaded version */ if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_CAPTURE,0) < 0) { ms_warning("alsa_open_r: Error opening PCM device %s",pcmdev ); return NULL; }#endif { struct timeval tv1; struct timeval tv2; struct timezone tz; int diff = 0; err = gettimeofday(&tv1, &tz); while (1) { if (!(alsa_set_params(pcm_handle,0,bits,stereo,rate)<0)){ ms_message("alsa_open_r: Audio params set"); break; } if (!gettimeofday(&tv2, &tz) && !err) { diff = ((tv2.tv_sec - tv1.tv_sec) * 1000000) + (tv2.tv_usec - tv1.tv_usec); } else { diff = -1; } if ((diff < 0) || (diff > 3000000)) { /* 3 secondes */ ms_error("alsa_open_r: Error setting params for more than 3 seconds"); snd_pcm_close(pcm_handle); return NULL; } ms_warning("alsa_open_r: Error setting params (for %d micros)", diff); usleep(200000); } } err=snd_pcm_start(pcm_handle); if (err<0){ ms_warning("snd_pcm_start() failed: %s", snd_strerror(err)); } return pcm_handle;}static snd_pcm_t * alsa_open_w(const char *pcmdev,int bits,int stereo,int rate){ snd_pcm_t *pcm_handle; if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_PLAYBACK,SND_PCM_NONBLOCK) < 0) { ms_warning("alsa_open_w: Error opening PCM device %s",pcmdev ); return NULL; } { struct timeval tv1; struct timeval tv2; struct timezone tz; int diff = 0; int err; err = gettimeofday(&tv1, &tz); while (1) { if (!(alsa_set_params(pcm_handle,1,bits,stereo,rate)<0)){ ms_message("alsa_open_w: Audio params set"); break; } if (!gettimeofday(&tv2, &tz) && !err) { diff = ((tv2.tv_sec - tv1.tv_sec) * 1000000) + (tv2.tv_usec - tv1.tv_usec); } else { diff = -1; } if ((diff < 0) || (diff > 3000000)) { /* 3 secondes */ ms_error("alsa_open_w: Error setting params for more than 3 seconds"); snd_pcm_close(pcm_handle); return NULL; } ms_warning("alsa_open_w: Error setting params (for %d micros)", diff); usleep(200000); } } return pcm_handle;}static int alsa_can_read(snd_pcm_t *dev){ snd_pcm_sframes_t avail; int err; avail = snd_pcm_avail_update(dev); if (avail < 0) { ms_error("snd_pcm_avail_update: %s", snd_strerror(avail)); // most probably -EPIPE /* overrun occured, snd_pcm_state() would return SND_PCM_STATE_XRUN FIXME: handle other error conditions*/ ms_error("*** alsa_can_read fixup, trying to recover"); snd_pcm_drain(dev); /* Ignore possible error, at least -EAGAIN.*/ err = snd_pcm_recover(dev, avail, 0); if (err){ ms_error("snd_pcm_recover() failed with err %d: %s", err, snd_strerror(err)); return -1; } err = snd_pcm_start(dev); if (err){ ms_error("snd_pcm_start() failed with err %d: %s", err, snd_strerror(err)); return -1; } ms_message("Recovery done"); } return avail;}static int alsa_read(snd_pcm_t *handle,unsigned char *buf,int nsamples){ int err; err=snd_pcm_readi(handle,buf,nsamples); if (err<0) { ms_warning("alsa_read: snd_pcm_readi() returned %i",err); if (err==-EPIPE){ snd_pcm_prepare(handle); err=snd_pcm_readi(handle,buf,nsamples); if (err<0) ms_warning("alsa_read: snd_pcm_readi() failed:%s.",snd_strerror(err)); }else if (err!=-EWOULDBLOCK){ ms_warning("alsa_read: snd_pcm_readi() failed:%s.",snd_strerror(err)); } }else if (err==0){ ms_warning("alsa_read: snd_pcm_readi() returned 0"); } return err;}static int alsa_write(snd_pcm_t *handle,unsigned char *buf,int nsamples){ int err; if ((err=snd_pcm_writei(handle,buf,nsamples))<0){ if (err==-EPIPE){ snd_pcm_prepare(handle);#ifdef EPIPE_BUGFIX alsa_fill_w (handle);#endif err=snd_pcm_writei(handle,buf,nsamples); if (err<0) ms_warning("alsa_card_write: Error writing sound buffer (nsamples=%i):%s",nsamples,snd_strerror(err)); }else if (err!=-EWOULDBLOCK){ ms_warning("alsa_card_write: snd_pcm_writei() failed:%s.",snd_strerror(err)); } }else if (err!=nsamples) { ms_debug("Only %i samples written instead of %i",err,nsamples); } return err;}static snd_mixer_t *alsa_mixer_open(const char *mixdev){ snd_mixer_t *mixer=NULL; int err; err=snd_mixer_open(&mixer,0); if (err<0){ ms_warning("Could not open alsa mixer: %s",snd_strerror(err)); return NULL; } if ((err = snd_mixer_attach (mixer, mixdev)) < 0){ ms_warning("Could not attach mixer to card: %s",snd_strerror(err)); snd_mixer_close(mixer); return NULL; } if ((err = snd_mixer_selem_register (mixer, NULL, NULL)) < 0){ ms_warning("snd_mixer_selem_register: %s",snd_strerror(err)); snd_mixer_close(mixer); return NULL; } if ((err = snd_mixer_load (mixer)) < 0){ ms_warning("snd_mixer_load: %s",snd_strerror(err)); snd_mixer_close(mixer); return NULL; } return mixer;}static void alsa_mixer_close(snd_mixer_t *mix){ snd_mixer_close(mix);}typedef enum {CAPTURE, PLAYBACK, CAPTURE_SWITCH, PLAYBACK_SWITCH} MixerAction;static int get_mixer_element(snd_mixer_t *mixer,const char *name, MixerAction action){ long value=0; const char *elemname; snd_mixer_elem_t *elem; int err; long sndMixerPMin=0; long sndMixerPMax=0; long newvol=0; elem=snd_mixer_first_elem(mixer); while (elem!=NULL){ elemname=snd_mixer_selem_get_name(elem); //ms_message("Found alsa mixer element %s.",elemname); if (strcmp(elemname,name)==0){ switch (action){ case CAPTURE: if (snd_mixer_selem_has_capture_volume(elem)){ snd_mixer_selem_get_capture_volume_range(elem, &sndMixerPMin, &sndMixerPMax); err=snd_mixer_selem_get_capture_volume(elem,SND_MIXER_SCHN_UNKNOWN,&newvol); newvol-=sndMixerPMin; value=(100*newvol)/(sndMixerPMax-sndMixerPMin); if (err<0) ms_warning("Could not get capture volume for %s:%s",name,snd_strerror(err)); //else ms_message("Successfully get capture level for %s.",elemname); break; } break; case PLAYBACK: if (snd_mixer_selem_has_playback_volume(elem)){ snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); err=snd_mixer_selem_get_playback_volume(elem,SND_MIXER_SCHN_FRONT_LEFT,&newvol); newvol-=sndMixerPMin; value=(100*newvol)/(sndMixerPMax-sndMixerPMin); if (err<0) ms_warning("Could not get playback volume for %s:%s",name,snd_strerror(err)); //else ms_message("Successfully get playback level for %s.",elemname); break; } break; case CAPTURE_SWITCH: break; case PLAYBACK_SWITCH: break; } } elem=snd_mixer_elem_next(elem); } return value;}static void set_mixer_element(snd_mixer_t *mixer,const char *name, int level,MixerAction action){ const char *elemname; snd_mixer_elem_t *elem; long sndMixerPMin=0; long sndMixerPMax=0; long newvol=0; elem=snd_mixer_first_elem(mixer); while (elem!=NULL){ elemname=snd_mixer_selem_get_name(elem); //ms_message("Found alsa mixer element %s.",elemname); if (strcmp(elemname,name)==0){ switch(action){ case CAPTURE: if (snd_mixer_selem_has_capture_volume(elem)){ snd_mixer_selem_get_capture_volume_range(elem, &sndMixerPMin, &sndMixerPMax); newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin; snd_mixer_selem_set_capture_volume_all(elem,newvol); //ms_message("Successfully set capture level for %s.",elemname); return; } break; case PLAYBACK: if (snd_mixer_selem_has_playback_volume(elem)){ snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin; snd_mixer_selem_set_playback_volume_all(elem,newvol); //ms_message("Successfully set playback level for %s.",elemname); return; } break; case CAPTURE_SWITCH: if (snd_mixer_selem_has_capture_switch(elem)){ snd_mixer_selem_set_capture_switch_all(elem,level); //ms_message("Successfully set capture switch for %s.",elemname); } break; case PLAYBACK_SWITCH: if (snd_mixer_selem_has_playback_switch(elem)){ snd_mixer_selem_set_playback_switch_all(elem,level); //ms_message("Successfully set capture switch for %s.",elemname); } break; } } elem=snd_mixer_elem_next(elem); } return ;}static void alsa_card_set_level(MSSndCard *obj,MSSndCardMixerElem e,int a){ snd_mixer_t *mixer; AlsaData *ad=(AlsaData*)obj->data; mixer=alsa_mixer_open(ad->mixdev); if (mixer==NULL) return ; switch(e){ case MS_SND_CARD_MASTER: set_mixer_element(mixer,"Master",a,PLAYBACK); break; case MS_SND_CARD_CAPTURE: set_mixer_element(mixer,"Capture",a,CAPTURE); break; case MS_SND_CARD_PLAYBACK: set_mixer_element(mixer,"PCM",a,PLAYBACK); break; default: ms_warning("alsa_card_set_level: unsupported command.");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -