/* Performance (ReiserFS Disk IO) Test of all extensions Determine file length at the beginning Keyboard shortcuts Too early abort with unfiltered WAVs No Wait for Coprocesses AAC doesn't work Process ID3 Tags V1/1.1 Playlength is only correct with 4 Bytes per Sample (2*16 bit) No support of 24 or 32 bit */ #define USE_OSS_AUDIO #define USE_NICE #define USE_REALTIME #define BLOCK 1024 #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct { const char* const ext; const char* const argv [8]; } decoder_t; // ogg, mpc, mp3, mp2, #define PATH "/usr/local/bin/" #define STDERR " 2> /dev/null" const decoder_t decoder [] = { { ".mp1" , { PATH"mpg123" , "-w", "-", "/dev/fd/0" , NULL }}, // MPEG Layer I : www.iis.fhg.de, www.mpeg.org { ".mp2" , { PATH"mpg123" , "-w", "-", "/dev/fd/0" , NULL }}, // MPEG Layer II : www.iis.fhg.de, www.uq.net.au/~zzmcheng, www.mpeg.org { ".mp3" , { PATH"mpg123" , "-w", "-", "/dev/fd/0" , NULL }}, // MPEG Layer III : www.iis.fhg.de, www.mp3dev.org/mp3, www.mpeg.org { ".mp3pro" , { PATH"mpg123" , "-w", "-", "/dev/fd/0" , NULL }}, // MPEG Layer III : www.iis.fhg.de, www.mp3dev.org/mp3, www.mpeg.org { ".mpt" , { PATH"mpg123" , "-w", "-", "/dev/fd/0" , NULL }}, // MPEG Layer III : www.iis.fhg.de, www.mp3dev.org/mp3, www.mpeg.org { ".mpp" , { PATH"mppdec" , "-", "-" , NULL }}, // MPEGplus : www.stud.uni-hannover.de/user/73884 { ".mpc" , { PATH"mppdec" , "-", "-" , NULL }}, // MPEGplus : www.stud.uni-hannover.de/user/73884 { ".mp+" , { PATH"mppdec" , "-", "-" , NULL }}, // MPEGplus : www.stud.uni-hannover.de/user/73884 { ".aac" , { PATH"faad" , "-t.wav", "-w", "/dev/fd/0" , NULL }}, // Advanced Audio Coding: psytel.hypermart.net, www.aac-tech.com, sourceforge.net/projects/faac, www.aac-audio.com, www.mpeg.org { ".mp4" , { PATH"faad" , "-t.wav", "-w", "/dev/fd/0" , NULL }}, // Advanced Audio Coding: psytel.hypermart.net, www.aac-tech.com, sourceforge.net/projects/faac, www.aac-audio.com, www.mpeg.org { "aac.lqt" , { PATH"faad" , "-t.wav", "-w", "/dev/fd/0" , NULL }}, // Advanced Audio Coding: psytel.hypermart.net, www.aac-tech.com, sourceforge.net/projects/faac, www.aac-audio.com, www.mpeg.org { ".ac3" , { PATH"ac3dec" , "/dev/fd/0" , NULL }}, // Dolby AC3 : www.att.com { "ac3.lqt" , { PATH"ac3dec" , "/dev/fd/0" , NULL }}, // Dolby AC3 : www.att.com // { ".ogg" , { PATH"ogg123" , "-d", "wav", "-o", "file:/dev/fd/1", "/dev/fd/0" , NULL }}, // Ogg Vorbis : www.xiph.org/ogg/vorbis/index.html { ".ogg" , { PATH"ogg123" , "-d", "wav", "-f", "/dev/fd/1", "/dev/fd/0" , NULL }}, // Ogg Vorbis : www.xiph.org/ogg/vorbis/index.html { ".pac" , { PATH"lpac" , "-x", "-o", "/dev/fd/0" , NULL }}, // Lossless predictive Audio Compression: www-ft.ee.tu-berlin.de/~liebchen/lpac.html (liebchen@ft.ee.tu-berlin.de) { ".shn" , { PATH"shorten", "-x" , NULL }}, // Shorten : shnutils.freeshell.org, www.softsound.com/Shorten.html (shnutils@freeshell.org, shorten@softsound.com) { ".gz" , { "gzip" , "-d" , NULL }}, // gziped WAV { ".sz" , { PATH"szip" , "-d" , NULL }}, // sziped WAV { ".sz2" , { PATH"szip2" , "-d" , NULL }}, // sziped WAV { ".bz" , { PATH"bzip" , "-d", "-" , NULL }}, // bziped WAV { ".bz2" , { "bzip2" , "-d", "-" , NULL }}, // bziped WAV { ".raw" , { "sox" , "-r44100 -sw -c2 -traw /dev/fd/0 -twav -sw -" , NULL }}, // raw files are treated as CD like audio { ".cdr" , { "sox" , "-r44100 -sw -c2 -traw /dev/fd/0 -twav -sw -" , NULL }}, // CD-DA files are treated as CD like audio, no preemphasis info available { ".flac" , { PATH"flac" , "-c", "-d", "/dev/fd/0" , NULL }}, // Free Lossless Audio Coder: flac.sourceforge.net/ { ".fla" , { PATH"flac" , "-c", "-d", "/dev/fd/0" , NULL }}, // Free Lossless Audio Coder: flac.sourceforge.net/ { ".ape" , { PATH"mac" , "/dev/stdin", "/dev/stdout", "-d" , NULL }}, // APE { ".ofr" , { PATH"optimfrog", "d", "/dev/fd/0", "-" , NULL }}, // OFR { ".la" , { PATH"la" , "-console", "/dev/fd/0" , NULL }}, // LA { ".mod" , { "xmp" , "-b16", "-c", "-f44100", "--stereo", "-o-", "/dev/fd/0" , NULL }}, // Amiga's Music on Disk: // { "" , { "sox" , "/dev/fd/0", "-twav", "-sw", "-" , NULL }}, // Rest, may be sox can handle it }; #undef PATH #if defined USE_OSS_AUDIO # include # include # if defined __linux__ # include # elif defined __bsdi__ # include # elif defined __FreeBSD__ # include # elif defined __NetBSD__ || defined __OpenBSD__ # include # else # include # endif #endif /* USE_OSS_AUDIO */ #if defined USE_ESD_AUDIO # include #endif #if defined USE_SUN_AUDIO # include #endif #if defined USE_NICE # include #endif // scheduler stuff #if defined USE_REALTIME # include #endif #ifndef O_BINARY # ifdef _O_BINARY # define O_BINARY _O_BINARY # else # define O_BINARY 0 # endif #endif #if defined _WIN32 || defined __TURBOC__ # define strncasecmp(s1,s2,n) strnicmp (s1, s2, n) # define strcasecmp(s1,s2,n) stricmp (s1, s2) #endif static void Set_Realtime ( void ) { # if defined USE_REALTIME // works for all POSIX 1b-conform systems, also the memory should be locked struct sched_param sp; memset ( &sp, 0, sizeof(sp) ); seteuid ( 0 ); sp.sched_priority = sched_get_priority_min ( SCHED_FIFO ); sched_setscheduler ( 0, SCHED_RR, &sp ); seteuid ( getuid() ); # endif # if defined USE_NICE seteuid ( 0 ); setpriority ( PRIO_PROCESS, getpid(), -20 ); seteuid ( getuid() ); # endif } /* * * Manpages of pipe(2), fork(2), dup2(2), execve(2) respectively exec(3), including waitpid(2) respectively sigaction(2)+signal(7). * * 1) create a pipe with pipe(2) * 2) fork(2) * * if fork successful: * * Parent process: * E3) close the write end of the pipe * E4) read the data from the pipe, wait for the end of the child and process errors * E5) clean up * * Child process: * K3) close the write end of the pipe * K4) dup2(2)licate fd on stdin * K5) dup2(2)licate the write end of the pipe on stdout * K6) think of something good for stderr ;-) * K7) exec(2/3)ute A * */ int filter ( int fdi, char** argv ) { int fd [2]; pid_t pid; int i; if ( 0 != pipe (fd) ) exit (1); pid = fork (); switch ( pid ) { case -1: /* error */ exit (2); case 0: /* child process */ dup2 ( fdi , STDIN_FILENO ); dup2 ( fd [1], STDOUT_FILENO ); close ( STDERR_FILENO ); for ( i = 3; i < 256; i++ ) close (i); Set_Realtime (); execvp ( argv [0], argv ); break; default: /* parent process */ close (fd [1]); break; } // fcntl ( fd [0], F_SETFD, FD_CLOEXEC); return fd [0]; } int test_for_filters ( int fd, const char* name ) { const char* nameend = name + strlen(name); size_t i; size_t sl; rep: for ( i = 0; i < sizeof(decoder)/sizeof(*decoder); i++ ) { sl = strlen(decoder[i].ext); if (nameend - sl >= name && 0 == strncasecmp (nameend - sl, decoder[i].ext, sl) ) { nameend -= sl; fd = filter (fd, decoder[i].argv ); goto rep; } } return fd; } typedef unsigned long u32; typedef unsigned short u16; typedef unsigned char label[4]; typedef struct { label riff; u32 File_Length; } Prefix; typedef struct { u16 is_PCM; u16 Channels; u32 Sample_Frequency; u32 Bytes_per_sec; u16 Bytes_per_Sample; u16 Bits; } Format; typedef struct { Prefix P; label wave; label fmt; u32 fmt_Length; Format F; label data; u32 Sample_Length; } Header; static long defaults ( long val, long std ) { return val ? val : std; } static void message ( unsigned long samples, unsigned long samptot, unsigned long sampfreq ) { if ( samptot > 0 && samptot < 100*60*sampfreq ) fprintf ( stderr, "%2u:%02u.%03u/%2u:%02u.%03u\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", samples/sampfreq/60, samples/sampfreq%60, samples%sampfreq*1000/sampfreq, samptot/sampfreq/60, samptot/sampfreq%60, samptot%sampfreq*1000/sampfreq ); else fprintf ( stderr, "%2u:%02u.%03u\b\b\b\b\b\b\b\b\b", samples/sampfreq/60, samples/sampfreq%60, samples%sampfreq*1000/sampfreq ); } static int last_channels = -1; static int last_freq = -1; static int last_fmt = -1; static int last_size = -1; static int channels; static int freq; static int fmt; static int size; static unsigned char A [BLOCK * 8 * 4]; static signed short B [BLOCK] [2]; static void play ( int fdd, int fd, const char* name ) { Header H; int org; /* argument for ioctl calls */ int arg; /* argument for ioctl calls */ int status; /* return status of system calls */ unsigned long filelen; unsigned long long toplay; unsigned long totalsamples; size_t len; size_t tmp; ssize_t bytesread; ssize_t byteswrote; size_t samples; size_t totalread; int i; int finish; unsigned char* p; if ( sizeof (H) != 44 ) { fprintf ( stderr,"%s: program malfunction: Header struct not 44 bytes long.\nCompile without struct alignment and try again.\a\n", "wp" ); exit (1); } fd = test_for_filters ( fd, name ); if ( sizeof(H)-8 != read ( fd, &H, sizeof (H)-8 )) return; fprintf (stderr, "\r\033[7m\r%s\033[0m\033[K\n", name ); if ( 0 != memcmp (H.P.riff, "RIFF", 4) ) { fprintf (stderr, "not a WAV file\n"); return; } do { memmove (H.data+0, H.data+1, 3); if (read (fd, H.data+3, 1) != 1) return; } while ( 0 != memcmp (H.data, "data", 4) ); read ( fd, &H.Sample_Length, 4 ); freq = defaults (H.F.Sample_Frequency, 44100); /* set sampling parameters: sampling rate */ channels = defaults (H.F.Channels, 2); /* set sampling parameters: mono or stereo */ size = defaults (H.F.Channels ? 8*H.F.Bytes_per_Sample/H.F.Channels : 0, 16); /* set sampling parameters: sample size */ #if 0 // Moved to front if ( -1 == (status = ioctl (fdd, SOUND_PCM_SYNC, 0)) ) perror ("SOUND_PCM_SYNC ioctl failed"); #endif org = arg = 2; if ( arg != last_channels && -1 == (status = ioctl (fdd, SOUND_PCM_WRITE_CHANNELS, &arg)) ) perror ("SOUND_PCM_WRITE_CHANNELS ioctl failed"); if (arg != org) perror ("unable to set number of channels"); last_channels = arg; org = arg = 16; if ( arg != last_size && -1 == (status = ioctl (fdd, SOUND_PCM_WRITE_BITS, &arg)) ) perror ("SOUND_PCM_WRITE_BITS ioctl failed"); if (arg != org) perror ("unable to set sample size"); last_size = arg; org = arg = org <= 8 ? AFMT_U8 : AFMT_S16_LE; if ( arg != last_fmt && -1 == ioctl (fdd, SNDCTL_DSP_SETFMT, &arg) ) perror ("SNDCTL_DSP_SETFMT ioctl failed"); if ((arg & org) == 0) perror ("unable to set data format"); last_fmt = arg; org = arg = freq; /* set sampling parameters: sampling rate */ if ( arg != last_freq && -1 == (status = ioctl (fdd, SOUND_PCM_WRITE_RATE, &arg)) ) perror ("SOUND_PCM_WRITE_WRITE ioctl failed"); last_freq = arg; fprintf (stderr, "\r%1u*%2u bit %5u Hz: ", channels, size, freq ); fflush (stderr); toplay = H.Sample_Length < 0x7FFFFFFF && H.Sample_Length > 0 ? H.Sample_Length / (channels*(size/8)) : 0xFFFFFFFFFFFFFFFF; totalread = 0; totalsamples = 0; message ( totalsamples, toplay, freq ); for ( finish = 0; !finish; ) { if ( toplay-totalsamples <= BLOCK ) len = (toplay-totalsamples) * channels * (size/8), finish = 1; else len = BLOCK * channels * (size/8); bytesread = 0; do { tmp = read ( fd, A+bytesread, len-bytesread ); if ( tmp <= 0 ) { finish = 1; break; } bytesread += tmp; } while ( bytesread < len ); totalread += bytesread; samples = bytesread / (channels * (size/8)); totalsamples += samples; message ( totalsamples, toplay, freq ); p = A; switch (channels) { case 1: switch (size) { case 8: for ( i = 0; i < samples; i++, p++ ) B [i][0] = B [i][1] = (*p-128) << 8; break; case 16: for ( i = 0; i < samples; i++, p+=2 ) B [i][0] = B [i][1] = *(short*)(p); break; case 24: for ( i = 0; i < samples; i++, p+=3 ) B [i][0] = B [i][1] = *(short*)(p+1); break; case 32: for ( i = 0; i < samples; i++, p+=4 ) B [i][0] = B [i][1] = *(short*)(p+2); break; } break; case 2: switch (size) { case 8: for ( i = 0; i < samples; i++, p+=2 ) B [i][0] = (p[0]-128) << 8, B [i][1] = (p[1]-128) << 8; break; case 16: for ( i = 0; i < samples; i++, p+=4 ) B [i][0] = *(short*)(p+0), B [i][1] = *(short*)(p+2); break; case 24: for ( i = 0; i < samples; i++, p+=6 ) B [i][0] = *(short*)(p+1), B [i][1] = *(short*)(p+4); break; case 32: for ( i = 0; i < samples; i++, p+=8 ) B [i][0] = *(short*)(p+2), B [i][1] = *(short*)(p+6); break; } break; default: switch (size) { case 8: for ( i = 0; i < samples; i++, p+=1*channels ) B [i][0] = (p[0]-128) << 8, B [i][0] = (p[1]-128) << 8; break; case 16: for ( i = 0; i < samples; i++, p+=2*channels ) B [i][0] = *(short*)(p+0), B [i][0] = *(short*)(p+2); break; case 24: for ( i = 0; i < samples; i++, p+=3*channels ) B [i][0] = *(short*)(p+1), B [i][0] = *(short*)(p+4); break; case 32: for ( i = 0; i < samples; i++, p+=4*channels ) B [i][0] = *(short*)(p+2), B [i][0] = *(short*)(p+6); break; } break; } len = samples * (16/8 * 2); byteswrote = 0; while ( byteswrote < len ) { tmp = write ( fdd, B+byteswrote, len-byteswrote ); if ( tmp <= 0 ) { perror ("Wrote wrong number of bytes"); finish = 1; break; } byteswrote += tmp; } } return; } int main ( int argc, char** argv ) { int fds; int fdd; int fdm; int org; /* argument for ioctl calls */ int arg; /* argument for ioctl calls */ int status; /* return status of system calls */ const char* name; seteuid ( getuid() ); #if 0 if ( (fdd = open ("/dev/audio0", O_WRONLY)) < 0 ) { /* open sound device */ perror ("open of /dev/audio0 failed"); return 1; } if ( (fdm = open ("/dev/mixer0", O_RDWR)) < 0 ) { /* open mixer device */ perror ("open of /dev/mixer0 failed"); return 1; } org = arg = 0x6060; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_VOLUME, &arg)) ) perror ("SOUND_MIXER_WRITE_VOLUME ioctl failed"); org = arg = 0x5A5A; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_PCM, &arg)) ) perror ("SOUND_MIXER_WRITE_PCM ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_MIC, &arg)) ) perror ("SOUND_MIXER_WRITE_MIC ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_SYNTH, &arg)) ) perror ("SOUND_MIXER_WRITE_SYNTH ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_IMIX, &arg)) ) perror ("SOUND_MIXER_WRITE_IMIX ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_LINE1, &arg)) ) perror ("SOUND_MIXER_WRITE_LINE1 ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_LINE2, &arg)) ) perror ("SOUND_MIXER_WRITE_LINE2 ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_LINE3, &arg)) ) perror ("SOUND_MIXER_WRITE_LINE3 ioctl failed"); close (fdm); #else if ( (fdd = open ("/dev/audio2", O_WRONLY)) < 0 ) { /* open sound device */ perror ("open of /dev/audio2 failed"); return 1; } if ( (fdm = open ("/dev/mixer1", O_RDWR)) < 0 ) { /* open mixer device */ perror ("open of /dev/mixer1 failed"); return 1; } org = arg = 0x6060; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_VOLUME, &arg)) ) perror ("SOUND_MIXER_WRITE_VOLUME ioctl failed"); org = arg = 0x5A5A; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_PCM, &arg)) ) perror ("SOUND_MIXER_WRITE_PCM ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_MIC, &arg)) ) perror ("SOUND_MIXER_WRITE_MIC ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_SYNTH, &arg)) ) perror ("SOUND_MIXER_WRITE_SYNTH ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_IMIX, &arg)) ) perror ("SOUND_MIXER_WRITE_IMIX ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_LINE1, &arg)) ) perror ("SOUND_MIXER_WRITE_LINE1 ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_LINE2, &arg)) ) perror ("SOUND_MIXER_WRITE_LINE2 ioctl failed"); org = arg = 0x0000; if ( -1 == (status = ioctl (fdm, SOUND_MIXER_WRITE_LINE3, &arg)) ) perror ("SOUND_MIXER_WRITE_LINE3 ioctl failed"); close (fdm); #endif #if 0 #define SOUND_MIXER_SPEAKER 5 #define SOUND_MIXER_LINE 6 #define SOUND_MIXER_CD 8 #define SOUND_MIXER_ALTPCM 10 #define SOUND_MIXER_RECLEV 11 /* Recording level */ #define SOUND_MIXER_IGAIN 12 /* Input gain */ #define SOUND_MIXER_OGAIN 13 /* Output gain */ #define SOUND_MIXER_DIGITAL1 17 /* Digital (input) 1 */ #define SOUND_MIXER_DIGITAL2 18 /* Digital (input) 2 */ #define SOUND_MIXER_DIGITAL3 19 /* Digital (input) 3 */ #define SOUND_MIXER_PHONEIN 20 /* Phone input */ #define SOUND_MIXER_PHONEOUT 21 /* Phone output */ #define SOUND_MIXER_VIDEO 22 /* Video/TV (audio) in */ #define SOUND_MIXER_RADIO 23 /* Radio in */ #define SOUND_MIXER_MONITOR 24 /* Monitor (usually mic) volume */ #endif mlock (A, sizeof (A) ); mlock (B, sizeof (B) ); Set_Realtime (); if ( argc <= 1 ) play ( fdd, 0, "" ); else while ( (name = *++argv) != NULL ) { if ( (fds = open (*argv, O_RDONLY)) < 0 ) { perror ("open of file failed"); continue; } play ( fdd, fds, name ); close ( fds ); } write (fdd, "\0\0\0\0\0\0\0\0\0\0\0\0", 12); close (fdd); fprintf (stderr, "\n"); return 0; } #if 0 static struct termios stored_settings; void reset ( void ) { tcsetattr ( 0, TCSANOW, &stored_settings ); } void set ( void ) { struct termios new_settings; tcgetattr ( 0, &stored_settings ); new_settings = stored_settings; new_settings.c_lflag &= ~ECHO; /* Disable canonical mode, and set buffer size to 1 byte */ new_settings.c_lflag &= ~ICANON; new_settings.c_cc[VTIME] = 0; new_settings.c_cc[VMIN] = 1; tcsetattr(0,TCSANOW,&new_settings); return; } int sel ( void ) { struct timeval t; fd_set fd [1]; int ret; unsigned char c; FD_SET (0, fd); t.tv_sec = 0; t.tv_usec = 0; ret = select ( 1, fd, NULL, NULL, &t ); switch ( ret ) { case 0: return -1; case 1: ret = read (0, &c, 1); return ret == 1 ? c : -1; default: return -2; } } #endif