/*
* madplay - MPEG audio decoder and player
* Copyright (C) 2000-2004 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: player.c,v 1.69 2004/02/23 21:34:53 rob Exp $
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include "global.h"
# include <stdio.h>
# include <stdarg.h>
# include <stdlib.h>
# ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
# endif
# include <sys/stat.h>
# ifdef HAVE_FCNTL_H
# include <fcntl.h>
# endif
# ifdef HAVE_UNISTD_H
# include <unistd.h>
# endif
# include <string.h>
# ifdef HAVE_ERRNO_H
# include <errno.h>
# endif
# include <time.h>
# include <locale.h>
# include <math.h>
# ifdef HAVE_TERMIOS_H
# include <termios.h>
# endif
# ifdef _WIN32
# include <windows.h>
# endif
# include <signal.h>
# ifdef HAVE_ASSERT_H
# include <assert.h>
# endif
# if defined(HAVE_MMAP)
# include <sys/mman.h>
# endif
# if !defined(O_BINARY)
# define O_BINARY 0
# endif
# include <mad.h>
# include <id3tag.h>
# include "gettext.h"
# include "player.h"
# include "audio.h"
# include "resample.h"
# include "filter.h"
# include "tag.h"
# include "rgain.h"
# define MPEG_BUFSZ 40000 /* 2.5 s at 128 kbps; 1 s at 320 kbps */
# define FREQ_TOLERANCE 2 /* percent sampling frequency tolerance */
# define TTY_DEVICE "/dev/tty"
# define KEY_CTRL(key) ((key) & 0x1f)
enum {
KEY_PAUSE = 'p',
KEY_STOP = 's',
KEY_FORWARD = 'f',
KEY_BACK = 'b',
KEY_TIME = 't',
KEY_QUIT = 'q',
KEY_INFO = 'i',
KEY_GAINDECR = '-',
KEY_GAININCR = '+',
KEY_GAINZERO = '_',
KEY_GAININFO = '='
};
static int on_same_line;
# if defined(USE_TTY) && !defined(_WIN32)
static int tty_fd = -1;
static struct termios save_tty;
static struct sigaction save_sigtstp, save_sigint;
# endif
/*
* NAME: player_init()
* DESCRIPTION: initialize player structure
*/
void player_init(struct player *player)
{
player->verbosity = 0;
player->options = 0;
player->repeat = 1;
player->control = PLAYER_CONTROL_DEFAULT;
player->playlist.entries = 0;
player->playlist.length = 0;
player->playlist.current = 0;
player->global_start = mad_timer_zero;
player->global_stop = mad_timer_zero;
player->fade_in = mad_timer_zero;
player->fade_out = mad_timer_zero;
player->gap = mad_timer_zero;
player->input.path = 0;
player->input.fd = -1;
# if defined(HAVE_MMAP)
player->input.fdm = 0;
# endif
player->input.data = 0;
player->input.length = 0;
player->input.eof = 0;
tag_init(&player->input.tag);
player->output.mode = AUDIO_MODE_DITHER;
player->output.voladj_db = 0;
player->output.attamp_db = 0;
player->output.gain = MAD_F_ONE;
player->output.replay_gain = 0;
player->output.filters = 0;
player->output.channels_in = 0;
player->output.channels_out = 0;
player->output.select = PLAYER_CHANNEL_DEFAULT;
player->output.speed_in = 0;
player->output.speed_out = 0;
player->output.speed_request = 0;
player->output.precision_in = 0;
player->output.precision_out = 0;
player->output.path = 0;
player->output.command = 0;
/* player->output.resample */
player->output.resampled = 0;
player->ancillary.path = 0;
player->ancillary.file = 0;
player->ancillary.buffer = 0;
player->ancillary.length = 0;
player->stats.show = STATS_SHOW_OVERALL;
player->stats.label = 0;
player->stats.total_bytes = 0;
player->stats.total_time = mad_timer_zero;
player->stats.global_timer = mad_timer_zero;
player->stats.absolute_timer = mad_timer_zero;
player->stats.play_timer = mad_timer_zero;
player->stats.global_framecount = 0;
player->stats.absolute_framecount = 0;
player->stats.play_framecount = 0;
player->stats.error_frame = -1;
player->stats.mute_frame = 0;
player->stats.vbr = 0;
player->stats.bitrate = 0;
player->stats.vbr_frames = 0;
player->stats.vbr_rate = 0;
player->stats.nsecs = 0;
player->stats.audio.clipped_samples = 0;
player->stats.audio.peak_clipping = 0;
player->stats.audio.peak_sample = 0;
}
/*
* NAME: player_finish()
* DESCRIPTION: destroy a player structure
*/
void player_finish(struct player *player)
{
if (player->output.filters)
filter_free(player->output.filters);
if (player->output.resampled) {
resample_finish(&player->output.resample[0]);
resample_finish(&player->output.resample[1]);
free(player->output.resampled);
player->output.resampled = 0;
}
}
/*
* NAME: message()
* DESCRIPTION: show a message, possibly overwriting a previous w/o newline
*/
static
int message(char const *format, ...)
{
int len, newline, result;
va_list args;
len = strlen(format);
newline = (len > 0 && format[len - 1] == '\n');
if (on_same_line && newline && len > 1)
fputc('\n', stderr);
va_start(args, format);
result = vfprintf(stderr, format, args);
va_end(args);
if (on_same_line && !newline && result < on_same_line) {
unsigned int i;
i = on_same_line - result;
while (i--)
putc(' ', stderr);
}
on_same_line = newline ? 0 : result;
if (!newline) {
fputc('\r', stderr);
fflush(stderr);
}
return result;
}
/*
* NAME: detail()
* DESCRIPTION: show right-aligned label and line-wrap corresponding text
*/
static
void detail(char const *label, char const *format, ...)
{
char const spaces[] = " ";
va_list args;
# define LINEWRAP (80 - sizeof(spaces) - 2 - 2)
if (on_same_line)
message("\n");
if (label) {
unsigned int len;
len = strlen(label);
assert(len < sizeof(spaces));
fprintf(stderr, "%s%s: ", &spaces[len], label);
}
else
fprintf(stderr, "%s ", spaces);
va_start(args, format);
if (format) {
vfprintf(stderr, format, args);
fputc('\n', stderr);
}
else {
char *ptr, *newline, *linebreak;
/* N.B. this argument must be mutable! */
ptr = va_arg(args, char *);
do {
newline = strchr(ptr, '\n');
if (newline)
*newline = 0;
if (strlen(ptr) > LINEWRAP) {
linebreak = ptr + LINEWRAP;
while (linebreak > ptr && *linebreak != ' ')
--linebreak;
if (*linebreak == ' ') {
if (newline)
*newline = '\n';
*(newline = linebreak) = 0;
}
}
fprintf(stderr, "%s\n", ptr);
if (newline) {
ptr = newline + 1;
fprintf(stderr, "%s ", spaces);
}
}
while (newline);
}
va_end(args);
}
/*
* NAME: error()
* DESCRIPTION: show an error using proper interaction with message()
*/
static
void error(char const *id, char const *format, ...)
{
int err;
va_list args;
err = errno;
if (on_same_line)
message("\n");
if (id)
fprintf(stderr, "%s: ", id);
va_start(args, format);
if (*format == ':') {
if (format[1] == 0) {
format = va_arg(args, char const *);
errno = err;
perror(format);
}
else {
errno = err;
perror(format + 1);
}
}
else {
vfprintf(stderr, format, args);
fputc('\n', stderr);
}
va_end(args);
}
# if defined(HAVE_MMAP)
/*
* NAME: map_file()
* DESCRIPTION: