/* -*- mode: c; c-file-style: "ellemtel"; -*- */ /** Makefile ************************* capture: play.c gcc -D MAIN -D CAPTURE -msse -o capture play.c -lasound play: play.c gcc -D MAIN -msse -o play play.c -lasound *************************************** **/ //-- play.h ----------------------------- #ifdef __cplusplus extern "C" { #else typedef enum { false, true } bool; #endif #define ALSA_PCM_NEW_HW_PARAMS_API #include extern snd_pcm_t* alsa_play_init ( unsigned int samplerate, unsigned int periods, snd_pcm_uframes_t samples, /* number of samples in each period */ bool hda, /* 16bit (false) or 32bit HD Audio (true) */ unsigned int channels, /* number of interleaved channels */ const char* device /* defaults to: "hw:0,0" */ ); extern snd_pcm_t* alsa_capture_init ( unsigned int samplerate, unsigned int periods, snd_pcm_uframes_t samples, /* number of samples in each period */ bool hda, /* 16bit (false) or 32bit HD Audio (true) */ unsigned int channels, /* number of interleaved channels */ const char* device /* defaults to: "hw:0,0" */ ); extern int alsa_play ( snd_pcm_t* pcm_handle, char* buffer, snd_pcm_uframes_t samples ); extern int alsa_capture ( snd_pcm_t* pcm_handle, char * buffer, snd_pcm_uframes_t samples ); #ifdef __cplusplus } #endif // EOF play.h ----------------------------------------------------------- /* These functions here are mostly for my own convenience dealing with * alsa, CUDA and Intel HD Audio. They may not work with your setup, but ... * * I have recently noticed that the ALSA project has a similar simplified * interface in the pipe, which may be ready by he time you read this notice. * As of late 2008 those functions still comes with a Warning though: * - "The simple PCM API may be broken in the current release." * * Anyway, look up/google for: snd_spcm_init() and snd_spcm_init_duplex(). */ #include #include //#include "play.h" #include #include // various information on x86 processor type #define CPUID(f,ax,bx,cx,dx) __asm__ __volatile__ \ ("cpuid": "=a" (ax), "=b" (bx), "=c" (cx), "=d" (dx) : "a" (f)); static int set_DAZ_and_FTZ(bool on) { int sse_level = 0; if(on) { unsigned long ax, bx, cx, dx; CPUID(0x00,ax,bx,cx,dx); CPUID(0x01,ax,bx,cx,dx); if (dx & 0x02000000) { sse_level = 1; // set FLUSH_TO_ZERO to ON and // set round towards zero (RZ) _mm_setcsr(_mm_getcsr() | 0x8000|0x6000); if (dx & 0x04000000) { sse_level = 2; if (cx & 0x00000001) { sse_level = 3; // set DENORMALS_ARE_ZERO to ON _mm_setcsr(_mm_getcsr() | 0x0040); } // we should check for AMD K8 without SSE3 here ... // if(AMD_K8_NO_SSE3) // ... } } } else // clear underflow and precision flags // and set DAZ and FTZ to OFF // and restore round to nearest (RN) _mm_setcsr(_mm_getcsr() & ~(0x0030|0x8000|0x0040|0x6000)); return sse_level; } #include // sigset obsolete? typedef void (*sighandler_t)(int); sighandler_t sigset(int sig, sighandler_t disp); #include static void bye(int value) { fprintf(stderr,"exit (%d)\n",value); exit(value); } /** * Adapted from ALSA 0.9.0 HOWTO * by Matthias Nagorni, last update: 6 May 2002 * */ static snd_pcm_t* alsa_pcm_init( unsigned int rate, unsigned int periods, snd_pcm_uframes_t periodsize, bool hda, unsigned int channels,const char* device,bool capture) { set_DAZ_and_FTZ(true); sigset(SIGINT,&bye); sigset(SIGTERM,&bye); // Name of the PCM device, like plughw:0,0 // The first number is the number of the soundcard, // the second number is the number of the device. char *pcm_name; if(device) pcm_name = strdup(device); else pcm_name = strdup("hw:0,0"); // Handle for the PCM device snd_pcm_t *pcm_handle; // Playback or capture stream snd_pcm_stream_t stream; if(capture) stream = SND_PCM_STREAM_CAPTURE; else stream = SND_PCM_STREAM_PLAYBACK; // This structure contains information about // the hardware and can be used to specify the // configuration to be used for the PCM stream. snd_pcm_hw_params_t *hwparams; // Allocate the snd_pcm_hw_params_t structure on the stack. snd_pcm_hw_params_malloc(&hwparams); // Open PCM. The last parameter of this function is the mode. // If this is set to 0, the standard mode is used. Possible // other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC. // If SND_PCM_NONBLOCK is used, read / write access to the // PCM device will return immediately. If SND_PCM_ASYNC is // specified, SIGIO will be emitted whenever a period has // been completely processed by the soundcard. if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) { fprintf(stderr, "Error opening PCM device %s\n", pcm_name); return(0); } // Init hwparams with full configuration space if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) { fprintf(stderr, "Can not configure this PCM device.\n"); return(0); } // Sample rate returned by // snd_pcm_hw_params_set_rate_near unsigned int exact_rate; // Set access type. This can be either // SND_PCM_ACCESS_RW_INTERLEAVED or // SND_PCM_ACCESS_RW_NONINTERLEAVED. // There are also access types for MMAPed // access, but this is beyond the scope // of this introduction. if (snd_pcm_hw_params_set_access(pcm_handle,hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { fprintf(stderr, "Error setting access.\n"); return(0); } // Set sample format if(hda) { if (snd_pcm_hw_params_set_format(pcm_handle,hwparams, SND_PCM_FORMAT_S32_LE) < 0) { fprintf(stderr, "Error setting HD Audio format.\n"); return(0); } } else { if (snd_pcm_hw_params_set_format(pcm_handle,hwparams, SND_PCM_FORMAT_S16_LE) < 0) { fprintf(stderr, "Error setting CD/DAT format.\n"); return(0); } } // Set sample rate. If the exact rate is not supported // by the hardware, use nearest possible rate. exact_rate = rate; if (snd_pcm_hw_params_set_rate_near(pcm_handle,hwparams, &exact_rate, 0) < 0) { fprintf(stderr, "Error setting rate.\n"); return(0); } if (rate != exact_rate) { fprintf(stderr, "The rate %d Hz is not supported by your hardware.\n" "==> Use %d Hz instead.\n", rate, exact_rate); return 0; } // Set number of channels if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, channels) < 0) { fprintf(stderr, "Error setting channels.\n"); return(0); } // Set number of periods. Periods used to be called fragments. if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0) { fprintf(stderr, "Error setting periods: %d\n",periods); return(0); } // Set buffer size (in frames). The resulting latency is given by // latency = periodsize * periods / rate if (snd_pcm_hw_params_set_buffer_size(pcm_handle,hwparams, periodsize * periods) < 0) { fprintf(stderr, "\nError setting buffersize.\n" "Periodsize == %u not supported for %s\n\n", (uint)periodsize,pcm_name); return(0); } // Apply HW parameter settings to // PCM device and prepare device if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) { fprintf(stderr, "Error setting HW params.\n"); return(0); } // We would like to run in realtime if((periods * periodsize) < 2048) { static struct sched_param schp; memset(&schp, 0, sizeof(schp)); schp.sched_priority = 50;//sched_get_priority_max(SCHED_FIFO) * 0.9; if (sched_setscheduler (0, SCHED_FIFO, &schp) != 0) fprintf(stderr,"\n Sorry, can't get permission to run in realtime!\n\n"); } else fprintf(stderr,"\n not running in realtime!\n\n"); return pcm_handle; } snd_pcm_t* alsa_play_init( unsigned int rate, unsigned int periods, snd_pcm_uframes_t periodsize, bool hda, unsigned int channels,const char* device) { bool capture = false; return alsa_pcm_init(rate, periods, periodsize, hda, channels, device,capture); } snd_pcm_t* alsa_capture_init( unsigned int rate, unsigned int periods, snd_pcm_uframes_t periodsize, bool hda, unsigned int channels,const char* device) { bool capture = true; return alsa_pcm_init(rate, periods, periodsize, hda, channels, device,capture); } int alsa_play(snd_pcm_t* pcm_handle, char * buffer, snd_pcm_uframes_t samples) { int rc = snd_pcm_writei(pcm_handle, buffer, samples); if (rc == -EPIPE) { // EPIPE means underrun //fprintf(stderr, "underrun occurred\n"); snd_pcm_prepare(pcm_handle); } else if (rc < 0) { fprintf(stderr, "error from writei: %s\n", snd_strerror(rc)); } else if (rc != (int)samples) { fprintf(stderr, "short write, wrote %d frames\n", rc); } return rc; } int alsa_capture(snd_pcm_t* pcm_handle, char * buffer, snd_pcm_uframes_t samples) { int rc = snd_pcm_readi(pcm_handle, buffer, samples); if (rc == -EPIPE) { // EPIPE means overrrun //fprintf(stderr, "overrrun occurred\n"); snd_pcm_prepare(pcm_handle); } else if (rc < 0) { fprintf(stderr, "error from readi: %s\n", snd_strerror(rc)); } else if (rc != (int)samples) { fprintf(stderr, "short read, read %d frames\n", rc); } return rc; } #ifdef MAIN int main(int argc,char** argv) { int samplerate = 48000; int periods = 3; snd_pcm_uframes_t periodsize = 128; bool hda = true; int channels = 2; char* device = NULL; // defaults to hw:0:0; snd_pcm_t* pcm_handle[2]; int rc; // io error // Use a stdio buffer large enough to hold one period int size = periodsize * channels * (hda?4:2); int *pcm = malloc(size); bzero(pcm,size); #ifdef CAPTURE int *mix = malloc(size); bzero(mix,size); pcm_handle[0]= alsa_capture_init(samplerate, periods, periodsize, hda, channels, "hw:0,0,0"); if(!pcm_handle[0]) exit(EXIT_FAILURE); pcm_handle[1]= alsa_capture_init(samplerate, periods, periodsize, hda, channels, "hw:0,0,1"); if(!pcm_handle[1]) exit(EXIT_FAILURE); for (;;) { int i; alsa_capture(pcm_handle[0],(char*)pcm,periodsize); i = 1; alsa_capture(pcm_handle[1],(char*)mix,periodsize); for(i = 0; i < periodsize*2; ++i) mix[i] = (pcm[i]>>1) + (mix[i]>>1); rc = write(1, mix, size); if (rc == 0) { fprintf(stderr, "end of file on input\n"); break; } else if (rc != size) { fprintf(stderr, "short read: read %d bytes\n", rc); } } #else // PLAYBACK pcm_handle[0] = alsa_play_init(samplerate, periods, periodsize, hda, channels, device); if(!pcm_handle[0]) exit(EXIT_FAILURE); for (;;) { rc = read(0, pcm, size); if (rc == 0) { fprintf(stderr, "end of file on input\n"); break; } else if (rc != size) { fprintf(stderr, "short read: read %d bytes\n", rc); } alsa_play(pcm_handle[0],(char*)pcm,periodsize); } #endif // This program will never get to the cleanup phase ... snd_pcm_drain(pcm_handle[0]); snd_pcm_close(pcm_handle[0]); free(pcm); return 0; } #endif