diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
commit | 1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch) | |
tree | 4495d23e7b54ab5700e3839081e797c1eafe0db9 /tutorials/sndkit/samples/fulldup.c | |
download | oss4-1058def8e7827e56ce4a70afb4aeacb5dc44148f.tar.gz |
Imported Upstream version 4.2-build2006upstream/4.2-build2006upstream
Diffstat (limited to 'tutorials/sndkit/samples/fulldup.c')
-rw-r--r-- | tutorials/sndkit/samples/fulldup.c | 816 |
1 files changed, 816 insertions, 0 deletions
diff --git a/tutorials/sndkit/samples/fulldup.c b/tutorials/sndkit/samples/fulldup.c new file mode 100644 index 0000000..91190c8 --- /dev/null +++ b/tutorials/sndkit/samples/fulldup.c @@ -0,0 +1,816 @@ +/* + * Purpose: Full duplex sample program using the single device approach. + * + * Description: + * This sample program explains how to use the one and twodevicefile based + * methods for full duplex. + * + * This program uses full duplex for echo-like processing (usefull for + * applications like guitar effect processors). However this task is + * actually very challenging. Applications that require almost zero + * latencies will need to be run on very high priority levels. The exact method + * required for this depends on the operating system and is beyond the scope + * of this simplistic sample program. + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/time.h> +#include <sys/select.h> +#include <soundcard.h> + +char *dspname = "/dev/dsp"; +char *dspname_in = NULL; + +int fd_out = -1, fd_in = -1; +char buffer[32 * 1024]; /* Max 32k local buffer */ +int rate = 48000; +int fragsize; + +static void +open_one_device (char *dspname) +{ +/* + * Open the device file. The one device full duplex scheme requires that + * the device file is opened with O_RDWR. See the description of the + * {!nlink open} system call for more info. + */ + oss_audioinfo ai; + int fd; + int tmp; + int devcaps; + int channels = 2; + int format; + int frag; + + if ((fd = open (dspname, O_RDWR, 0)) == -1) + { + perror (dspname); + exit (-1); + } + + ai.dev = -1; + if (ioctl (fd, SNDCTL_ENGINEINFO, &ai) != -1) + { + printf ("\nUsing audio engine %d=%s for duplex\n\n", ai.dev, ai.name); + } + +#ifdef USE_RAW_FORMATS +/* + * In some cases it's recommended that all sample rate and format + * conversions are disabled. These conversions may make timing very + * tricky. However the drawback of disabling format conversions is that + * the application must be able to handle the format and rate + * conversions itself. + * + * We don't do any error checking because SNDCTL_DSP_COOKEDMODE is an optional + * ioctl call that may not be supported by all OSS implementations (in such + * cases there are no format conversions anyway). + */ + + tmp = 0; + ioctl (fd, SNDCTL_DSP_COOKEDMODE, &tmp); +#endif + +/* + * Check that the device supports full duplex. Otherwise there is no point in + * continuing. + */ + + if (ioctl (fd, SNDCTL_DSP_GETCAPS, &devcaps) == -1) + { + perror ("SNDCTL_DSP_GETCAPS"); + exit (-1); + } + + if (!(devcaps & PCM_CAP_DUPLEX)) + { + fprintf (stderr, + "%s doesn't support one device based full duplex scheme\n", + dspname); + fprintf (stderr, "Please use the two device scheme.\n"); + exit (-1); + } + +#if 0 + /* + * There is no point in calling SNDCTL_DSP_SETDUPLEX any more. This call has not had any + * effect since SB16. + */ + if (ioctl (fd, SNDCTL_DSP_SETDUPLEX, NULL) == -1) + { + perror ("SNDCTL_DSP_SETDUPLEX"); + exit (-1); + } +#endif + +/* + * Try to set the fragment size to suitable level. + */ + + frag = 0x7fff000a; /* Unlimited number of 1k fragments */ + + if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &frag) == -1) + { + perror ("SNDCTL_DSP_SETFRAGMENT"); + exit (-1); + } + +/* + * Set up the sampling rate and other sample parameters. + */ + + tmp = channels; + + if (ioctl (fd, SNDCTL_DSP_CHANNELS, &tmp) == -1) + { + perror ("SNDCTL_DSP_CHANNELS"); + exit (-1); + } + + if (tmp != channels) + { + fprintf (stderr, "%s doesn't support stereo (%d)\n", dspname, tmp); + exit (-1); + } + + /* + * Request 16 bit native endian sample format. + */ + tmp = AFMT_S16_NE; + + if (ioctl (fd, SNDCTL_DSP_SETFMT, &tmp) == -1) + { + perror ("SNDCTL_DSP_SETFMT"); + exit (-1); + } + +/* + * Note that most devices support only the usual litle endian (Intel) + * byte order. This may be a problem under big endian architectures such + * as Sparc. We accept also the opposite (alien) endianess but + * this may require different handling (byte swapping) in most applications. + * However we don't care about this issue because this applicaton doesn't + * do any processing on the data. + */ + + if (tmp != AFMT_S16_NE && tmp != AFMT_S16_OE) + { + fprintf (stderr, "%s doesn't support 16 bit sample format (%x)\n", + dspname, tmp); + exit (-1); + } + + format = tmp; + + if (format == AFMT_S16_OE) + { + fprintf (stderr, + "Warning: Using 16 bit sample format with wrong endianess.\n"); + } + + tmp = rate; + + if (ioctl (fd, SNDCTL_DSP_SPEED, &tmp) == -1) + { + perror ("SNDCTL_DSP_SPEED"); + exit (-1); + } + + if (tmp != rate) + { + fprintf (stderr, "%s doesn't support requested rate %d (%d)\n", dspname, + rate, tmp); + exit (-1); + } + +/* + * Get the actual fragment size. SNDCTL_DSP_GETBLKSIZE gives a good + * compromise. If cooked mode is not disabled then the fragment sizes + * used for input and output may be different. Using + * {!nlink SNDCTL_DSP_GETOSPACE} or {!nlink SNDCTL_DSP_GETISPACE} + * may return different values. In this case SNDCTL_DSP_GETBLKSIZE will + * return a value that is between them. + */ + + if (ioctl (fd, SNDCTL_DSP_GETBLKSIZE, &fragsize) == -1) + { + perror ("SNDCTL_DSP_GETBLKSIZE"); + exit (-1); + } + + if (fragsize > sizeof (buffer)) + { + fprintf (stderr, "Too large fragment size %d\n", fragsize); + exit (-1); + } + + printf ("Sample parameters set OK. Using fragment size %d\n", fragsize); + + fd_in = fd_out = fd; +} + +static void +open_two_devices (char *dspname_out, char *dspname_in) +{ +/* + * Open the device file. The one device full duplex scheme requires that + * the device file is opened with O_RDWR. See the description of the + * {!nlink open} system call for more info. + */ + oss_audioinfo ai_in, ai_out; + int tmp; + int devcaps; + int channels = 2; + int format; + int frag = 0x7fff000a; /* Unlimited number of 1k fragments */ + +/* + * Open the output device + */ + if ((fd_out = open (dspname_out, O_WRONLY, 0)) == -1) + { + perror (dspname_out); + exit (-1); + } + + ai_out.dev = -1; + if (ioctl (fd_out, SNDCTL_ENGINEINFO, &ai_out) != -1) + { + printf ("\nUsing audio engine %d=%s for output\n", ai_out.dev, + ai_out.name); + } + +/* + * Open the input device + */ + if ((fd_in = open (dspname_in, O_RDONLY, 0)) == -1) + { + perror (dspname_in); + exit (-1); + } + + ai_in.dev = -1; + if (ioctl (fd_in, SNDCTL_ENGINEINFO, &ai_in) != -1) + { + printf ("Using audio engine %d=%s for input\n\n", ai_in.dev, + ai_in.name); + } + + if (ai_in.rate_source != ai_out.rate_source) + { +/* + * Input and output devices should have their sampling rates derived from + * the same crystal clock. Otherwise there will be drift in the sampling rates + * which may cause dropouts/hiccup (unless the application can handle the rate + * error). So check that the rate sources are the same. + * + * However two devices may still be driven by the same clock even if the + * rate sources are different. OSS has no way to know if the user is using some + * common sample clock (word clock) generator to syncronize all devices + * together (which is _THE_ practice in production environments). So do not + * overreact. Just let the user to know about the potential problem. + */ + fprintf (stderr, + "Note! %s and %s are not necessarily driven by the same clock.\n", + dspname_out, dspname_in); + } + +#ifdef USE_RAW_FORMATS +/* + * In many cases it's recommended that all sample rate and format + * conversions are disabled. These conversions may make timing very + * tricky. However the drawback of disabling format conversions is that + * the application must be able to handle the format and rate + * conversions itself. + * + * We don't do any error checking because SNDCTL_DSP_COOKEDMODE is an optional + * ioctl call that may not be supported by all OSS implementations (in such + * cases there are no format conversions anyway). + */ + + tmp = 0; + ioctl (fd_out, SNDCTL_DSP_COOKEDMODE, &tmp); + tmp = 0; + ioctl (fd_in, SNDCTL_DSP_COOKEDMODE, &tmp); +#endif + +/* + * Check output device capabilities. + */ + if (ioctl (fd_out, SNDCTL_DSP_GETCAPS, &devcaps) == -1) + { + perror ("SNDCTL_DSP_GETCAPS"); + exit (-1); + } + + if (devcaps & PCM_CAP_DUPLEX) + { + fprintf (stderr, + "Device %s supports duplex so you may want to use the single device approach instead\n", + dspname_out); + } + + if (!(devcaps & PCM_CAP_OUTPUT)) + { + fprintf (stderr, "%s doesn't support output\n", dspname_out); + fprintf (stderr, "Please use different device.\n"); +#if 0 + /* + * NOTE! Earlier OSS versions don't necessarily support PCM_CAP_OUTPUT + * so don't panic. + */ + exit (-1); +#endif + } + +/* + * Check input device capabilities. + */ + if (ioctl (fd_in, SNDCTL_DSP_GETCAPS, &devcaps) == -1) + { + perror ("SNDCTL_DSP_GETCAPS"); + exit (-1); + } + + if (devcaps & PCM_CAP_DUPLEX) + { + fprintf (stderr, + "Device %s supports duplex so you may want to use the single device approach instead\n", + dspname_in); + } + + if (!(devcaps & PCM_CAP_INPUT)) + { + fprintf (stderr, "%s doesn't support input\n", dspname_in); + fprintf (stderr, "Please use different device.\n"); +#if 0 + /* + * NOTE! Earlier OSS versions don't necessarily support PCM_CAP_INPUT + * so don't panic. + */ + exit (-1); +#endif + } + +/* + * No need to turn on the full duplex mode when using separate input and output + * devices. In fact calling SNDCTL_DSP_SETDUPLEX in this mode would be a + * major mistake + */ + +/* + * Try to set the fragment size to suitable level. + */ + + if (ioctl (fd_out, SNDCTL_DSP_SETFRAGMENT, &frag) == -1) + { + perror ("SNDCTL_DSP_SETFRAGMENT"); + exit (-1); + } + if (ioctl (fd_in, SNDCTL_DSP_SETFRAGMENT, &frag) == -1) + { + perror ("SNDCTL_DSP_SETFRAGMENT"); + exit (-1); + } + +/* + * Set up the sampling rate and other sample parameters. + */ + + tmp = channels; + + if (ioctl (fd_out, SNDCTL_DSP_CHANNELS, &tmp) == -1) + { + perror ("SNDCTL_DSP_CHANNELS"); + exit (-1); + } + + if (tmp != channels) + { + fprintf (stderr, "%s doesn't support stereo (%d)\n", dspname_out, tmp); + exit (-1); + } + if (ioctl (fd_in, SNDCTL_DSP_CHANNELS, &tmp) == -1) + { + perror ("SNDCTL_DSP_CHANNELS"); + exit (-1); + } + + if (tmp != channels) + { + fprintf (stderr, "%s doesn't support stereo (%d)\n", dspname_in, tmp); + exit (-1); + } + + /* + * Request 16 bit native endian sample format. + */ + tmp = AFMT_S16_NE; + + if (ioctl (fd_out, SNDCTL_DSP_SETFMT, &tmp) == -1) + { + perror ("SNDCTL_DSP_SETFMT"); + exit (-1); + } + +/* + * Note that most devices support only the usual litle endian (Intel) + * byte order. This may be a problem under big endian architectures such + * as Sparc. We accept also the opposite (alien) endianess but + * this may require different handling (byte swapping) in most applications. + * However we don't care about this issue because this applicaton doesn't + * do any processing on the data. + */ + + if (tmp != AFMT_S16_NE && tmp != AFMT_S16_OE) + { + fprintf (stderr, "%s doesn't support 16 bit sample format (%x)\n", + dspname_out, tmp); + exit (-1); + } + + format = tmp; + if (ioctl (fd_in, SNDCTL_DSP_SETFMT, &tmp) == -1) + { + perror ("SNDCTL_DSP_SETFMT"); + exit (-1); + } + + if (tmp != format) + { + fprintf (stderr, + "Error: Input and output devices use different formats (%x/%x)\n", + tmp, format); + exit (-1); + } + + if (format == AFMT_S16_OE) + { + fprintf (stderr, + "Warning: Using 16 bit sample format with wrong endianess.\n"); + } + +/* + * It might be better to set the sampling rate firs on the input device and + * then use the same rate with the output device. The reason for this is that + * the vmix driver supports sample rate conversions for output. However input + * is locked to the master device rate. + */ + tmp = rate; + + if (ioctl (fd_in, SNDCTL_DSP_SPEED, &tmp) == -1) + { + perror ("SNDCTL_DSP_SPEED"); + exit (-1); + } + + if (tmp != rate) + { + fprintf (stderr, "%s doesn't support requested rate %d (%d)\n", + dspname_out, rate, tmp); + exit (-1); + } + + if (ioctl (fd_out, SNDCTL_DSP_SPEED, &tmp) == -1) + { + perror ("SNDCTL_DSP_SPEED"); + exit (-1); + } + + if (tmp != rate) + { + fprintf (stderr, "%s doesn't support the same rate %d!=%d as %s\n", + dspname_out, rate, tmp, dspname_in); + exit (-1); + } + +/* + * Get the actual fragment size. SNDCTL_DSP_GETBLKSIZE gives a good + * compromise. If cooked mode is not disabled then the fragment sizes + * used for input and output may be different. Using + * {!nlink SNDCTL_DSP_GETOSPACE} or {!nlink SNDCTL_DSP_GETISPACE} + * may return different values. In this case SNDCTL_DSP_GETBLKSIZE will + * return a value that is between them. + */ + + if (ioctl (fd_in, SNDCTL_DSP_GETBLKSIZE, &fragsize) == -1) + { + perror ("SNDCTL_DSP_GETBLKSIZE"); + exit (-1); + } + + if (fragsize > sizeof (buffer)) + { + fprintf (stderr, "Too large fragment size %d\n", fragsize); + exit (-1); + } + +/* + * Do not check the fragment sizes on both devices. It is perfectly normal + * that they are different. Instead check just the input fragment size because + * it is the one that matters. + */ + + printf ("Sample parameters set OK. Using fragment size %d\n", fragsize); + +} + +static void +method_0 (int fd_out, int fd_in) +{ +/* + * This function demonstrates how to implement an full duplex + * application in the easiest and most reliable way. This method uses + * blocking reads for synchronization with the device. + */ + + int l; + int loopcount = 0; + + /* + * Fragments can be as large as 32k (or even up to 64k) + * with certain devices. + */ + char silence[32 * 1024]; /* Buffer for one fragment of silence */ + +/* + * This is the record/play loop. This block uses simple model where recorded data + * is just read from the device and written back without use of any additional + * ioctl calls. This approach should be used if the application doesn't need + * to correlate the recorded samples with playback (for example for echo + * cancellation). + * + * In this approach OSS will automatically find out how large buffer is + * needed to handle the process without buffer underruns. However there will + * probably be few dropouts before the buffer gets adapted for the worst + * case latencies. + * + * There are two methods to avoid the initial dropouts. The easiest method is + * to write few milliseconds of silent sampels to the output before writing + * the first block of actual recorded data. Another approach is to warm up + * the device by replacing first seconds of recorded data with silecene + * before writing the data to the output. + * + * After few moments of run the output should settle. After that the lag + * between input and output should be very close to the minimum input-output + * latency that can be obtained without using applied woodoo. The latencies can + * possibly be reduced by optimizing the application itself (if it's CPU bound), + * by using shorter fragments (also check the max_intrate parameter in + * osscore.conf), by terminating unnecessary applications and by using + * higher (linear) priority. + */ + + while ((l = read (fd_in, buffer, fragsize)) == fragsize) + { + int delay; + float t; + + if (loopcount == 0) + { + /* + * Output one extra fragment filled with silence + * before starting to write the actual data. This must + * be done after we have recorded the first fragment + * Without this extra silence in the beginning playback + * data will run out microseconds before next recorded + * data becomes available. + * + * Number of silence bytes written doesn't need to be + * exactly one fragment or any multiple of it. Sometimes + * (under highly loaded system) more silence will be + * needed. Sometimes just few samples may be + * enough. + */ + + memset (silence, 0, fragsize); + if (write (fd_out, silence, fragsize) != fragsize) + { + perror ("write"); + exit (-1); + } + } + + /* + * Compute the output delay (in milliseconds) + */ + if (ioctl (fd_out, SNDCTL_DSP_GETODELAY, &delay) == -1) + delay = 0; + + delay /= 4; /* Get number of 16 bit stereo samples */ + + t = (float) delay / (float) rate; /* Get delay in seconds */ + + t *= 1000.0; /* Convert delay to milliseconds */ + + if ((l = write (fd_out, buffer, fragsize)) != fragsize) + { + perror ("write"); + exit (-1); + } + +#if 1 + /* + * Printing the delay level will slow down the application which + * makes the delay longer. So don't print it by default. + */ + printf ("\rDelay=%5.3g msec", t); + fflush (stdout); +#endif + + loopcount++; + } + + perror ("read"); + exit (-1); +} + +static void +method_1 (int fd_out, int fd_in) +{ +/* + * Many applications use select/poll or the equivivalent facilities provided + * by GUI libraries like GTK+ to handle I/O with multiple devices. We use + * select() in this example. + * + * This routine reads audio input, does some processing on the data and + * forwards it to the output device. Hitting ENTER terminates the program. + * + * The logic is that any input available on the device will be read. However + * the amount of space available on output is not checked because it's + * unnecessary. Checking both input and output status would be very tricky + * if not impossible. + */ + + char silence[32 * 1024]; /* Buffer for one fragment of silence */ + fd_set reads; + int n; + unsigned int trig; + int first_time = 1; + +/* + * First we have to start the recording engine. Otherwise select() will never + * report available input and the application will just iddle. + */ + trig = 0; /* Trigger OFF */ + if (ioctl (fd_in, SNDCTL_DSP_SETTRIGGER, &trig) == -1) + perror ("SETTRIGGER 0"); + + trig = PCM_ENABLE_INPUT; /* Trigger ON */ + + /* + * Trigger output too if using the single device mode. Otherwise all writes + * will fail. + */ + if (fd_in == fd_out) + trig |= PCM_ENABLE_OUTPUT; + + if (ioctl (fd_in, SNDCTL_DSP_SETTRIGGER, &trig) == -1) + perror ("SETTRIGGER 1"); + + while (1) /* Infinite loop */ + { + struct timeval time; + + FD_ZERO (&reads); + + FD_SET (0, &reads); /* stdin */ + FD_SET (fd_in, &reads); + + time.tv_sec = 1; + time.tv_usec = 0; + if ((n = select (fd_in + 1, &reads, NULL, NULL, &time)) == -1) + { + perror ("select"); + exit (-1); + } + + if (n == 0) + { + fprintf (stderr, "Timeout\n"); + continue; + } + + if (FD_ISSET (0, &reads)) /* Keyboard input */ + { + printf ("Finished.\n"); + exit (0); + } + + if (FD_ISSET (fd_in, &reads)) + { + /* + * Recorded data is available. + */ + int l, n; + struct audio_buf_info info; + + if (ioctl (fd_in, SNDCTL_DSP_GETISPACE, &info) == -1) + { + perror ("GETISPACE"); + exit (-1); + } + + n = info.bytes; /* How much */ + + if ((l = read (fd_in, buffer, n)) == n) + { + printf ("\r %5d bytes ", n); + fflush (stdout); + + if (first_time) + { + /* + * Write one fragment of silence before the actual data. + * This is necessary to avoid regular underruns while + * waiting for more data. + */ + memset (silence, 0, fragsize); + if (write (fd_out, silence, fragsize) != fragsize) + { + perror ("write"); + exit (-1); + } + first_time = 0; + } + + /* + * This is the place where you can add your processing. + * There are 'l' bytes of audio data stored in 'buffer'. This + * program uses 16 bit native endian format and two channels + * (stereo). + */ + if (write (fd_out, buffer, l) != l) + { + perror ("write"); + exit (-1); + } + continue; + } + } + + perror ("read"); + exit (-1); + } +} + +int +main (int argc, char *argv[]) +{ + + int method = 0; + +/* + * Check the working method + */ + if (argc > 1) + method = atoi (argv[1]); + +/* + * Check if the sampling rate was given on command line. + */ + if (argc > 2) + { + rate = atoi (argv[2]); + if (rate == 0) + rate = 48000; + } + +/* + * Check if the device name is given on command line. + */ + if (argc > 3) + dspname = argv[3]; + +/* + * Check if anotherdevice name is given for input. + */ + if (argc > 4) + dspname_in = argv[4]; + + if (dspname_in == NULL) + open_one_device (dspname); + else + open_two_devices (dspname, dspname_in); + + switch (method) + { + case 0: + method_0 (fd_out, fd_in); + break; + + case 1: + method_1 (fd_out, fd_in); + break; + + default: + fprintf (stderr, "Method %d not defined\n", method); + exit (-1); + } + + exit (0); +} |