Blame |
Last modification |
View Log
| RSS feed
/*
* Mpeg Layer audio decoder (see version.h for version number)
* ------------------------
* copyright (c) 1995,1996,1997 by Michael Hipp, All rights reserved.
* See also 'README' !
*
*/
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
/* #define SET_PRIO */
#include "mpg123.h"
#include "getlopt.h"
#include "version.h"
static void usage
(char *dummy
);
static void print_title
(void);
static long rates
[3][3] = {
{ 32000,44100,48000 } ,
{ 16000,22050,24000 } ,
{ 8000,11025,12000 }
};
int supported_rates
= 0;
int outmode
= DECODE_AUDIO
;
char *listname
= NULL
;
long outscale
= 32768;
int checkrange
= FALSE
;
int tryresync
= TRUE
;
int quiet
= FALSE
;
int verbose
= 0;
int doublespeed
= 0;
int halfspeed
= 0;
int shuffle
= 0;
int change_always
= 1;
int force_8bit
= 0;
int force_frequency
= -1;
long numframes
= -1;
long startFrame
= 0;
int usebuffer
= 0;
int frontend_type
= 0;
int remote
= 0;
int buffer_fd
[2];
int buffer_pid
;
static void catch_child
(void)
{
while (waitpid
(-1, NULL
, WNOHANG
) > 0);
}
static int intflag
= FALSE
;
static int remflag
= FALSE
;
static void catch_interrupt
(void)
{
intflag
= TRUE
;
}
static char remote_buffer
[1024];
static struct frame fr
;
static struct audio_info_struct ai
;
txfermem
*buffermem
;
#define FRAMEBUFUNIT (18 * 64 * 4)
void print_rheader
(struct frame
*fr
);
static void catch_remote
(void)
{
remflag
= TRUE
;
intflag
= TRUE
;
if(usebuffer
)
kill
(buffer_pid
,SIGINT
);
}
char *handle_remote
(void)
{
switch(frontend_type
) {
case FRONTEND_SAJBER
:
#ifdef FRONTEND
control_sajber
(&fr
);
#endif
break;
case FRONTEND_TK3PLAY
:
#ifdef FRONTEND
control_tk3play
(&fr
);
#endif
break;
default:
fgets(remote_buffer
,1024,stdin
);
remote_buffer
[strlen(remote_buffer
)-1]=0;
switch(remote_buffer
[0]) {
case 'P':
return remote_buffer
+1;
}
if(usebuffer
)
kill
(buffer_pid
,SIGINT
);
break;
}
return NULL
;
}
void init_output
(void)
{
static int init_done
= FALSE
;
if (init_done
)
return;
init_done
= TRUE
;
#ifndef OS2
if (usebuffer
) {
unsigned int bufferbytes
;
sigset_t newsigset
, oldsigset
;
if (usebuffer
< 32)
usebuffer
= 32; /* minimum is 32 Kbytes! */
bufferbytes
= (usebuffer
* 1024);
bufferbytes
-= bufferbytes
% FRAMEBUFUNIT
;
xfermem_init
(&buffermem
, bufferbytes
, sizeof(ai.
rate));
pcm_sample
= (unsigned char *) buffermem
->data
;
pcm_point
= 0;
sigemptyset
(&newsigset
);
sigaddset
(&newsigset
, SIGUSR1
);
sigprocmask
(SIG_BLOCK
, &newsigset
, &oldsigset
);
catchsignal
(SIGCHLD
, catch_child
);
switch ((buffer_pid
= fork
())) {
case -1: /* error */
perror("fork()");
exit(1);
case 0: /* child */
xfermem_init_reader
(buffermem
);
buffer_loop
(&ai
, &oldsigset
);
xfermem_done_reader
(buffermem
);
xfermem_done
(buffermem
);
_exit
(0);
default: /* parent */
xfermem_init_writer
(buffermem
);
outmode
= DECODE_BUFFER
;
}
}
else {
#endif
if (!(pcm_sample
= (unsigned char *) malloc(audiobufsize
* 2))) {
perror ("malloc()");
exit (1);
#ifndef OS2
}
#endif
}
if(outmode
==DECODE_AUDIO
) {
if(audio_open
(&ai
) < 0) {
perror("audio");
exit(1);
}
/* audio_set_rate (&ai); should already be done in audio_open() [OF] */
}
}
char *get_next_file
(int argc
, char *argv
[])
{
static FILE
*listfile
= NULL
;
static char line
[1024];
#ifdef SHUFFLESUPPORT
static int ord
[2048];
int temp
, randomized
,pos
;
static char initialized
=0;
time_t t
;
#endif
if (remote
)
return handle_remote
();
if (listname
|| listfile
) {
if (!listfile
) {
if (!*listname
|| !strcmp(listname
, "-")) {
listfile
= stdin
;
listname
= NULL
;
}
else if (!strncmp(listname
, "http://", 7))
listfile
= http_open
(listname
);
else if (!(listfile
= fopen(listname
, "rb"))) {
perror (listname
);
exit (1);
}
if (verbose
)
fprintf (stderr
, "Using playlist from %s ...\n",
listname
? listname
: "standard input");
}
do {
if (fgets(line
, 1023, listfile
)) {
line
[strcspn(line
, "\t\n\r")] = '\0';
if (line
[0]=='\0' || line
[0]=='#')
continue;
return (line
);
}
else {
if (*listname
)
fclose (listfile
);
listname
= NULL
;
listfile
= NULL
;
}
} while (listfile
);
}
#ifdef SHUFFLESUPPORT
if(!initialized
){
for(pos
=0;pos
<=argc
; pos
++) ord
[pos
]=pos
;
initialized
=1;
}
if(shuffle
){
fprintf(stderr
, "\nShuffle play - %u file(s) in loop.\n", argc
-loptind
);
srandom
(time(&t
));
for(pos
=loptind
;pos
<argc
;pos
++){
randomized
=(random
()%(argc
-pos
))+pos
;
temp
=ord
[pos
];
ord
[pos
]=ord
[randomized
];
ord
[randomized
]=temp
;
}
shuffle
=0;
}
if (loptind
< argc
)
return (argv
[ord
[loptind
++]]);
return (NULL
);
#else
if (loptind
< argc
)
return (argv
[loptind
++]);
return (NULL
);
#endif
}
void set_synth
(char *arg
)
{
if (*arg
== '2') {
fr.
down_sample = 1;
}
else {
fr.
down_sample = 2;
}
}
#ifdef VARMODESUPPORT
void set_varmode
(char *arg
)
{
audiobufsize
= ((audiobufsize
>> 1) + 63) & 0xffffc0;
}
#endif
void set_output
(char *arg
)
{
switch (*arg
) {
case 'h': ai.
output = AUDIO_OUT_HEADPHONES
; break;
case 's': ai.
output = AUDIO_OUT_INTERNAL_SPEAKER
; break;
case 'l': ai.
output = AUDIO_OUT_LINE_OUT
; break;
default:
fprintf (stderr
, "%s: Unknown argument \"%s\" to option \"%s\".\n",
prgName
, arg
, loptarg
);
exit (1);
}
}
void set_verbose
(char *arg
)
{
verbose
++;
}
topt opts
[] = {
{'k', "skip", GLO_ARG
| GLO_NUM
, 0, &startFrame
, 0},
{'a', "audiodevice", GLO_ARG
| GLO_CHAR
, 0, &ai.
device, 0},
{'2', "2to1", 0, set_synth
, 0, 0},
{'4', "4to1", 0, set_synth
, 0, 0},
{'t', "test", 0, 0, &outmode
, DECODE_TEST
},
{'s', "stdout", 0, 0, &outmode
, DECODE_STDOUT
},
{'c', "check", 0, 0, &checkrange
, TRUE
},
{'v', "verbose", 0, set_verbose
, 0, 0},
{'q', "quiet", 0, 0, &quiet
, TRUE
},
{'y', "resync", 0, 0, &tryresync
, FALSE
},
{'0', "single0", 0, 0, &fr.
single, 0},
{0, "left", 0, 0, &fr.
single, 0},
{'1', "single1", 0, 0, &fr.
single, 1},
{0, "right", 0, 0, &fr.
single, 1},
{'m', "singlemix", 0, 0, &fr.
single, 3},
{0, "mix", 0, 0, &fr.
single, 3},
{'g', "gain", GLO_ARG
| GLO_NUM
, 0, &ai.
gain, 0},
{'r', "rate", GLO_ARG
| GLO_NUM
, 0, &force_frequency
, 0},
{0, "8bit", 0, 0, &force_8bit
, 1},
{0, "headphones", 0, 0, &ai.
output, AUDIO_OUT_HEADPHONES
},
{0, "speaker", 0, 0, &ai.
output, AUDIO_OUT_INTERNAL_SPEAKER
},
{0, "lineout", 0, 0, &ai.
output, AUDIO_OUT_LINE_OUT
},
{'o', "output", GLO_ARG
| GLO_CHAR
, set_output
, 0, 0},
{'f', "scale", GLO_ARG
| GLO_NUM
, 0, &outscale
, 0},
{'n', "frames", GLO_ARG
| GLO_NUM
, 0, &numframes
, 0},
#ifdef VARMODESUPPORT
{'v', "var", 0, set_varmode
, &varmode
, TRUE
},
#endif
{'b', "buffer", GLO_ARG
| GLO_NUM
, 0, &usebuffer
, 0},
{'R', "remote", 0, 0, &remote
, TRUE
},
{'d', "doublespeed", GLO_ARG
| GLO_NUM
, 0, &doublespeed
,0},
{'h', "halfspeed", GLO_ARG
| GLO_NUM
, 0, &halfspeed
, 0},
{'p', "proxy", GLO_ARG
| GLO_CHAR
, 0, &proxyurl
, 0},
{'@', "list", GLO_ARG
| GLO_CHAR
, 0, &listname
, 0},
#ifdef SHUFFLESUPPORT
/* 'z' comes from the the german word 'zufall' (eng: random) */
{'z', "shuffle", 0, 0, &shuffle
, 1},
#endif
{'?', "help", 0, usage
, 0, 0},
{0, 0, 0, 0, 0, 0}
};
/*
* Change the playback sample rate.
*/
static void reset_audio_samplerate
(void)
{
if (usebuffer
) {
/* wait until the buffer is empty,
* then tell the buffer process to
* change the sample rate. [OF]
*/
while (xfermem_get_usedspace
(buffermem
) > 0)
if (xfermem_block
(XF_WRITER
, buffermem
) == XF_CMD_TERMINATE
) {
intflag
= TRUE
;
break;
}
buffermem
->freeindex
= -1;
buffermem
->readindex
= 0; /* I know what I'm doing! ;-) */
buffermem
->freeindex
= 0;
if (intflag
)
return;
memcpy (buffermem
->metadata
, &ai.
rate, sizeof(ai.
rate));
kill
(buffer_pid
, SIGUSR1
);
}
else if (outmode
== DECODE_AUDIO
) {
/* audio_reset_parameters(&ai); */
/* close and re-open in order to flush
* the device's internal buffer before
* changing the sample rate. [OF]
*/
audio_close
(&ai
);
if (audio_open
(&ai
) < 0) {
perror("audio");
exit(1);
}
}
}
/*
* play a frame read read_frame();
* (re)initialize audio if necessary.
*/
void play_frame
(int init
,struct frame
*fr
)
{
int clip
;
if((fr
->header_change
&& change_always
) || init
) {
int reset_audio
= 0;
if(remote
)
print_rheader
(fr
);
if (!quiet
&& init
)
if (verbose
)
print_header
(fr
);
else
print_header_compact
(fr
);
if(force_frequency
< 0) {
if(ai.
rate != freqs
[fr
->sampling_frequency
]>>(fr
->down_sample
)) {
ai.
rate = freqs
[fr
->sampling_frequency
]>>(fr
->down_sample
);
reset_audio
= 1;
}
}
else if(ai.
rate != force_frequency
) {
ai.
rate = force_frequency
;
reset_audio
= 1;
}
init_output
();
if(reset_audio
) {
reset_audio_samplerate
();
if (intflag
)
return;
}
}
if (fr
->error_protection
) {
getbits
(16); /* crc */
}
clip
= (fr
->do_layer
)(fr
,outmode
,&ai
);
#ifndef OS2
if (usebuffer
) {
if (!intflag
) {
buffermem
->freeindex
=
(buffermem
->freeindex
+ pcm_point
) % buffermem
->size
;
if (buffermem
->wakeme
[XF_READER
])
xfermem_putcmd
(buffermem
->fd
[XF_WRITER
], XF_CMD_WAKEUP
);
}
pcm_sample
= (unsigned char *) (buffermem
->data
+ buffermem
->freeindex
);
pcm_point
= 0;
while (xfermem_get_freespace
(buffermem
) < (FRAMEBUFUNIT
<< 1))
if (xfermem_block
(XF_WRITER
, buffermem
) == XF_CMD_TERMINATE
) {
intflag
= TRUE
;
break;
}
if (intflag
)
return;
}
#endif
if(clip
> 0 && checkrange
)
fprintf(stderr
,"%d samples clipped\n", clip
);
}
int main
(int argc
, char *argv
[])
{
int result
;
unsigned long frameNum
= 0;
char *fname
;
struct timeval start_time
, now
;
unsigned long secdiff
;
int init
;
#ifdef OS2
_wildcard
(&argc
,&argv
);
#endif
#ifdef SET_PRIO
int mypid
= getpid
();
setpriority
(PRIO_PROCESS
,mypid
,-20);
#endif
if(!strcmp("sajberplay",argv
[0]))
frontend_type
= FRONTEND_SAJBER
;
if(!strcmp("mpg123m",argv
[0]))
frontend_type
= FRONTEND_TK3PLAY
;
fr.
single = -1; /* both channels */
fr.
synth = synth_1to1
;
fr.
down_sample = 0;
ai.
format = AUDIO_FORMAT_SIGNED_16
;
ai.
gain = ai.
rate = ai.
output = -1;
ai.
device = NULL
;
ai.
channels = 2;
(prgName
= strrchr(argv
[0], '/')) ? prgName
++ : (prgName
= argv
[0]);
while ((result
= getlopt
(argc
, argv
, opts
)))
switch (result
) {
case GLO_UNKNOWN
:
fprintf (stderr
, "%s: Unknown option \"%s\".\n", prgName
, loptarg
);
exit (1);
case GLO_NOARG
:
fprintf (stderr
, "%s: Missing argument for option \"%s\".\n",
prgName
, loptarg
);
exit (1);
}
if (loptind
>= argc
&& !listname
&& !frontend_type
)
usage
(NULL
);
if (remote
){
verbose
= 0;
quiet
= 1;
catchsignal
(SIGUSR1
, catch_remote
);
fprintf(stderr
,"@R MPG123\n");
}
if (!quiet
)
print_title
();
{
int fmts
;
int i
,j
;
struct audio_info_struct ai
;
audio_info_struct_init
(&ai
);
if (outmode
== DECODE_AUDIO
) {
audio_open
(&ai
);
fmts
= audio_get_formats
(&ai
);
}
else
fmts
= AUDIO_FORMAT_SIGNED_16
;
supported_rates
= 0;
for(i
=0;i
<3;i
++) {
for(j
=0;j
<3;j
++) {
ai.
rate = rates
[i
][j
];
if (outmode
== DECODE_AUDIO
)
audio_rate_best_match
(&ai
);
/* allow about 2% difference */
if( ((rates
[i
][j
]*98) < (ai.
rate*100)) &&
((rates
[i
][j
]*102) > (ai.
rate*100)) )
supported_rates
|= 1<<(i
*3+j
);
}
}
if (outmode
== DECODE_AUDIO
)
audio_close
(&ai
);
if(!force_8bit
&& !(fmts
& AUDIO_FORMAT_SIGNED_16
))
force_8bit
= 1;
if(force_8bit
&& !(fmts
& AUDIO_FORMAT_ULAW_8
)) {
fprintf(stderr
,"No supported audio format found!\n");
exit(1);
}
}
if(force_8bit
) {
#if 0
ai.
format = AUDIO_FORMAT_UNSIGNED_8
;
ai.
format = AUDIO_FORMAT_SIGNED_8
;
#endif
ai.
format = AUDIO_FORMAT_ULAW_8
;
make_conv16to8_table
(ai.
format);
switch(fr.
down_sample) {
case 0:
fr.
synth = synth_1to1_8bit
;
fr.
synth_mono = synth_1to1_8bit_mono
;
fr.
block_size = 64;
break;
case 1:
fr.
synth = synth_2to1_8bit
;
fr.
synth_mono = synth_2to1_8bit_mono
;
fr.
block_size = 32;
break;
case 2:
fr.
synth = synth_4to1_8bit
;
fr.
synth_mono = synth_4to1_8bit_mono
;
fr.
block_size = 16;
break;
}
}
else {
switch(fr.
down_sample) {
case 0:
fr.
synth = synth_1to1
;
fr.
synth_mono = synth_1to1_mono
;
fr.
block_size = 128;
break;
case 1:
fr.
synth = synth_2to1
;
fr.
synth_mono = synth_2to1_mono
;
fr.
block_size = 64;
break;
case 2:
fr.
synth = synth_4to1
;
fr.
synth_mono = synth_4to1_mono
;
fr.
block_size = 32;
break;
}
}
make_decode_tables
(outscale
);
init_layer2
(); /* inits also shared tables with layer1 */
init_layer3
(fr.
down_sample);
catchsignal
(SIGINT
, catch_interrupt
);
if(frontend_type
) {
handle_remote
();
exit(0);
}
while ((fname
= get_next_file
(argc
, argv
))) {
char *dirname
, *filename
;
if(!*fname
|| !strcmp(fname
, "-"))
fname
= NULL
;
open_stream
(fname
,-1);
if (!quiet
) {
if (split_dir_file
(fname
? fname
: "standard input",
&dirname
, &filename
))
fprintf(stderr
, "\nDirectory: %s", dirname
);
fprintf(stderr
, "\nPlaying MPEG stream from %s ...\n", filename
);
}
gettimeofday
(&start_time
, NULL
);
read_frame_init
();
init
= 1;
for(frameNum
=0;read_frame
(&fr
) && numframes
&& !intflag
;frameNum
++) {
if(frameNum
< startFrame
|| (doublespeed
&& (frameNum
% doublespeed
))) {
if(fr.
lay == 3)
set_pointer
(512);
continue;
}
numframes
--;
play_frame
(init
,&fr
);
init
= 0;
if(verbose
) {
if (verbose
> 1 || !(frameNum
& 0xf))
fprintf(stderr
, "\r{%4lu} ",frameNum
);
#ifndef OS2
if (verbose
> 1 && usebuffer
)
fprintf (stderr
, "%7d ", xfermem_get_usedspace
(buffermem
));
#endif
}
}
close_stream
();
if (!quiet
) {
/* This formula seems to work at least for
* MPEG 1.0/2.0 layer 3 streams.
*/
int sfd
= freqs
[fr.
sampling_frequency] * (fr.
lsf + 1);
int secs
= (frameNum
* (fr.
lay==1 ? 384 : 1152) + sfd
/ 2) / sfd
;
fprintf(stderr
,"[%d:%02d] Decoding of %s finished.\n", secs
/ 60,
secs
% 60, filename
);
}
if(remote
)
fprintf(stderr
,"@R MPG123\n");
if (remflag
) {
intflag
= FALSE
;
remflag
= FALSE
;
}
if (intflag
) {
gettimeofday
(&now
, NULL
);
secdiff
= (now.
tv_sec - start_time.
tv_sec) * 1000;
if (now.
tv_usec >= start_time.
tv_usec)
secdiff
+= (now.
tv_usec - start_time.
tv_usec) / 1000;
else
secdiff
-= (start_time.
tv_usec - now.
tv_usec) / 1000;
if (secdiff
< 1000)
break;
intflag
= FALSE
;
}
}
#ifndef OS2
if (usebuffer
) {
xfermem_done_writer
(buffermem
);
waitpid
(buffer_pid
, NULL
, 0);
xfermem_done
(buffermem
);
}
else {
#endif
audio_flush
(outmode
, &ai
);
free (pcm_sample
);
#ifndef OS2
}
#endif
if(outmode
==DECODE_AUDIO
)
audio_close
(&ai
);
exit( 0 );
}
static void print_title
(void)
{
fprintf(stderr
,"High Performance MPEG 1.0/2.0 Audio Player for Layer 1, 2 and 3.\n");
fprintf(stderr
,"Version %s (%s). Written and copyrights by Michael Hipp.\n", prgVersion
, prgDate
);
fprintf(stderr
,"Uses code from various people. See 'README' for more!\n");
fprintf(stderr
,"THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY! USE AT YOUR OWN RISK!\n");
}
static void usage
(char *dummy
) /* print syntax & exit */
{
print_title
();
fprintf(stderr
,"\nusage: %s [option(s)] [file(s) | URL(s) | -]\n", prgName
);
fprintf(stderr
,"supported options [defaults in brackets]:\n");
fprintf(stderr
," -v increase verbosity level -q quiet (don't print title)\n");
fprintf(stderr
," -t testmode (no output) -s write to stdout\n");
fprintf(stderr
," -k n skip first n frames [0] -n n decode only n frames [all]\n");
fprintf(stderr
," -c check range violations -y DISABLE resync on errors\n");
fprintf(stderr
," -b n output buffer: n Kbytes [0] -f n change scalefactor [32768]\n");
fprintf(stderr
," -r n override samplerate [auto] -g n set audio hardware output gain\n");
fprintf(stderr
," -os output to built-in speaker -oh output to headphones\n");
fprintf(stderr
," -ol output to line-out connector -a d set audio device\n");
fprintf(stderr
," -2 downsample 1:2 (22 kHz) -4 downsample 1:4 (11 kHz)\n");
fprintf(stderr
," -d n play every n'th frame only -h n play every frame n times\n");
fprintf(stderr
," -0 decode channel 0 (left) only -1 decode channel 1 (right) only\n");
fprintf(stderr
," -m mix both channels (mono) -p p use HTTP proxy p [$HTTP_PROXY]\n");
fprintf(stderr
," -@ f read filenames/URLs from f -z shuffle play (with wildcards)\n");
fprintf(stderr
,"See the manpage %s(1) for more information.\n", prgName
);
exit(1);
}