1
0
mirror of http://mpg123.de/trunk/.git synced 2025-10-29 14:09:21 +03:00
Files
mpg123/src/control_generic.c
thor c7df68fe55 wrap buffer code in #ifndef NOXFERMEM
git-svn-id: svn://scm.orgis.org/mpg123/trunk@630 35dc7657-300d-0410-a2e5-dc2837fedb53
2007-05-02 12:08:21 +00:00

503 lines
14 KiB
C

/*
control_generic.c: control interface for frontends and real console warriors
copyright 1997-99,2004-6 by the mpg123 project - free software under the terms of the LGPL 2.1
see COPYING and AUTHORS files in distribution or http://mpg123.de
initially written by Andreas Neuhaus and Michael Hipp
reworked by Thomas Orgis - it was the entry point for eventually becoming maintainer...
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include "config.h"
#include "mpg123.h"
#include "common.h"
#include "icy.h"
#include "debug.h"
#ifdef GAPLESS
#include "layer3.h"
struct audio_info_struct pre_ai;
#endif
#define MODE_STOPPED 0
#define MODE_PLAYING 1
#define MODE_PAUSED 2
extern struct audio_info_struct ai;
extern int buffer_pid;
FILE *outstream;
void generic_sendmsg (const char *fmt, ...)
{
va_list ap;
fprintf(outstream, "@");
va_start(ap, fmt);
vfprintf(outstream, fmt, ap);
va_end(ap);
fprintf(outstream, "\n");
}
void generic_sendstat (struct frame *fr)
{
unsigned long frames_left;
double current_seconds, seconds_left;
if(!position_info(fr, fr->num, xfermem_get_usedspace(buffermem), &ai, &frames_left, &current_seconds, &seconds_left))
generic_sendmsg("F %li %lu %3.2f %3.2f", fr->num, frames_left, current_seconds, seconds_left);
}
extern char *genre_table[];
extern int genre_count;
void generic_sendinfoid3 (char *buf)
{
char info[200] = "", *c;
int i;
unsigned char genre;
for (i=0, c=buf+3; i<124; i++, c++)
info[i] = *c ? *c : ' ';
info[i] = 0;
genre = *c;
generic_sendmsg("I ID3:%s%s", info, (genre<=genre_count) ? genre_table[genre] : "Unknown");
}
void generic_sendinfo (char *filename)
{
char *s, *t;
s = strrchr(filename, '/');
if (!s)
s = filename;
else
s++;
t = strrchr(s, '.');
if (t)
*t = 0;
generic_sendmsg("I %s", s);
}
int control_generic (struct frame *fr)
{
struct timeval tv;
fd_set fds;
int n;
int mode = MODE_STOPPED;
int init = 0;
/* ThOr */
char alive = 1;
char silent = 0;
unsigned long frame_before = 0;
/* responses to stderr for frontends needing audio data from stdout */
if (param.remote_err)
outstream = stderr;
else
outstream = stdout;
setlinebuf(outstream);
/* the command behaviour is different, so is the ID */
/* now also with version for command availability */
fprintf(outstream, "@R MPG123 (ThOr) v2\n");
while (alive) {
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
/* play frame if no command needs to be processed */
if (mode == MODE_PLAYING) {
n = select(32, &fds, NULL, NULL, &tv);
if (n == 0) {
if (!read_frame(fr)) {
mode = MODE_STOPPED;
audio_flush(param.outmode, &ai);
rd->close(rd);
generic_sendmsg("P 0");
continue;
}
if(!play_frame(init,fr))
{
generic_sendmsg("E play_frame failed");
audio_flush(param.outmode, &ai);
rd->close(rd);
mode = MODE_STOPPED;
generic_sendmsg("P 0");
}
if (init) {
static char tmp[1000];
make_remote_header(fr, tmp);
generic_sendmsg(tmp);
init = 0;
}
if(!frame_before && (silent == 0))
{
generic_sendstat(fr);
if (icy.changed && icy.data)
{
generic_sendmsg("I ICY-META: %s", icy.data);
icy.changed = 0;
}
}
if(frame_before) --frame_before;
}
}
else {
/* wait for command */
while (1) {
n = select(32, &fds, NULL, NULL, NULL);
if (n > 0)
break;
}
}
/* on error */
if (n < 0) {
fprintf(stderr, "Error waiting for command: %s\n", strerror(errno));
return 1;
}
/* process command */
if (n > 0) {
short int len = 1; /* length of buffer */
short int cnum = 0; /* number of commands */
short int cind = 0; /* index for commands */
char *cmd, *comstr, *arg; /* variables for parsing, */
char buf[REMOTE_BUFFER_SIZE];
char **coms; /* list of commands */
short int counter;
coms = malloc(sizeof(*coms)); /* malloc magic */
coms[0] = &buf[0]; /* first command string */
/* read as much as possible, maybe multiple commands */
/* When there is nothing to read (EOF) or even an error, it is the end */
if((len = read(STDIN_FILENO, buf, REMOTE_BUFFER_SIZE)) < 1) break;
/* one command on a line - separation by \n -> C strings in a row */
for(counter = 0; counter < len; ++counter) {
/* line end is command end */
if( (buf[counter] == '\n') || (buf[counter] == '\r') ) {
buf[counter] = 0; /* now it's a properly ending C string */
/* next "real" char is first of next command */
if( (counter < (len - 1)) && ((buf[counter+1] == '\n') || (buf[counter+1] == '\r')) )
++counter; /* skip the additional line ender */
if(counter < (len - 1)) coms[++cind] = &buf[counter+1];
}
}
cnum = cind+1;
/*
when last command had no \n... should I discard it?
Ideally, I should remember the part and wait for next
read() to get the rest up to a \n. But that can go
to infinity. Too long commands too quickly are just
bad. Cannot/Won't change that. So, discard the unfinished
command and have fingers crossed that the rest of this
unfinished one qualifies as "unknown".
*/
if(buf[len-1] != 0){
char lasti = buf[len-1];
buf[len-1] = 0;
generic_sendmsg("E Unfinished command: %s%c", coms[cind], lasti);
--cnum;
}
for(cind = 0; cind < cnum; ++cind) {
comstr = coms[cind];
if(strlen(comstr) == 0) continue;
/* PAUSE */
if (!strcasecmp(comstr, "P") || !strcasecmp(comstr, "PAUSE")) {
if (!(mode == MODE_STOPPED))
{
if (mode == MODE_PLAYING) {
mode = MODE_PAUSED;
audio_flush(param.outmode, &ai);
#ifndef NOXFERMEM
if (param.usebuffer)
kill(buffer_pid, SIGSTOP);
#endif
generic_sendmsg("P 1");
} else {
mode = MODE_PLAYING;
#ifndef NOXFERMEM
if (param.usebuffer)
kill(buffer_pid, SIGCONT);
#endif
generic_sendmsg("P 2");
}
}
continue;
}
/* STOP */
if (!strcasecmp(comstr, "S") || !strcasecmp(comstr, "STOP")) {
if (mode != MODE_STOPPED) {
audio_flush(param.outmode, &ai);
rd->close(rd);
mode = MODE_STOPPED;
generic_sendmsg("P 0");
}
continue;
}
/* SILENCE */
if(!strcasecmp(comstr, "SILENCE")) {
silent = 1;
generic_sendmsg("silence");
continue;
}
/* QUIT */
if (!strcasecmp(comstr, "Q") || !strcasecmp(comstr, "QUIT")){
alive = FALSE; continue;
}
/* some HELP */
if (!strcasecmp(comstr, "H") || !strcasecmp(comstr, "HELP")) {
generic_sendmsg("HELP/H: command listing (LONG/SHORT forms), command case insensitve");
generic_sendmsg("LOAD/L <trackname>: load and start playing resource <trackname>");
generic_sendmsg("LOADPAUSED/LP <trackname>: load and start playing resource <trackname>");
generic_sendmsg("PAUSE/P: pause playback");
generic_sendmsg("STOP/S: stop playback (closes file)");
generic_sendmsg("JUMP/J <frame>|<+offset>|<-offset>|<[+|-]seconds>s: jump to mpeg frame <frame> or change position by offset, same in seconds if number followed by \"s\"");
generic_sendmsg("VOLUME/V <percent>: set volume in % (0..100...); float value");
generic_sendmsg("RVA off|(mix|radio)|(album|audiophile): set rva mode");
generic_sendmsg("EQ/E <channel> <band> <value>: set equalizer value for frequency band on channel");
generic_sendmsg("SEQ <bass> <mid> <treble>: simple eq setting...");
generic_sendmsg("SILENCE: be silent during playback (meaning silence in text form)");
generic_sendmsg("meaning of the @S stream info:");
generic_sendmsg(remote_header_help);
continue;
}
/* commands with arguments */
cmd = NULL;
arg = NULL;
cmd = strtok(comstr," \t"); /* get the main command */
arg = strtok(NULL,""); /* get the args */
if (cmd && strlen(cmd) && arg && strlen(arg))
{
/* Simple EQ: SEQ <BASS> <MID> <TREBLE> */
if (!strcasecmp(cmd, "SEQ")) {
real b,m,t;
int cn;
have_eq_settings = TRUE;
if(sscanf(arg, REAL_SCANF" "REAL_SCANF" "REAL_SCANF, &b, &m, &t) == 3)
{
/* very raw line */
if ((t >= 0) && (t <= 3))
for(cn=0; cn < 1; ++cn)
{
equalizer[0][cn] = b;
equalizer[1][cn] = b;
}
if ((m >= 0) && (m <= 3))
for(cn=1; cn < 2; ++cn)
{
equalizer[0][cn] = m;
equalizer[1][cn] = m;
}
if ((b >= 0) && (b <= 3))
for(cn=2; cn < 32; ++cn)
{
equalizer[0][cn] = t;
equalizer[1][cn] = t;
}
generic_sendmsg("bass: %f mid: %f treble: %f", b, m, t);
}
else generic_sendmsg("E invalid arguments for SEQ: %s", arg);
continue;
}
/* Equalizer control :) (JMG) */
if (!strcasecmp(cmd, "E") || !strcasecmp(cmd, "EQ")) {
real e; /* ThOr: equalizer is of type real... whatever that is */
int c, v;
have_eq_settings = TRUE;
/*generic_sendmsg("%s",updown);*/
if(sscanf(arg, "%i %i "REAL_SCANF, &c, &v, &e) == 3)
{
equalizer[c][v] = e;
generic_sendmsg("%i : %i : "REAL_PRINTF, c, v, e);
}
else generic_sendmsg("E invalid arguments for EQ: %s", arg);
continue;
}
/* JUMP */
if (!strcasecmp(cmd, "J") || !strcasecmp(cmd, "JUMP")) {
char *spos;
long offset;
double secs;
audio_flush(param.outmode, &ai);
spos = arg;
if (!spos)
continue;
if (mode == MODE_STOPPED)
continue;
if(spos[strlen(spos)-1] == 's' && sscanf(arg, "%lf", &secs) == 1) offset = time_to_frame(fr, secs);
else offset = atol(spos);
/* totally replaced that stuff - it never fully worked
a bit usure about why +pos -> spos+1 earlier... */
if (spos[0] == '-' || spos[0] == '+')
offset += frame_before;
else
offset -= fr->num;
/* ah, this offset stuff is twisted - I want absolute numbers */
#ifdef GAPLESS
if(param.gapless && (fr->lay == 3) && (mode == MODE_PAUSED))
{
if(fr->num+offset > 0)
{
--offset;
frame_before = 1;
if(fr->num+offset > 0)
{
--offset;
++frame_before;
}
}
else frame_before = 0;
}
#endif
if(rd->back_frame(rd, fr, -offset))
{
generic_sendmsg("E Error while seeking");
rd->rewind(rd);
fr->num = 0;
}
#ifdef GAPLESS
if(param.gapless && (fr->lay == 3))
{
prepare_audioinfo(fr, &pre_ai);
layer3_gapless_set_position(fr->num, fr, &pre_ai);
layer3_gapless_set_ignore(frame_before, fr, &pre_ai);
}
#endif
generic_sendmsg("J %d", fr->num+frame_before);
continue;
}
/* VOLUME in percent */
if(!strcasecmp(cmd, "V") || !strcasecmp(cmd, "VOLUME"))
{
do_volume(atof(arg)/100);
generic_sendmsg("V %f%%", outscale / (double) MAXOUTBURST * 100);
continue;
}
/* RVA mode */
if(!strcasecmp(cmd, "RVA"))
{
if(!strcasecmp(arg, "off")) param.rva = RVA_OFF;
else if(!strcasecmp(arg, "mix") || !strcasecmp(arg, "radio")) param.rva = RVA_MIX;
else if(!strcasecmp(arg, "album") || !strcasecmp(arg, "audiophile")) param.rva = RVA_ALBUM;
do_rva();
generic_sendmsg("RVA %s", rva_name[param.rva]);
continue;
}
/* LOAD - actually play */
if (!strcasecmp(cmd, "L") || !strcasecmp(cmd, "LOAD")) {
#ifdef GAPLESS
frame_before = 0;
#endif
if (mode != MODE_STOPPED) {
rd->close(rd);
mode = MODE_STOPPED;
}
if( open_stream(arg, -1) < 0 ){
generic_sendmsg("E Error opening stream: %s", arg);
generic_sendmsg("P 0");
continue;
}
if (rd && rd->flags & READER_ID3TAG)
generic_sendinfoid3((char *)rd->id3buf);
else
generic_sendinfo(arg);
if (icy.name.fill) generic_sendmsg("I ICY-NAME: %s", icy.name.p);
if (icy.url.fill) generic_sendmsg("I ICY-URL: %s", icy.url.p);
mode = MODE_PLAYING;
init = 1;
read_frame_init(fr);
generic_sendmsg("P 2");
continue;
}
/* LOADPAUSED */
if (!strcasecmp(cmd, "LP") || !strcasecmp(cmd, "LOADPAUSED")) {
#ifdef GAPLESS
frame_before = 0;
#endif
if (mode != MODE_STOPPED) {
rd->close(rd);
mode = MODE_STOPPED;
}
if( open_stream(arg, -1) < 0 ){
generic_sendmsg("E Error opening stream: %s", arg);
generic_sendmsg("P 0");
continue;
}
if (rd && rd->flags & READER_ID3TAG)
generic_sendinfoid3((char *)rd->id3buf);
else
generic_sendinfo(arg);
mode = MODE_PAUSED;
init = 1;
read_frame_init(fr);
generic_sendmsg("P 1");
continue;
}
/* no command matched */
generic_sendmsg("E Unknown command: %s", cmd); /* unknown command */
} /* end commands with arguments */
else generic_sendmsg("E Unknown command or no arguments: %s", comstr); /* unknown command */
} /*end command processing loop */
free(coms); /* release memory of command string (pointer) array */
} /* end command reading & processing */
} /* end main (alive) loop */
/* quit gracefully */
#ifndef NOXFERMEM
if (param.usebuffer) {
kill(buffer_pid, SIGINT);
xfermem_done_writer(buffermem);
waitpid(buffer_pid, NULL, 0);
xfermem_done(buffermem);
} else {
#endif
audio_flush(param.outmode, &ai);
free(pcm_sample);
#ifndef NOXFERMEM
}
#endif
if (param.outmode == DECODE_AUDIO)
audio_close(&ai);
if (param.outmode == DECODE_WAV)
wav_close();
return 0;
}
/* EOF */