//----------------------------------------------------------------------------- // COPYRIGHT BY OERTEL & ZAHL SOFTWAREENTWICKLUNG GbR, BERLIN, GERMANY // file: audio-ibm.cc // written by: Christian Zahl // description: Audio class for the IBM RS/6000 familiy using the // Ultimedia Audio Adapter (or the build-in in the // 43P) // creation date: 1996/03/13 // // $Id: audio-ibm.cc,v 1.4 1996/03/25 08:30:15 czahl Exp $ // // Revision 1.4 1996/03/25 08:30:15 czahl // - forgott to disable the hardware monitor after opening the audio device. // // Revision 1.3 1996/03/14 20:01:06 czahl // - minor bug: the recFd and playFd were not initialized in IBMAudio::Obtain // - minor cosmetic changes // // Revision 1.2 1996/03/14 10:59:58 czahl // - this is the first alpha version for Mimi. // - now we also added the other in- and output ports. // // Revision 1.1 1996/03/14 08:39:46 czahl // Initial revision // - After downloading the merged version of Vat with the NeVoT audio driver // support on 1996/03/13, I decided to hack my own real class for the // IBM audio decive. At midnight, we had the first stable version running // (I listened more than an hour to an Eurythmics CD via the loopback. :-) //------------------------------------------------------------------------------ static const char rcsid[] = "@(#) $Header: audio-ibm.cc,v 1.3 96/05/01 21:04:27 van Exp $"; /*----- standard includefiles ----------------------------------------------*/ #include #include #include #include #define _BSD_COMPAT 1 #include #include #include /*----- user includefiles --------------------------------------------------*/ #include "Tcl.h" #include "audio.h" /*----- defines ------------------------------------------------------------*/ #define WITH_TRACE 1 #if WITH_TRACE #define TRACE(xxx) xxx #else #define TRACE(xxx) #endif #define AUDIO_MIN_GAIN 0 #define AUDIO_MAX_GAIN 255 /* * Because we need some more definitions than normaly found in the "offical" * include file (sys/audio.h), we declare them here, if they heven't been * declared before. */ #ifndef AUDIO_MODIFY_LIMITS #define AUX1 100 #define AUDIO_SET_GAIN 14 #define INTERNAL_SPEAKER_ON 10 #define INTERNAL_SPEAKER_OFF 11 #define AUDIO_MODIFY_LIMITS 20 typedef struct _audio_set_gain { signed long left_gain; signed long right_gain; } audio_set_gain; typedef struct _init_buf_req { long buffer_size; long play_block_count; long play_lower_limit; long cap_upper_limit; long byte_count; long return_code; } init_buf_req; #endif /* AUDIO_MODIFY_LIMITS */ /*----- type definitions ---------------------------------------------------*/ /*----- local functions ----------------------------------------------------*/ /*----- global functions ---------------------------------------------------*/ /*----- local variables ----------------------------------------------------*/ /*----- global variables ---------------------------------------------------*/ extern const unsigned char lintomulawX[]; extern const unsigned char lintomulaw[]; extern const short mulawtolin[]; /*------------------------------------------------------------------------*/ class IBMAudio : public Audio { public: IBMAudio(); virtual u_char* Read(); virtual void Write(u_char *); virtual void SetRGain(int); virtual void SetPGain(int); virtual void Obtain(); virtual void Release(); virtual int FrameReady (); virtual void InputPort (int); virtual void OutputPort (int); virtual void SetMonitor (int); protected: void InitAudioChange (); void InitAudioControl (); void InitAudioDevice (); int GainClip(int); int recFd; int playFd; audio_buffer bufStat; audio_init audioInit; audio_change audioChange; audio_control audioControl; init_buf_req bufReq; u_char *playBuf; /* needed for delay reduction */ short *recBuf; /* buffer for holding captured data */ int recBufSize; /* # of bytes in the buffer */ u_char *readBuf; /* buffer for delivering the "read" data */ int lastmean; int contHighWaters; /* # on continous hiwater marks reached */ }; /*------------------------------------------------------------------------*/ static class IBMAudioMatcher : public Matcher { public: IBMAudioMatcher() : Matcher ("audio") {} TclObject* match (const char* id) { if (strcasecmp (id, "ibm") == 0) return (new IBMAudio); return 0; } } ibmaudio_matcher; /*------------------------------------------------------------------------*/ IBMAudio::IBMAudio() { TRACE (fprintf (stderr, "IBMAudio\n");) /* open (or create) the lock file */ openlock(); iports = 4; oports = 4; fd = -1; recFd = -1; playFd = -1; playBuf = new u_char[blksize]; readBuf = new u_char[blksize]; recBuf = new short[blksize]; } /* IBMAudio::IBMAudio */ /*------------------------------------------------------------------------*/ void IBMAudio::Obtain() { static char *recFn[] = {"/dev/baud0/1", "/dev/paud0/1", NULL}; static char *playFn[] = {"/dev/baud0/2", "/dev/paud0/2", NULL}; int i; TRACE (fprintf (stderr, "IBMAudio::Obtain\n");) if (HaveAudio()) abort(); if (lock() != 0) return; recBufSize = 0; contHighWaters = 0; /*** search an valid audio device and open it ***/ for (i=0; recFn[i] != NULL; i++) if ((recFd = open (recFn[i], O_RDONLY | O_NDELAY)) >= 0) break; if (recFn[i] == NULL) { fprintf(stderr, "vat: couldn't open any record port.\n"); return; } TRACE (fprintf (stderr, "IBMAudio::Obtain %s %s\n", recFn[i], playFn[i]);) if ((playFd = open (playFn[i], O_WRONLY | O_NDELAY)) < 0) { close (recFd); recFd = -1; fprintf (stderr, "vat: cannot open playback port!\n"); return; } /*** initialize the device ***/ InitAudioDevice (); fd = recFd; Audio::Obtain(); } /* IBMAudio::Obtain */ /*------------------------------------------------------------------------*/ void IBMAudio::Release() { TRACE (fprintf (stderr, "IBMAudio::Release\n");) if (HaveAudio()) { unlock (); unlink (); close (recFd); close (playFd); recFd = -1; playFd = -1; fd = -1; notify (); } } /* IBMAudio::IBMAudioRelease */ /*------------------------------------------------------------------------*/ int IBMAudio::FrameReady () /* * Because the IBM audio device does not deliver blocks of 160 samples, but * frames of 2^n, we have to buffer the data, so that vat thinks we are * reading 160 samples. * Keep in mind, that we are recording with 16 linear at 8000 Hz, because we * want to reduce the DC offset. */ { int n; n = blksize -recBufSize; /* samples missing to 160 */ if (n <= 0) return 1; n *= 2; /* 2 bytes / sample! */ if ((n = read (recFd, recBuf +recBufSize, n)) <= 0) return 0; n /= 2; /* we need the # of samples, not bytes! */ recBufSize += n; if (recBufSize >= blksize) return 1; else return 0; } /* IBMAudio::FrameRead */ /*------------------------------------------------------------------------*/ u_char* IBMAudio::Read() /* * Because it seems that the AIX Audio Adapter also has a DC offset, * we use the same method to kill the DC level as in the SGI driver. */ { register int smean = lastmean; register int mean, dif, res; register int r; int i; for (i=0; i> 13; dif = r - mean; smean += dif; readBuf[i] = lintomulawX[dif & 0x1ffff]; } /* for */ lastmean = smean; recBufSize -= blksize; /* should be = 0 */ return readBuf; /* return the data */ } /* IBMAudio::Read */ /*------------------------------------------------------------------------*/ void IBMAudio::Write (u_char *cp) /* * Write the audio samples the the audio device. * Because of the nature of the IBM audio device (don't know how it is on * other platforms) it can be possible that the dalay increases to an * very bad amount of audio data (up a second), specially on slow machines * like my M20. The reason is, that if the process will be descheduled * for a time longer than data are available for playback, the buffer * gets empty. In this state, the audio device STOPS to play any data. * When the process get rescheduled again, it receives a bulk of captured * audio data (capturing is NOT stoped). Because this acts as a trigger * for writing the audio data, the same bulk of data will be written to * the audio device, resulting in a high delay. * * What I do is the same as I have done in the NeVoT audio driver for AIX. * I reduce the delay by ommiting some samples, so that the delay will * be reduced by time. Of course, this results in a short frequency shift, * but you don't hear it very hard. But the result is still acceptable, * not for continous tones, but for voice and music :-) * * In fact, now I have implemented a variation of the algorithm. Now we not * only ommit some samples, but perform a linear aproximation for the new * samples by combining several samples (something like dithering). By doing * so, the quality is much better, for voice, music and for tones also! */ { #define HIGHWATER (150*8) /* 150ms */ register int i; register int n; register int v; if (ioctl (playFd, AUDIO_BUFFER, &bufStat) == -1) perror ("IBMAudio::Write AUDIO_BUFFER"); if (bufStat.write_buf_size >= HIGHWATER) contHighWaters++; else contHighWaters = 0; if (contHighWaters > 3) { /* after 3 continous "overflows" */ /* fprintf (stderr, "%d ", bufStat.write_buf_size >> 3); fflush (stderr); */ #ifdef OLD for (i=0, n=0; i AUDIO_MAX_GAIN) return AUDIO_MAX_GAIN; else return level; } /* IBMAudio::GainClip */ /*------------------------------------------------------------------------*/ void IBMAudio::SetRGain (int level) { audio_set_gain audioSetGain; rgain = GainClip (level); if (recFd < 0) return; audioSetGain.left_gain = rgain *100 /AUDIO_MAX_GAIN; audioSetGain.right_gain = rgain *100 /AUDIO_MAX_GAIN; if (ioctl (recFd, AUDIO_SET_GAIN, &audioSetGain) == -1) perror ("IBMAudio::SetRGain"); } /* IBMAudio::SetRGain */ /*------------------------------------------------------------------------*/ void IBMAudio::SetPGain (int level) { pgain = GainClip(level); if (playFd < 0) return; InitAudioChange (); InitAudioControl (); audioChange.volume = 0x7fff0000 /AUDIO_MAX_GAIN *pgain; audioChange.volume_delay = 0; audioControl.request_info = &audioChange; audioControl.ioctl_request = AUDIO_CHANGE; if (ioctl (playFd, AUDIO_CONTROL, &audioControl) == -1) perror ("IBMAudio::SetPGain"); } /* IBMAudio::SetPGain */ /*--------------------------------------------------------------------*/ void IBMAudio::InputPort (int p) { iport = p; if (recFd < 0) return; /*** select the input port ***/ InitAudioChange (); InitAudioControl (); switch (iport) { case input_mike: audioChange.input = LOW_GAIN_MIKE; break; case input_line: audioChange.input = LINE_1; break; case input_line2: audioChange.input = LINE_2; break; case input_line3: audioChange.input = HIGH_GAIN_MIKE; break; } /* switch */ audioControl.request_info = &audioChange; audioControl.ioctl_request = AUDIO_CHANGE; if (ioctl (recFd, AUDIO_CONTROL, &audioControl) == -1) perror ("IBMAudio::InputPort"); } /* IBMAudio::InputPort */ /*--------------------------------------------------------------------*/ void IBMAudio::OutputPort (int p) { oport = p; if (playFd < 0) return; /*** select the output port ***/ InitAudioChange (); InitAudioControl (); switch (oport) { case output_speaker: audioChange.output = EXTERNAL_SPEAKER; break; case output_phones: audioChange.output = EXTERNAL_SPEAKER; break; case output_line: audioChange.output = OUTPUT_1; break; case output_line2: audioChange.output = AUX1; break; } /* switch */ audioControl.request_info = &audioChange; audioControl.ioctl_request = AUDIO_CHANGE; if (ioctl (recFd, AUDIO_CONTROL, &audioControl) == -1) perror ("IBMAudio::OutputPort"); /*** internal speaker can only be toggled ***/ InitAudioChange (); InitAudioControl (); audioControl.request_info = &audioChange; audioControl.ioctl_request = AUDIO_CHANGE; if (oport == output_speaker) audioChange.output = INTERNAL_SPEAKER_ON; else audioChange.output = INTERNAL_SPEAKER_OFF; if (ioctl (recFd, AUDIO_CONTROL, &audioControl) == -1) perror ("IBMAudio::OutputPort internal speaker"); } /* IBMAudio::OutputPort */ /*--------------------------------------------------------------------*/ void IBMAudio::InitAudioDevice () { /*** initialize the recording part ***/ audioInit.srate = 8000; audioInit.bits_per_sample = 8 *2; audioInit.bsize = 256 *2; audioInit.mode = PCM; audioInit.channels = 1; audioInit.position_resolution = AUDIO_IGNORE; audioInit.flags = BIG_ENDIAN; audioInit.operation = RECORD; audioInit.reserved = NULL; if (ioctl (recFd, AUDIO_INIT, &audioInit) == -1) perror ("IBMAudio::InitAudioDevice record"); InputPort (iport); SetRGain (rgain); SetMonitor (0); /*** select the kernel buffersize for recording ***/ bufReq.buffer_size = 8000 *2 *2; /* for 2 seconds */ bufReq.play_block_count = AUDIO_IGNORE; bufReq.play_lower_limit = AUDIO_IGNORE; bufReq.cap_upper_limit = AUDIO_IGNORE; bufReq.byte_count = AUDIO_IGNORE; bufReq.return_code = 0xDeadBeaf; if (ioctl (recFd, AUDIO_MODIFY_LIMITS, &bufReq) == -1) perror ("IBMAudio::InitAudioDevice record buffer"); /*** initialize the playback part ***/ audioInit.srate = 8000; audioInit.bits_per_sample = 8; audioInit.bsize = 512; audioInit.mode = MU_LAW; audioInit.channels = 1; audioInit.position_resolution = AUDIO_IGNORE; audioInit.flags = FIXED; audioInit.operation = PLAY; audioInit.reserved = NULL; if (ioctl (playFd, AUDIO_INIT, &audioInit) == -1) perror ("IBMAudio::InitAudioDevice playback"); OutputPort (oport); SetPGain (pgain); /*** start the both device parts ***/ InitAudioControl (); audioControl.ioctl_request = AUDIO_START; if (ioctl (recFd, AUDIO_CONTROL, &audioControl) != 0) perror ("IBMAudio::InitAudioDevice start record"); InitAudioControl (); audioControl.ioctl_request = AUDIO_START; if (ioctl (playFd, AUDIO_CONTROL, &audioControl) != 0) perror ("IBMAudio::InitAudioDevice start playback"); } /* IBMAudio::InitAudioDevice */ /*------------------------------------------------------------------------*/ void IBMAudio::SetMonitor (int on) { IBMAudio::InitAudioChange (); IBMAudio::InitAudioControl (); if (on) audioChange.monitor = MONITOR_UNCOMPRESSED; else audioChange.monitor = MONITOR_OFF; audioControl.request_info = &audioChange; audioControl.ioctl_request = AUDIO_CHANGE; if (ioctl (recFd, AUDIO_CONTROL, &audioControl) < 0) perror ("IBMAudio::SetMonitor ioctl"); } /* IBMAudio::SetMonitor */ /*--------------------------------------------------------------------*/ void IBMAudio::InitAudioChange () { audioChange.dev_info = NULL; audioChange.input = AUDIO_IGNORE; audioChange.output = AUDIO_IGNORE; audioChange.monitor = AUDIO_IGNORE; audioChange.volume = AUDIO_IGNORE; audioChange.volume_delay = AUDIO_IGNORE; audioChange.balance = AUDIO_IGNORE; audioChange.balance_delay = AUDIO_IGNORE; audioChange.treble = AUDIO_IGNORE; audioChange.bass = AUDIO_IGNORE; audioChange.pitch = AUDIO_IGNORE; } /* IBMAudio::InitAudioChange */ /*--------------------------------------------------------------------*/ void IBMAudio::InitAudioControl () { audioControl.ioctl_request = AUDIO_IGNORE; audioControl.request_info = NULL; audioControl.position = 0; audioControl.return_code = 0xDeadBeaf; } /* IBMAudio::InitAudioControl */ /*------------------------------------------------------------------------*/