/* * tvaudio.c * * API for controlling the sound card audio parameters relevent to capture * * (C) 1997 Randall Hopper * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. 2. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* ******************** Include Files ************** */ #include #include #include #include #include #include #include "voxware.h" #include /*#include */ void XawScrollbarSetThumb( Widget w, float top, float shown ); #include "tvdefines.h" #include "glob.h" #include "tvmenu.h" #include "app_rsrc.h" #include "tvutil.h" #include "tvaudio.h" /* ******************** Local defines ************** */ #define VOLUME_RANGE (TV_VOLUME_MAX - TV_VOLUME_MIN+1) #define DO_IOCTL_SERR(str,arg) fprintf(stderr, "ioctl(%s, %ld) failed: %s\n",\ str, (long)arg, strerror(errno) ) typedef struct { TV_AUDIO_SAMPLE_FMT samp_fmt_tv; int samp_fmt_drv; TV_INT32 bytes_per_samp; } TV_AUDIO_SAMPLE_FMT_REC; /* ******************** Private variables ************** */ static TV_AUDIO_SAMPLE_FMT_REC S_samp_cnv[] = { { TV_AUDIO_SAMPLE_FMT_MULAW_U8 , AFMT_MU_LAW, 1 }, { TV_AUDIO_SAMPLE_FMT_LIN_S8 , AFMT_S8 , 1 }, { TV_AUDIO_SAMPLE_FMT_LIN_U8 , AFMT_U8 , 1 }, { TV_AUDIO_SAMPLE_FMT_LIN_S16_LE, AFMT_S16_LE, 2 }, { TV_AUDIO_SAMPLE_FMT_LIN_U16_LE, AFMT_U16_LE, 2 }, { TV_AUDIO_SAMPLE_FMT_LIN_S16_BE, AFMT_S16_BE, 2 }, { TV_AUDIO_SAMPLE_FMT_LIN_U16_BE, AFMT_U16_BE, 2 } }; static TV_UINT32 Mixer_dev_id = 0x00, /* Uninitialized */ Mixer_dev_mask = 0x00; static char *Mixer_dev_name = ""; /* ******************** Forward declarations ************** */ /* ******************** Function Definitions ************** */ static void TVAUDIOSetMixerMask() { char buf[20]; if ( Mixer_dev_mask ) return; buf[0] = '\0'; strncat( buf, App_res.mixer_channel, sizeof(buf)-1 ); TVUTILstrupr( buf ); if ( STREQ( buf, "CD" ) ) { Mixer_dev_id = SOUND_MIXER_CD; Mixer_dev_mask = SOUND_MASK_CD; Mixer_dev_name = "CD"; } else if ( STREQ( buf, "MIC" ) ) { Mixer_dev_id = SOUND_MIXER_MIC; Mixer_dev_mask = SOUND_MASK_MIC; Mixer_dev_name = "MIC"; } #ifdef SOUND_MIXER_VIDEO else if ( STREQ( buf, "VIDEO" ) ) { Mixer_dev_id = SOUND_MIXER_VIDEO; Mixer_dev_mask = SOUND_MASK_VIDEO; Mixer_dev_name = "VIDEO"; } #endif else if ( STREQ( buf, "LINE" ) ) { Mixer_dev_id = SOUND_MIXER_LINE; Mixer_dev_mask = SOUND_MASK_LINE; Mixer_dev_name = "LINE"; } else if ( STREQ( buf, "LINE1" ) ) { Mixer_dev_id = SOUND_MIXER_LINE1; Mixer_dev_mask = SOUND_MASK_LINE1; Mixer_dev_name = "LINE1"; } else if ( STREQ( buf, "LINE2" ) ) { Mixer_dev_id = SOUND_MIXER_LINE2; Mixer_dev_mask = SOUND_MASK_LINE2; Mixer_dev_name = "LINE2"; } else if ( STREQ( buf, "LINE3" ) ) { Mixer_dev_id = SOUND_MIXER_LINE3; Mixer_dev_mask = SOUND_MASK_LINE3; Mixer_dev_name = "LINES"; } } void TVAUDIOResync( TV_BOOL update_gui ) { TV_AUDIO *a = &G_glob.audio; /* FIXME: Voxware MIXER_READ stomps two bytes past the end of */ /* vol[2], so declaring it to be 4 bytes long protects us even */ /* in the face of -O and auto-removing unreferenced variables */ /* (since -O isn't 'that' smart...). */ unsigned char vol[4]; if ( a->mixer_fd < 0 ) return; if ( ioctl( a->mixer_fd, MIXER_READ(Mixer_dev_id), vol ) < 0 ) { fprintf( stderr, "Can't read %s volume\n", Mixer_dev_name ); close( a->mixer_fd ); a->mixer_fd = -1; } else { a->line_vol = ( ((TV_INT32)vol[0]) + vol[1] ) / 2.0; a->line_vol = MAX( TV_VOLUME_MIN, MIN( TV_VOLUME_MAX, a->line_vol ) ); if ( update_gui ) TVAUDIOSetLineVolume( a->line_vol, TRUE ); } } void TVAUDIOInit( TV_AUDIO *a ) { int ctrls; a->mute_on = False; a->line_vol = 0; if ( !App_res.do_audio ) { a->mixer_fd = -1; return; } TVAUDIOSetMixerMask(); /* See if we can control line in */ if ( (a->mixer_fd = open( App_res.mixer_device, O_RDWR, 0 )) < 0 ) fprintf( stderr, "open(\"%s\") failed\n", App_res.mixer_device ); else if ( ioctl( a->mixer_fd, SOUND_MIXER_READ_DEVMASK, &ctrls ) < 0 ) { fprintf( stderr, "/dev/mixer query for devices failed\n" ); close( a->mixer_fd ); a->mixer_fd = -1; } else if ( !(ctrls & Mixer_dev_mask) ) { fprintf( stderr, "Can't control %s volume via /dev/mixer\n", Mixer_dev_name ); close( a->mixer_fd ); a->mixer_fd = -1; } /* Read current line-in volume */ else TVAUDIOResync( FALSE ); } void TVAUDIOGetMuteState( TV_BOOL *mute_on ) { TV_AUDIO *a = &G_glob.audio; *mute_on = a->mute_on; } void TVAUDIOSetMuteState( TV_BOOL mute_on ) { TV_AUDIO *a = &G_glob.audio; TV_CAPTURE *c = &G_glob.capture; /* Accomplish mute via capture card audio mute */ if ( a->mute_on != mute_on ) { a->mute_on = mute_on; TVCAPTURESetAudioMute( c, a->mute_on ); } TVTOOLSSetToggleState( TV_TOOLITEM_MUTE, !a->mute_on ); } void TVAUDIOGetLineVolume( TV_INT32 *line_vol ) { TV_AUDIO *a = &G_glob.audio; *line_vol = a->line_vol; } void TVAUDIOSetLineVolume( TV_INT32 line_vol, TV_BOOL update_slider ) { TV_AUDIO *a = &G_glob.audio; Widget scrollbar = TVTOOLSGetVolumeScrollbar(); /* FIXME: Voxware MIXER_READ stomps two bytes past the end of */ /* vol[2], so declaring it to be 4 bytes long protects us even */ /* in the face of -O and auto-removing unreferenced variables */ /* (since -O isn't 'that' smart...). */ /* Just to be safe, we'll declare vol[4] for MIXER_WRITE too. */ unsigned char vol[4] = { '\0','\0','\0','\0' }; if ( a->mixer_fd < 0 ) return; line_vol = MAX( TV_VOLUME_MIN, MIN( TV_VOLUME_MAX, line_vol ) ); a->line_vol = line_vol; if ( update_slider ) XawScrollbarSetThumb ( scrollbar, a->line_vol / 100.0, -1.0 ); vol[0] = vol[1] = line_vol; if ( ioctl( a->mixer_fd, MIXER_WRITE(Mixer_dev_id), vol ) < 0 ) fprintf( stderr, "Can't set %s volume\n", Mixer_dev_name ); } /* TVAUDIOSelectLineForRecord - Select the correct active recording source */ void TVAUDIOSelectLineForRecord() { TV_AUDIO *a = &G_glob.audio; int parm; if ( a->mixer_fd < 0 ) return; /* Voxware BUG: Despite what the docs say, you can't tweak the */ /* active recording device via the DSP device. */ parm = Mixer_dev_mask; if ( ioctl( a->mixer_fd, SOUND_MIXER_WRITE_RECSRC, &parm ) < 0 ) DO_IOCTL_SERR( "SOUND_MIXER_WRITE_RECSRC", parm ); } /* TVAUDIOGetSampleFmtByTVType - Lookup sample fmt conv rec by tv type */ static TV_AUDIO_SAMPLE_FMT_REC *TVAUDIOGetSampleFmtByTVType( TV_AUDIO_SAMPLE_FMT fmt ) { TV_INT32 i; for ( i = 0; i < XtNumber( S_samp_cnv ); i++ ) if ( S_samp_cnv[i].samp_fmt_tv == fmt ) return &S_samp_cnv[i]; return NULL; } /* TVAUDIOOpenDsp - Open and configure the DSP device for play/record */ TV_BOOL TVAUDIOOpenDsp( TV_SOUND *snd, TV_BOOL record, int *dsp_fd, char **error_msg ) { static char S_error_msg[ 128 ]; TV_BOOL error = False; int parm, parm_new; TV_AUDIO_SAMPLE_FMT_REC *fmt_p; *dsp_fd = -1; if ( error_msg ) *error_msg = NULL; /* Is audio enabled? */ if ( !App_res.do_audio ) { sprintf( S_error_msg, "Audio was disabled by the user." ); error = True; goto RETURN; } /* See if we can open the DSP device */ if ( (*dsp_fd = open( App_res.dsp_device, (record ? O_RDONLY : O_WRONLY), 0 )) < 0 ) { sprintf( S_error_msg, "Can't open audio device (\"%s\"). " "It may be busy.", App_res.dsp_device ); error = True; goto RETURN; } /* FIXME: If AU file fmt selected, validate capture fmt */ /* Set the buffering size down so our reads/writes don't slow the app. */ /* Note: the driver will autoadjust # frags down based on avail mem. */ parm_new = parm = (10 /* 2^10 (1024) bytes per fragment, and */) | (344 /* up to 344 fragments */) << 16; if ( ioctl( *dsp_fd, SNDCTL_DSP_SETFRAGMENT, &parm_new ) < 0 ) { DO_IOCTL_SERR( "SNDCTL_DSP_SETFRAGMENT", parm ); error = True; goto RETURN; } if ( (parm & 0xFFFF) != (parm_new & 0xFFFF) ) { fprintf( stderr, "Info: Soundcard does not support %d-sized DMA " "fragments.", (parm & 0xFFFF) ); } /* Ok, configure the DSP and make sure these parms are ok */ fmt_p = TVAUDIOGetSampleFmtByTVType( snd->sample_fmt ); if ( fmt_p == NULL ) { fprintf( stderr, "TVAUDIOOpenDsp: Bad sample format\n" ); exit(1); } parm_new = parm = fmt_p->samp_fmt_drv; if ( ioctl( *dsp_fd, SNDCTL_DSP_SETFMT, &parm_new ) < 0 ) { DO_IOCTL_SERR( "SNDCTL_DSP_SETFMT", parm ); error = True; goto RETURN; } if ( parm != parm_new ) { sprintf( S_error_msg, "Selected sample format not supported (on " "this soundcard)." ); error = True; goto RETURN; } parm_new = parm = snd->stereo ? 2 : 1; if ( ioctl( *dsp_fd, SOUND_PCM_WRITE_CHANNELS, &parm_new ) < 0 ) { DO_IOCTL_SERR( "SOUND_PCM_WRITE_CHANNELS", parm ); error = True; goto RETURN; } if ( parm != parm_new ) { sprintf( S_error_msg, "Selected mono/stereo setting not supported " "with the selected sample format (on this soundcard)." ); error = True; goto RETURN; } parm_new = parm = snd->sample_rate; if ( ioctl( *dsp_fd, SOUND_PCM_WRITE_RATE, &parm_new ) < 0 ) { DO_IOCTL_SERR( "SOUND_PCM_WRITE_RATE", parm ); error = True; goto RETURN; } if ( parm != parm_new ) { sprintf( S_error_msg, "Selected sampling rate not supported with " "the selected sample format and mono/stereo setting " "(on this soundcard)." ); error = True; goto RETURN; } RETURN: /* Done recording, close up shop */ if ( error && ( *dsp_fd >= 0 )) { close( *dsp_fd ); *dsp_fd = -1; } if ( error && error_msg ) *error_msg = S_error_msg; return !error; } /* TVAUDIOGetSampleFmtBps - Get how many bytes-per-sec/chan for a samplefmt */ TV_INT32 TVAUDIOGetSampleFmtBps( TV_AUDIO_SAMPLE_FMT fmt ) { TV_AUDIO_SAMPLE_FMT_REC *fmt_p; fmt_p = TVAUDIOGetSampleFmtByTVType( fmt ); if ( fmt_p == NULL ) { fprintf( stderr, "TVAUDIOGetSampleFmtBps: Bad sample format\n" ); exit(1); } return fmt_p->bytes_per_samp; } /* FIXME: Refetch volume settings on EnterNotify of top level shells */ /* FIXME: Add Scroll callback to volume slider */