/* * snd.c - plays and records * format: raw * rate = 44100 * channels = 2 * * adapted from aplay.c (package alsa-utils; usage: sudo apt-get source alsa-utils) * */ #define _GNU_SOURCE #include #include #include #include "audio.h" #define FORMAT_DEFAULT -1 #define FORMAT_RAW 0 /* global data */ static char *command; static snd_pcm_t *handle; static struct { snd_pcm_format_t format; unsigned int channels; unsigned int rate; } hwparams, rhwparams; static int file_type = FORMAT_DEFAULT; static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; static int interleaved = 1; static u_char *audiobuf = NULL; static snd_pcm_uframes_t chunk_size = 0; static int start_delay = 0; static int stop_delay = 0; static int avail_min = -1; static unsigned period_time = 0; static unsigned buffer_time = 0; static snd_pcm_uframes_t period_frames = 0; static snd_pcm_uframes_t buffer_frames = 0; static size_t bits_per_sample, bits_per_frame; static size_t chunk_bytes; static int timelimit = 0; static void set_params(void) { snd_pcm_hw_params_t *params; snd_pcm_sw_params_t *swparams; snd_pcm_uframes_t buffer_size; int err; size_t n; snd_pcm_uframes_t xfer_align; unsigned int rate; snd_pcm_uframes_t start_threshold, stop_threshold; snd_pcm_hw_params_alloca(¶ms); snd_pcm_sw_params_alloca(&swparams); err = snd_pcm_hw_params_any(handle, params); if (err < 0) { fprintf(stderr,"Broken configuration for this PCM: no configurations available\n"); exit(EXIT_FAILURE); } err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) { fprintf(stderr,"Access type not available\n"); exit(EXIT_FAILURE); } err = snd_pcm_hw_params_set_format(handle, params, hwparams.format); if (err < 0) { fprintf(stderr,"Sample format non available\n"); exit(EXIT_FAILURE); } err = snd_pcm_hw_params_set_channels(handle, params, hwparams.channels); if (err < 0) { fprintf(stderr,"Channels count non available\n"); exit(EXIT_FAILURE); } rate = hwparams.rate; err = snd_pcm_hw_params_set_rate_near(handle, params, &hwparams.rate, 0); if (err < 0) { fprintf(stderr,"snd_pcm_hw_params_set_rate_near fail\n"); exit(EXIT_FAILURE); } rate = hwparams.rate; if (buffer_time == 0 && buffer_frames == 0) { err = snd_pcm_hw_params_get_buffer_time_max(params, &buffer_time, 0); if (err < 0) { fprintf(stderr,"snd_pcm_hw_params_get_buffer_time_max fail\n"); exit(EXIT_FAILURE); } if (buffer_time > 500000) buffer_time = 500000; } if (period_time == 0 && period_frames == 0) { if (buffer_time > 0) period_time = buffer_time / 4; else period_frames = buffer_frames / 4; } if (period_time > 0) err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, 0); else err = snd_pcm_hw_params_set_period_size_near(handle, params, &period_frames, 0); if (err < 0) { fprintf(stderr,"snd_pcm_hw_params_set_period_size_near fail\n"); exit(EXIT_FAILURE); } if (buffer_time > 0) { err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, 0); } else { err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_frames); } if (err < 0) { fprintf(stderr,"snd_pcm_hw_params_set_buffer_size_near fail\n"); exit(EXIT_FAILURE); } err = snd_pcm_hw_params(handle, params); if (err < 0) { fprintf(stderr,"Unable to install hw params:\n"); exit(EXIT_FAILURE); } snd_pcm_hw_params_get_period_size(params, &chunk_size, 0); snd_pcm_hw_params_get_buffer_size(params, &buffer_size); if (chunk_size == buffer_size) { fprintf(stderr,"Can't use period equal to buffer size (%lu == %lu)\n", chunk_size, buffer_size); exit(EXIT_FAILURE); } snd_pcm_sw_params_current(handle, swparams); err = snd_pcm_sw_params_get_xfer_align(swparams, &xfer_align); if (err < 0) { fprintf(stderr,"Unable to obtain xfer align\n"); exit(EXIT_FAILURE); } err = snd_pcm_sw_params_set_sleep_min(handle, swparams, 0); if (err < 0) { fprintf(stderr,"snd_pcm_sw_params_set_sleep_min fail\n"); exit(EXIT_FAILURE); } if (avail_min < 0) n = chunk_size; else n = (double) rate * avail_min / 1000000; err = snd_pcm_sw_params_set_avail_min(handle, swparams, n); /* round up to closest transfer boundary */ n = (buffer_size / xfer_align) * xfer_align; if (start_delay <= 0) { start_threshold = n + (double) rate * start_delay / 1000000; } else start_threshold = (double) rate * start_delay / 1000000; if (start_threshold < 1) start_threshold = 1; if (start_threshold > n) start_threshold = n; err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold); if (err < 0) { fprintf(stderr,"snd_pcm_sw_params_set_start_threshold fail\n"); exit(EXIT_FAILURE); } if (stop_delay <= 0) stop_threshold = buffer_size + (double) rate * stop_delay / 1000000; else stop_threshold = (double) rate * stop_delay / 1000000; err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold); if (err < 0) { fprintf(stderr,"snd_pcm_sw_params_set_stop_threshold fail\n"); exit(EXIT_FAILURE); } err = snd_pcm_sw_params_set_xfer_align(handle, swparams, xfer_align); if (err < 0) { fprintf(stderr,"snd_pcm_sw_params_set_xfer_align fail\n"); exit(EXIT_FAILURE); } if (snd_pcm_sw_params(handle, swparams) < 0) { fprintf(stderr,"unable to install sw params:\n"); exit(EXIT_FAILURE); } bits_per_sample = snd_pcm_format_physical_width(hwparams.format); bits_per_frame = bits_per_sample * hwparams.channels; chunk_bytes = chunk_size * bits_per_frame / 8; audiobuf = realloc(audiobuf, chunk_bytes); if (audiobuf == NULL) { fprintf(stderr,"not enough memory\n"); exit(EXIT_FAILURE); } } static void xrun(void) { int res; fprintf(stderr,"over/under run!\n"); if ((res = snd_pcm_prepare(handle))<0) { fprintf(stderr,"xrun: prepare error: %s\n", snd_strerror(res)); exit(EXIT_FAILURE); } } /* I/O suspend handler */ static void suspend(void) { int res; fprintf(stderr,"suspend!\n"); while ((res = snd_pcm_resume(handle)) == -EAGAIN) sleep(1); /* wait until suspend flag is released */ if (res < 0) { if ((res = snd_pcm_prepare(handle)) < 0) { fprintf(stderr,"suspend: prepare error: %sfax + n", snd_strerror(res)); exit(EXIT_FAILURE); } } } /* write to audio device (for playback) */ static ssize_t pcm_write(u_char *data, size_t count) { int res; ssize_t r; ssize_t result = 0; if (count < chunk_size) { // fprintf(stderr,"set silence!\n"); snd_pcm_format_set_silence(hwparams.format, data + count * bits_per_frame / 8, (chunk_size - count) * hwparams.channels); count = chunk_size; } while (count > 0) { r = snd_pcm_writei(handle, data, count); if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { fprintf(stderr,"again!\n"); snd_pcm_wait(handle, 1000); } else if (r == -EPIPE) { xrun(); } else if (r == -ESTRPIPE) { suspend(); } else if (r < 0) { fprintf(stderr,"pcm_write: write error: %s\n", snd_strerror(r) ); exit(EXIT_FAILURE); } if (r > 0) { result += r; count -= r; data += r * bits_per_frame / 8; } } return result; } /* read from audio device (for recording) */ static ssize_t pcm_read(u_char *data, size_t rcount) { ssize_t r; size_t count = rcount; if (count != chunk_size) { count = chunk_size; } while (count > 0) { r = snd_pcm_readi(handle, data, count); if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { fprintf(stderr,"again!\n"); snd_pcm_wait(handle, 1000); } else if (r == -EPIPE) { xrun(); } else if (r == -ESTRPIPE) { suspend(); } else if (r < 0) { fprintf(stderr,"audio read error: %s\n", snd_strerror(r)); exit(EXIT_FAILURE); } if (r > 0) { count -= r; data += r * bits_per_frame / 8; } } return rcount; } /* playing raw data */ static void playback(char *name) { int l, r; int fd; if ((fd = open64(name, O_RDONLY, 0)) == -1) { fprintf(stderr,"can't open: %s\n",name); exit(EXIT_FAILURE); } /* should be raw data */ hwparams = rhwparams; /* setting the globals for playing raw data */ set_params(); audioGlobals.bufferPointer = audiobuf; audioGlobals.audioRunningFlag = 1; while (1) { r = read(fd, audiobuf, chunk_bytes); audioGlobals.bufferSize = r; if (r < 0) { perror(name); exit(EXIT_FAILURE); } if (r == 0){ break; } if (audioGlobals.stopFlag) { audioGlobals.stopFlag = 0; break; } /* if */ l = r; l = l * 8 / bits_per_frame; r = pcm_write(audiobuf, l); if (r != l){ break; } } snd_pcm_drain(handle); close(fd); } /* recording raw data */ static void capture(char *name) { off64_t count, rest; /* number of bytes to capture */ int fd; /* get number of bytes to capture */ count = snd_pcm_format_size(hwparams.format, hwparams.rate * hwparams.channels); count *= (off64_t)timelimit; count += count % 2; /* setup sound hardware */ set_params(); /* open a file to write */ remove(name); if ((fd = open64(name, O_WRONLY | O_CREAT, 0644)) == -1) { perror(name); exit(EXIT_FAILURE); } rest = count; audioGlobals.bufferPointer = audiobuf; audioGlobals.audioRunningFlag = 1; /* capture */ while (rest > 0) { size_t c = (rest <= (off64_t)chunk_bytes) ? (size_t)rest : chunk_bytes; size_t f = c * 8 / bits_per_frame; if (pcm_read(audiobuf, f) != f) break; audioGlobals.bufferSize = c; if (write(fd, audiobuf, c) != c) { perror(name); exit(EXIT_FAILURE); } if (audioGlobals.stopFlag) { audioGlobals.stopFlag = 0; break; } /* if */ rest -= c; } close(fd); } int ProcessAudio () { char *pcm_name = "default"; int tmp, err, c; snd_pcm_info_t *info; char *dataFilename; char *decision = "unknown"; while (1) { audioGlobals.playbackFlag = 0; audioGlobals.recordFlag = 0; audioGlobals.audioRunningFlag = 0; while (1) { if (audioGlobals.playbackFlag) { dataFilename = audioGlobals.audioFilename; audioGlobals.playbackFlag = 0; audioGlobals.stopFlag = 0; decision = "playback"; audioGlobals.bufferSize = 0; break; } /* if */ if (audioGlobals.recordFlag) { dataFilename = audioGlobals.audioFilename; audioGlobals.recordFlag = 0; audioGlobals.stopFlag = 0; audioGlobals.bufferSize = 0; decision = "record"; break; } /* if */ sleep(1); } /* while */ /* allocate an invalid snd_pcm_info_t using standard alloca (http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___info.html) */ snd_pcm_info_alloca(&info); file_type = FORMAT_RAW; if (strcmp(decision, "playback") == 0) { // playback stream = SND_PCM_STREAM_PLAYBACK; command = "aplay"; } /* if */ else if (strcmp(decision, "record") == 0) { // record stream = SND_PCM_STREAM_CAPTURE; command = "arecord"; start_delay = 1; timelimit = audioGlobals.audioTimeLimit; } /* elseIf */ else { fprintf(stderr,"error: impossible! not playback or record\n"); return(1) ; } /* else */ chunk_size = -1; rhwparams.format = SND_PCM_FORMAT_S16_LE; rhwparams.rate = 44100; rhwparams.channels = 2; /* Opens a PCM. (http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) */ err = snd_pcm_open(&handle, pcm_name, stream, 0); if (err < 0) { fprintf(stderr,"error: can't open\n"); return 1; } /* Obtain general (static) information for PCM handle. (http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) */ if ((err = snd_pcm_info(handle, info)) < 0) { fprintf(stderr,"error: info\n"); return 1; } chunk_size = 1024; hwparams = rhwparams; audiobuf = (u_char *)malloc(1024); if (audiobuf == NULL) { fprintf(stderr,"error: not enough memory\n"); return 1; } if (stream == SND_PCM_STREAM_PLAYBACK) { playback(dataFilename); } /* if */ else { capture(dataFilename); } /* elseIf */ /* close PCM handle (http://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html) */ snd_pcm_close(handle); free(audiobuf); /* Frees the global configuration tree in snd_config. (http://www.alsa-project.org/alsa-doc/alsa-lib/group___config.html) */ snd_config_update_free_global(); } /* while */ return (EXIT_SUCCESS); }