blob: ebf2909af8f4faca97b7ca70148e9a5951882d3b [file] [log] [blame]
/*
The mediastreamer library aims at providing modular media processing and I/O
for linphone, but also for any telephony application.
Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "alsacard.h"
#ifdef HAVE_ALSA_ASOUNDLIB_H
static gchar *over_pcmdev=NULL;
#include "msossread.h"
#include "msosswrite.h"
#include <signal.h>
int __alsa_card_write(AlsaCard *obj,char *buf,int size);
int alsa_set_params(AlsaCard *obj, int rw, int bits, int stereo, int rate)
{
snd_pcm_hw_params_t *hwparams=NULL;
snd_pcm_sw_params_t *swparams=NULL;
snd_pcm_t *pcm_handle;
gint dir,exact_value;
gint channels;
gint fsize=0;
gint periods=8;
gint periodsize=256;
gint err;
int format;
if (rw) {
pcm_handle=obj->write_handle;
}
else pcm_handle=obj->read_handle;
/* 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) {
g_warning("alsa_set_params: Cannot configure this PCM device.\n");
return(-1);
}
if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
g_warning("alsa_set_params: Error setting access.\n");
return(-1);
}
/* Set sample format */
#ifdef WORDS_BIGENDIAN
format=SND_PCM_FORMAT_S16_BE;
#else
format=SND_PCM_FORMAT_S16_LE;
#endif
if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) < 0) {
g_warning("alsa_set_params: Error setting format.\n");
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) {
g_warning("alsa_set_params: Error setting channels.\n");
return(-1);
}
/* Set sample rate. If the exact rate is not supported */
/* by the hardware, use nearest possible rate. */
exact_value=rate;
dir=0;
if ((err=snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_value, &dir))<0){
g_warning("alsa_set_params: Error setting rate to %i:%s",rate,snd_strerror(err));
return -1;
}
if (dir != 0) {
g_warning("alsa_set_params: The rate %d Hz is not supported by your hardware.\n "
"==> Using %d Hz instead.\n", rate, exact_value);
}
/* 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) */
/*
fsize=periodsize * periods;
exact_value=fsize;
if ((err=snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams,&exact_value)) < 0) {
g_warning("alsa_set_params: Error setting buffer size:%s",snd_strerror(err));
return(-1);
}
if (fsize!= exact_value) {
g_warning("alsa_set_params: The buffer size %d is not supported by your hardware.\n "
"==> Using %d instead.\n", fsize, exact_value);
}
*/
/* set period size */
exact_value=periodsize;
dir=0;
if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &exact_value, &dir) < 0) {
g_warning("alsa_set_params: Error setting period size.\n");
return(-1);
}
if (dir != 0) {
g_warning("alsa_set_params: The period size %d is not supported by your hardware.\n "
"==> Using %d instead.\n", periodsize, exact_value);
}
periodsize=exact_value;
/* Set number of periods. Periods used to be called fragments. */
exact_value=periods;
dir=0;
if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &exact_value, &dir) < 0) {
g_warning("alsa_set_params: Error setting periods.\n");
return(-1);
}
if (dir != 0) {
g_warning("alsa_set_params: The number of periods %d is not supported by your hardware.\n "
"==> Using %d instead.\n", periods, exact_value);
}
/* Apply HW parameter settings to */
/* PCM device and prepare device */
if ((err=snd_pcm_hw_params(pcm_handle, hwparams)) < 0) {
g_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){
g_warning("alsa_set_params: Error setting start threshold:%s",snd_strerror(err));
return -1;
}
if ((err=snd_pcm_sw_params(pcm_handle, swparams))<0){
g_warning("alsa_set_params: Error setting SW params:%s",snd_strerror(err));
return(-1);
}
}
obj->frame_size=channels*(bits/8);
SND_CARD(obj)->bsize=periodsize*obj->frame_size;
//SND_CARD(obj)->bsize=4096;
obj->frames=periodsize;
g_message("alsa_set_params: blocksize=%i.",SND_CARD(obj)->bsize);
return SND_CARD(obj)->bsize;
}
int alsa_card_open_r(AlsaCard *obj,int bits,int stereo,int rate)
{
int bsize;
int err;
snd_pcm_t *pcm_handle;
gchar *pcmdev;
if (over_pcmdev!=NULL) pcmdev=over_pcmdev;
else pcmdev=obj->pcmdev;
if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_CAPTURE,SND_PCM_NONBLOCK) < 0) {
g_warning("alsa_card_open_r: Error opening PCM device %s\n",obj->pcmdev );
return -1;
}
g_return_val_if_fail(pcm_handle!=NULL,-1);
obj->read_handle=pcm_handle;
if ((bsize=alsa_set_params(obj,0,bits,stereo,rate))<0){
snd_pcm_close(pcm_handle);
obj->read_handle=NULL;
return -1;
}
obj->readbuf=g_malloc0(bsize);
err=snd_pcm_start(obj->read_handle);
if (err<0){
g_warning("Cannot start read pcm: %s", snd_strerror(err));
}
obj->readpos=0;
SND_CARD(obj)->bsize=bsize;
SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED;
return 0;
}
int alsa_card_open_w(AlsaCard *obj,int bits,int stereo,int rate)
{
int err,bsize;
snd_pcm_t *pcm_handle;
gchar *pcmdev;
if (over_pcmdev!=NULL) pcmdev=over_pcmdev;
else pcmdev=obj->pcmdev;
if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_PLAYBACK,SND_PCM_NONBLOCK) < 0) {
g_warning("alsa_card_open_w: Error opening PCM device %s\n", obj->pcmdev);
return -1;
}
obj->write_handle=pcm_handle;
if ((bsize=alsa_set_params(obj,1,bits,stereo,rate))<0){
snd_pcm_close(pcm_handle);
obj->write_handle=NULL;
return -1;
}
obj->writebuf=g_malloc0(bsize);
obj->writepos=0;
SND_CARD(obj)->bsize=bsize;
SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED;
return 0;
}
void alsa_card_set_blocking_mode(AlsaCard *obj, gboolean yesno){
if (obj->read_handle!=NULL) snd_pcm_nonblock(obj->read_handle,!yesno);
if (obj->write_handle!=NULL) snd_pcm_nonblock(obj->write_handle,!yesno);
}
void alsa_card_close_r(AlsaCard *obj)
{
if (obj->read_handle!=NULL){
snd_pcm_close(obj->read_handle);
obj->read_handle=NULL;
g_free(obj->readbuf);
obj->readbuf=NULL;
}
}
void alsa_card_close_w(AlsaCard *obj)
{
if (obj->write_handle!=NULL){
snd_pcm_close(obj->write_handle);
obj->write_handle=NULL;
g_free(obj->writebuf);
obj->writebuf=NULL;
}
}
int alsa_card_probe(AlsaCard *obj,int bits,int stereo,int rate)
{
int ret;
ret=alsa_card_open_w(obj,bits,stereo,rate);
if (ret<0) return -1;
ret=SND_CARD(obj)->bsize;
alsa_card_close_w(obj);
return ret;
}
void alsa_card_destroy(AlsaCard *obj)
{
snd_card_uninit(SND_CARD(obj));
g_free(obj->pcmdev);
if (obj->readbuf!=0) g_free(obj->readbuf);
if (obj->writebuf!=0) g_free(obj->writebuf);
}
gboolean alsa_card_can_read(AlsaCard *obj)
{
int frames;
g_return_val_if_fail(obj->read_handle!=NULL,0);
if (obj->readpos!=0) return TRUE;
if ( frames=snd_pcm_avail_update(obj->read_handle)>=obj->frames) return 1;
//g_message("frames=%i",frames);
return 0;
}
int __alsa_card_read(AlsaCard *obj,char *buf,int bsize)
{
int err;
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGALRM);
sigprocmask(SIG_BLOCK,&set,NULL);
err=snd_pcm_readi(obj->read_handle,buf,bsize/obj->frame_size);
if (err<0) {
if (err!=-EPIPE){
g_warning("alsa_card_read: snd_pcm_readi() failed:%s.",snd_strerror(err));
}
snd_pcm_prepare(obj->read_handle);
err=snd_pcm_readi(obj->read_handle,buf,bsize/obj->frame_size);
if (err<0) g_warning("alsa_card_read: snd_pcm_readi() failed:%s.",snd_strerror(err));
}
sigprocmask(SIG_UNBLOCK,&set,NULL);
return err*obj->frame_size;
}
int alsa_card_read(AlsaCard *obj,char *buf,int size)
{
int err;
gint bsize=SND_CARD(obj)->bsize;
g_return_val_if_fail(obj->read_handle!=NULL,-1);
if (size<bsize){
gint canread=MIN(bsize-obj->readpos,size);
if (obj->readpos==0){
err=__alsa_card_read(obj,obj->readbuf,bsize);
}
memcpy(buf,&obj->readbuf[obj->readpos],canread);
obj->readpos+=canread;
if (obj->readpos>=bsize) obj->readpos=0;
return canread;
}else{
err=__alsa_card_read(obj,buf,size);
return err;
}
}
int __alsa_card_write(AlsaCard *obj,char *buf,int size)
{
int err;
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGALRM);
sigprocmask(SIG_BLOCK,&set,NULL);
if ((err=snd_pcm_writei(obj->write_handle,buf,size/obj->frame_size))<0){
if (err!=-EPIPE){
g_warning("alsa_card_write: snd_pcm_writei() failed:%s.",snd_strerror(err));
}
snd_pcm_prepare(obj->write_handle);
err=snd_pcm_writei(obj->write_handle,buf,size/obj->frame_size);
if (err<0) g_warning("alsa_card_write: Error writing sound buffer (size=%i):%s",size,snd_strerror(err));
}
sigprocmask(SIG_UNBLOCK,&set,NULL);
return err;
}
int alsa_card_write(AlsaCard *obj,char *buf,int size)
{
int err;
gint bsize=SND_CARD(obj)->bsize;
g_return_val_if_fail(obj->write_handle!=NULL,-1);
if (size<bsize){
gint canwrite;
canwrite=MIN(bsize-obj->writepos,size);
memcpy(&obj->writebuf[obj->writepos],buf,canwrite);
obj->writepos+=canwrite;
if (obj->writepos>=bsize){
err=__alsa_card_write(obj,obj->writebuf,bsize);
obj->writepos=0;
}
return canwrite;
}else{
return __alsa_card_write(obj,buf,bsize);
}
}
snd_mixer_t *alsa_mixer_open(AlsaCard *obj){
snd_mixer_t *mixer=NULL;
int err;
err=snd_mixer_open(&mixer,0);
if (err<0){
g_warning("Could not open alsa mixer: %s",snd_strerror(err));
return NULL;
}
if ((err = snd_mixer_attach (mixer, obj->mixdev)) < 0){
g_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){
g_warning("snd_mixer_selem_register: %s",snd_strerror(err));
snd_mixer_close(mixer);
return NULL;
}
if ((err = snd_mixer_load (mixer)) < 0){
g_warning("snd_mixer_load: %s",snd_strerror(err));
snd_mixer_close(mixer);
return NULL;
}
obj->mixer=mixer;
return mixer;
}
void alsa_mixer_close(AlsaCard *obj){
snd_mixer_close(obj->mixer);
obj->mixer=NULL;
}
typedef enum {CAPTURE, PLAYBACK, CAPTURE_SWITCH, PLAYBACK_SWITCH} MixerAction;
static gint 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;
long sndMixerPMax;
long newvol;
elem=snd_mixer_first_elem(mixer);
while (elem!=NULL){
elemname=snd_mixer_selem_get_name(elem);
//g_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_playback_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) g_warning("Could not get capture volume for %s:%s",name,snd_strerror(err));
//else g_message("Succesfully 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) g_warning("Could not get playback volume for %s:%s",name,snd_strerror(err));
//else g_message("Succesfully get playback level for %s.",elemname);
break;
}
break;
case CAPTURE_SWITCH:
break;
}
}
elem=snd_mixer_elem_next(elem);
}
return value;
}
static void set_mixer_element(snd_mixer_t *mixer,const char *name, gint level,MixerAction action){
const char *elemname;
snd_mixer_elem_t *elem;
int tmp;
long sndMixerPMin;
long sndMixerPMax;
long newvol;
elem=snd_mixer_first_elem(mixer);
while (elem!=NULL){
elemname=snd_mixer_selem_get_name(elem);
//g_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_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax);
newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin;
snd_mixer_selem_set_capture_volume_all(elem,newvol);
//g_message("Succesfully 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);
//g_message("Succesfully 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);
//g_message("Succesfully 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);
//g_message("Succesfully set capture switch for %s.",elemname);
}
break;
}
}
elem=snd_mixer_elem_next(elem);
}
return ;
}
void alsa_card_set_level(AlsaCard *obj,gint way,gint a)
{
snd_mixer_t *mixer;
mixer=alsa_mixer_open(obj);
if (mixer==NULL) return ;
switch(way){
case SND_CARD_LEVEL_GENERAL:
set_mixer_element(mixer,"Master",a,PLAYBACK);
break;
case SND_CARD_LEVEL_INPUT:
set_mixer_element(mixer,"Capture",a,CAPTURE);
break;
case SND_CARD_LEVEL_OUTPUT:
set_mixer_element(mixer,"PCM",a,PLAYBACK);
break;
default:
g_warning("oss_card_set_level: unsupported command.");
}
alsa_mixer_close(obj);
}
gint alsa_card_get_level(AlsaCard *obj,gint way)
{
snd_mixer_t *mixer;
gint value;
mixer=alsa_mixer_open(obj);
if (mixer==NULL) return 0;
switch(way){
case SND_CARD_LEVEL_GENERAL:
value=get_mixer_element(mixer,"Master",PLAYBACK);
break;
case SND_CARD_LEVEL_INPUT:
value=get_mixer_element(mixer,"Capture",CAPTURE);
break;
case SND_CARD_LEVEL_OUTPUT:
value=get_mixer_element(mixer,"PCM",PLAYBACK);
break;
default:
g_warning("oss_card_set_level: unsupported command.");
}
alsa_mixer_close(obj);
return value;
}
void alsa_card_set_source(AlsaCard *obj,int source)
{
snd_mixer_t *mixer;
mixer=alsa_mixer_open(obj);
if (mixer==NULL) return;
switch (source){
case 'm':
set_mixer_element(mixer,"Mic",1,CAPTURE_SWITCH);
set_mixer_element(mixer,"Capture",1,CAPTURE_SWITCH);
break;
case 'l':
set_mixer_element(mixer,"Line",1,CAPTURE_SWITCH);
set_mixer_element(mixer,"Capture",1,CAPTURE_SWITCH);
break;
}
}
MSFilter *alsa_card_create_read_filter(AlsaCard *card)
{
MSFilter *f=ms_oss_read_new();
ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index);
return f;
}
MSFilter *alsa_card_create_write_filter(AlsaCard *card)
{
MSFilter *f=ms_oss_write_new();
ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index);
return f;
}
SndCard * alsa_card_new(gint devid)
{
AlsaCard * obj;
SndCard *base;
int err;
gchar *name=NULL;
/* carefull: this is an alsalib call despite its name! */
err=snd_card_get_name(devid,&name);
if (err<0) {
return NULL;
}
obj= g_new0(AlsaCard,1);
base= SND_CARD(obj);
snd_card_init(base);
base->card_name=g_strdup_printf("%s (Advanced Linux Sound Architecture)",name);
base->_probe=(SndCardOpenFunc)alsa_card_probe;
base->_open_r=(SndCardOpenFunc)alsa_card_open_r;
base->_open_w=(SndCardOpenFunc)alsa_card_open_w;
base->_can_read=(SndCardPollFunc)alsa_card_can_read;
base->_set_blocking_mode=(SndCardSetBlockingModeFunc)alsa_card_set_blocking_mode;
base->_read=(SndCardIOFunc)alsa_card_read;
base->_write=(SndCardIOFunc)alsa_card_write;
base->_close_r=(SndCardCloseFunc)alsa_card_close_r;
base->_close_w=(SndCardCloseFunc)alsa_card_close_w;
base->_set_rec_source=(SndCardMixerSetRecSourceFunc)alsa_card_set_source;
base->_set_level=(SndCardMixerSetLevelFunc)alsa_card_set_level;
base->_get_level=(SndCardMixerGetLevelFunc)alsa_card_get_level;
base->_destroy=(SndCardDestroyFunc)alsa_card_destroy;
base->_create_read_filter=(SndCardCreateFilterFunc)alsa_card_create_read_filter;
base->_create_write_filter=(SndCardCreateFilterFunc)alsa_card_create_write_filter;
obj->pcmdev=g_strdup_printf("plughw:%i,0",devid);
obj->mixdev=g_strdup_printf("hw:%i",devid);
obj->readbuf=NULL;
obj->writebuf=NULL;
return base;
}
gint alsa_card_manager_init(SndCardManager *m, gint index)
{
gint devindex;
gint i;
gint found=0;
gchar *name=NULL;
for(devindex=0;index<MAX_SND_CARDS && devindex<MAX_SND_CARDS ;devindex++){
if (snd_card_get_name(devindex,&name)==0){
g_message("Found ALSA device: %s",name);
m->cards[index]=alsa_card_new(devindex);
m->cards[index]->index=index;
found++;
index++;
}
}
return found;
}
void alsa_card_manager_set_default_pcm_device(const gchar *pcmdev){
if (over_pcmdev!=NULL){
g_free(over_pcmdev);
}
over_pcmdev=g_strdup(pcmdev);
}
#endif