summaryrefslogtreecommitdiff
path: root/player.c
diff options
context:
space:
mode:
authorKyle McMartin <kyle@debian.org>2004-06-06 01:23:28 +0200
committerKyle McMartin <kyle@debian.org>2004-06-06 01:23:28 +0200
commit194c0bbde98196ce6871bd21a9e3d52a771f8dfd (patch)
treef77af65ebcb4c1fa9d33dce45d206407a98c646d /player.c
Import madplay_0.15.2b.orig.tar.gz
[dgit import orig madplay_0.15.2b.orig.tar.gz]
Diffstat (limited to 'player.c')
-rw-r--r--player.c2818
1 files changed, 2818 insertions, 0 deletions
diff --git a/player.c b/player.c
new file mode 100644
index 0000000..370646a
--- /dev/null
+++ b/player.c
@@ -0,0 +1,2818 @@
+/*
+ * 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: map the contents of a file into memory
+ */
+static
+void *map_file(int fd, unsigned long length)
+{
+ void *fdm;
+
+ fdm = mmap(0, length, PROT_READ, MAP_SHARED, fd, 0);
+ if (fdm == MAP_FAILED)
+ return 0;
+
+# if defined(HAVE_MADVISE)
+ madvise(fdm, length, MADV_SEQUENTIAL);
+# endif
+
+ return fdm;
+}
+
+/*
+ * NAME: unmap_file()
+ * DESCRIPTION: undo a file mapping
+ */
+static
+int unmap_file(void *fdm, unsigned long length)
+{
+ if (munmap(fdm, length) == -1)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * NAME: decode->input_mmap()
+ * DESCRIPTION: (re)fill decoder input buffer from a memory map
+ */
+static
+enum mad_flow decode_input_mmap(void *data, struct mad_stream *stream)
+{
+ struct player *player = data;
+ struct input *input = &player->input;
+
+ if (input->eof)
+ return MAD_FLOW_STOP;
+
+ if (stream->next_frame) {
+ struct stat stat;
+ unsigned long posn, left;
+
+ if (fstat(input->fd, &stat) == -1)
+ return MAD_FLOW_BREAK;
+
+ posn = stream->next_frame - input->fdm;
+
+ /* check for file size change and update map */
+
+ if (stat.st_size > input->length) {
+ if (unmap_file(input->fdm, input->length) == -1) {
+ input->fdm = 0;
+ input->data = 0;
+ return MAD_FLOW_BREAK;
+ }
+
+ player->stats.total_bytes += stat.st_size - input->length;
+
+ input->length = stat.st_size;
+
+ input->fdm = map_file(input->fd, input->length);
+ if (input->fdm == 0) {
+ input->data = 0;
+ return MAD_FLOW_BREAK;
+ }
+
+ mad_stream_buffer(stream, input->fdm + posn, input->length - posn);
+
+ return MAD_FLOW_CONTINUE;
+ }
+
+ /* end of memory map; append MAD_BUFFER_GUARD zero bytes */
+
+ left = input->length - posn;
+
+ input->data = malloc(left + MAD_BUFFER_GUARD);
+ if (input->data == 0)
+ return MAD_FLOW_BREAK;
+
+ input->eof = 1;
+
+ memcpy(input->data, input->fdm + posn, left);
+ memset(input->data + left, 0, MAD_BUFFER_GUARD);
+
+ mad_stream_buffer(stream, input->data, left + MAD_BUFFER_GUARD);
+
+ return MAD_FLOW_CONTINUE;
+ }
+
+ /* first call */
+
+ mad_stream_buffer(stream, input->fdm, input->length);
+
+ return MAD_FLOW_CONTINUE;
+}
+# endif
+
+/*
+ * NAME: decode->input_read()
+ * DESCRIPTION: (re)fill decoder input buffer by reading a file descriptor
+ */
+static
+enum mad_flow decode_input_read(void *data, struct mad_stream *stream)
+{
+ struct player *player = data;
+ struct input *input = &player->input;
+ int len;
+
+ if (input->eof)
+ return MAD_FLOW_STOP;
+
+ if (stream->next_frame) {
+ memmove(input->data, stream->next_frame,
+ input->length = &input->data[input->length] - stream->next_frame);
+ }
+
+ do {
+ len = read(input->fd, input->data + input->length,
+ MPEG_BUFSZ - input->length);
+ }
+ while (len == -1 && errno == EINTR);
+
+ if (len == -1) {
+ error("input", ":read");
+ return MAD_FLOW_BREAK;
+ }
+ else if (len == 0) {
+ input->eof = 1;
+
+ assert(MPEG_BUFSZ - input->length >= MAD_BUFFER_GUARD);
+
+ while (len < MAD_BUFFER_GUARD)
+ input->data[input->length + len++] = 0;
+ }
+
+ mad_stream_buffer(stream, input->data, input->length += len);
+
+ return MAD_FLOW_CONTINUE;
+}
+
+/*
+ * NAME: decode->header()
+ * DESCRIPTION: decide whether to continue decoding based on header
+ */
+static
+enum mad_flow decode_header(void *data, struct mad_header const *header)
+{
+ struct player *player = data;
+
+ if ((player->options & PLAYER_OPTION_TIMED) &&
+ mad_timer_compare(player->stats.global_timer, player->global_stop) > 0)
+ return MAD_FLOW_STOP;
+
+ /* accounting (except first frame) */
+
+ if (player->stats.absolute_framecount) {
+ ++player->stats.absolute_framecount;
+ mad_timer_add(&player->stats.absolute_timer, header->duration);
+
+ ++player->stats.global_framecount;
+ mad_timer_add(&player->stats.global_timer, header->duration);
+
+ if ((player->options & PLAYER_OPTION_SKIP) &&
+ mad_timer_compare(player->stats.global_timer,
+ player->global_start) < 0)
+ return MAD_FLOW_IGNORE;
+ }
+
+ return MAD_FLOW_CONTINUE;
+}
+
+/*
+ * NAME: write_ancillary()
+ * DESCRIPTION: pack ancillary data into octets and output
+ */
+static
+int write_ancillary(struct ancillary *ancillary,
+ struct mad_bitptr ptr, unsigned int length)
+{
+ if (ancillary->length) {
+ unsigned int balance;
+
+ balance = 8 - ancillary->length;
+ if (balance > length) {
+ ancillary->buffer =
+ (ancillary->buffer << length) | mad_bit_read(&ptr, length);
+ ancillary->length += length;
+
+ return 0;
+ }
+ else {
+ if (fputc((ancillary->buffer << balance) | mad_bit_read(&ptr, balance),
+ ancillary->file) == EOF) {
+ error("ancillary", ":fputc");
+ return -1;
+ }
+ ancillary->length = 0;
+
+ length -= balance;
+ }
+ }
+
+ while (length >= 8) {
+ int byte;
+
+ byte = mad_bit_read(&ptr, 8);
+ if (putc(byte, ancillary->file) == EOF) {
+ error("ancillary", ":putc");
+ return -1;
+ }
+
+ length -= 8;
+ }
+
+ if (length) {
+ ancillary->buffer = mad_bit_read(&ptr, length);
+ ancillary->length = length;
+ }
+
+ if (fflush(ancillary->file) == EOF) {
+ error("ancillary", ":fflush");
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * NAME: show_id3()
+ * DESCRIPTION: display ID3 tag information
+ */
+static
+void show_id3(struct id3_tag const *tag)
+{
+ unsigned int i;
+ struct id3_frame const *frame;
+ id3_ucs4_t const *ucs4;
+ id3_latin1_t *latin1;
+
+ static struct {
+ char const *id;
+ char const *label;
+ } const info[] = {
+ { ID3_FRAME_TITLE, N_("Title") },
+ { "TIT3", 0 }, /* Subtitle */
+ { "TCOP", 0 }, /* Copyright */
+ { "TPRO", 0 }, /* Produced */
+ { "TCOM", N_("Composer") },
+ { ID3_FRAME_ARTIST, N_("Artist") },
+ { "TPE2", N_("Orchestra") },
+ { "TPE3", N_("Conductor") },
+ { "TEXT", N_("Lyricist") },
+ { ID3_FRAME_ALBUM, N_("Album") },
+ { ID3_FRAME_TRACK, N_("Track") },
+ { ID3_FRAME_YEAR, N_("Year") },
+ { "TPUB", N_("Publisher") },
+ { ID3_FRAME_GENRE, N_("Genre") },
+ { "TRSN", N_("Station") },
+ { "TENC", N_("Encoder") }
+ };
+
+ /* text information */
+
+ for (i = 0; i < sizeof(info) / sizeof(info[0]); ++i) {
+ union id3_field const *field;
+ unsigned int nstrings, j;
+
+ frame = id3_tag_findframe(tag, info[i].id, 0);
+ if (frame == 0)
+ continue;
+
+ field = id3_frame_field(frame, 1);
+ nstrings = id3_field_getnstrings(field);
+
+ for (j = 0; j < nstrings; ++j) {
+ ucs4 = id3_field_getstrings(field, j);
+ assert(ucs4);
+
+ if (strcmp(info[i].id, ID3_FRAME_GENRE) == 0)
+ ucs4 = id3_genre_name(ucs4);
+
+ latin1 = id3_ucs4_latin1duplicate(ucs4);
+ if (latin1 == 0)
+ goto fail;
+
+ if (j == 0 && info[i].label)
+ detail(gettext(info[i].label), 0, latin1);
+ else {
+ if (strcmp(info[i].id, "TCOP") == 0 ||
+ strcmp(info[i].id, "TPRO") == 0) {
+ detail(0, "%s %s", (info[i].id[1] == 'C') ?
+ _("Copyright (C)") : _("Produced (P)"), latin1);
+ }
+ else
+ detail(0, 0, latin1);
+ }
+
+ free(latin1);
+ }
+ }
+
+ /* comments */
+
+ i = 0;
+ while ((frame = id3_tag_findframe(tag, ID3_FRAME_COMMENT, i++))) {
+ ucs4 = id3_field_getstring(id3_frame_field(frame, 2));
+ assert(ucs4);
+
+ if (*ucs4)
+ continue;
+
+ ucs4 = id3_field_getfullstring(id3_frame_field(frame, 3));
+ assert(ucs4);
+
+ latin1 = id3_ucs4_latin1duplicate(ucs4);
+ if (latin1 == 0)
+ goto fail;
+
+ detail(_("Comment"), 0, latin1);
+
+ free(latin1);
+ break;
+ }
+
+ if (0) {
+ fail:
+ error("id3", _("not enough memory to display tag"));
+ }
+}
+
+/*
+ * NAME: show_rgain()
+ * DESCRIPTION: display Replay Gain information
+ */
+static
+void show_rgain(struct rgain const *rgain)
+{
+ char const *label, *source;
+
+ if (!RGAIN_VALID(rgain))
+ return;
+
+ label = 0;
+ switch (rgain->name) {
+ case RGAIN_NAME_NOT_SET:
+ break;
+ case RGAIN_NAME_RADIO:
+ label = _("Radio Gain");
+ break;
+ case RGAIN_NAME_AUDIOPHILE:
+ label = _("Audiophile Gain");
+ break;
+ }
+
+ source = rgain_originator(rgain);
+
+ assert(label && source);
+
+ detail(label, "%+.1f dB => %d dB SPL (%s)",
+ RGAIN_DB(rgain), (int) RGAIN_REFERENCE, source);
+}
+
+/*
+ * NAME: show_tag()
+ * DESCRIPTION: display Xing/LAME tag information
+ */
+static
+void show_tag(struct tag const *tag)
+{
+ char ident[22];
+ int i;
+
+ memcpy(ident, tag->encoder, 21);
+
+ /* separate version number from encoder name */
+
+ for (i = 0; i < 20; ++i) {
+ if (ident[i] == 0)
+ break;
+
+ if (ident[i] >= '0' && ident[i] <= '9') {
+ if (i > 0 && ident[i - 1] != ' ' && ident[i - 1] != 'v') {
+ memmove(&ident[i + 1], &ident[i], 21 - i);
+ ident[i] = ' ';
+ }
+
+ break;
+ }
+ }
+
+ if (ident[0])
+ detail(_("Encoder Version"), "%s", ident);
+
+ if (tag->flags & TAG_LAME) {
+ char const *text;
+
+# if 0
+ detail(_("Tag Revision"), "%u", tag->lame.revision);
+# endif
+
+ text = 0;
+ switch (tag->lame.vbr_method) {
+ case TAG_LAME_VBR_CONSTANT:
+ text = _("constant");
+ break;
+ case TAG_LAME_VBR_ABR:
+ text = _("ABR");
+ break;
+ case TAG_LAME_VBR_METHOD1:
+ text = _("1 (old/rh)");
+ break;
+ case TAG_LAME_VBR_METHOD2:
+ text = _("2 (mtrh)");
+ break;
+ case TAG_LAME_VBR_METHOD3:
+ text = _("3 (mt)");
+ break;
+ case TAG_LAME_VBR_METHOD4:
+ text = _("4");
+ break;
+ case TAG_LAME_VBR_CONSTANT2PASS:
+ text = _("constant (two-pass)");
+ break;
+ case TAG_LAME_VBR_ABR2PASS:
+ text = _("ABR (two-pass)");
+ break;
+ }
+ detail(_("VBR Method"), "%s", text ? text : _("unknown"));
+
+ text = 0;
+ switch (tag->lame.vbr_method) {
+ case TAG_LAME_VBR_CONSTANT:
+ case TAG_LAME_VBR_CONSTANT2PASS:
+ text = _("Bitrate");
+ break;
+ case TAG_LAME_VBR_ABR:
+ case TAG_LAME_VBR_ABR2PASS:
+ text = _("Target Bitrate");
+ break;
+ case TAG_LAME_VBR_METHOD1:
+ case TAG_LAME_VBR_METHOD2:
+ case TAG_LAME_VBR_METHOD3:
+ case TAG_LAME_VBR_METHOD4:
+ text = _("Minimum Bitrate");
+ break;
+ }
+ if (text) {
+ detail(text, _("%u%s kbps"), tag->lame.bitrate,
+ tag->lame.bitrate == 255 ? "+" : "");
+ }
+
+ text = 0;
+ switch (tag->lame.stereo_mode) {
+ case TAG_LAME_MODE_MONO:
+ text = _("mono");
+ break;
+ case TAG_LAME_MODE_STEREO:
+ text = _("normal");
+ break;
+ case TAG_LAME_MODE_DUAL:
+ text = _("dual channel");
+ break;
+ case TAG_LAME_MODE_JOINT:
+ text = _("joint");
+ break;
+ case TAG_LAME_MODE_FORCE:
+ text = _("force");
+ break;
+ case TAG_LAME_MODE_AUTO:
+ text = _("auto");
+ break;
+ case TAG_LAME_MODE_INTENSITY:
+ text = _("intensity");
+ break;
+ case TAG_LAME_MODE_UNDEFINED:
+ text = _("undefined");
+ break;
+ }
+ if (text)
+ detail(_("Stereo Mode"), "%s", text);
+
+ if (tag->lame.preset >= 8 && tag->lame.preset <= 320)
+ detail(_("Preset"), _("ABR %u"), tag->lame.preset);
+ else {
+ text = 0;
+ switch (tag->lame.preset) {
+ case TAG_LAME_PRESET_NONE:
+ text = _("none");
+ break;
+ case TAG_LAME_PRESET_V9:
+ text = _("V9");
+ break;
+ case TAG_LAME_PRESET_V8:
+ text = _("V8");
+ break;
+ case TAG_LAME_PRESET_V7:
+ text = _("V7");
+ break;
+ case TAG_LAME_PRESET_V6:
+ text = _("V6");
+ break;
+ case TAG_LAME_PRESET_V5:
+ text = _("V5");
+ break;
+ case TAG_LAME_PRESET_V4:
+ text = _("V4");
+ break;
+ case TAG_LAME_PRESET_V3:
+ text = _("V3");
+ break;
+ case TAG_LAME_PRESET_V2:
+ text = _("V2");
+ break;
+ case TAG_LAME_PRESET_V1:
+ text = _("V1");
+ break;
+ case TAG_LAME_PRESET_V0:
+ text = _("V0");
+ break;
+ case TAG_LAME_PRESET_R3MIX:
+ text = _("r3mix");
+ break;
+ case TAG_LAME_PRESET_STANDARD:
+ text = _("standard");
+ break;
+ case TAG_LAME_PRESET_EXTREME:
+ text = _("extreme");
+ break;
+ case TAG_LAME_PRESET_INSANE:
+ text = _("insane");
+ break;
+ case TAG_LAME_PRESET_STANDARD_FAST:
+ text = _("standard/fast");
+ break;
+ case TAG_LAME_PRESET_EXTREME_FAST:
+ text = _("extreme/fast");
+ break;
+ case TAG_LAME_PRESET_MEDIUM:
+ text = _("medium");
+ break;
+ case TAG_LAME_PRESET_MEDIUM_FAST:
+ text = _("medium/fast");
+ break;
+ }
+ detail(_("Preset"), "%s", text ? text : _("unknown"));
+ }
+
+ detail(_("Unwise Settings"), "%s",
+ (tag->lame.flags & TAG_LAME_UNWISE) ? _("yes") : _("no"));
+
+ detail(_("Encoding Flags"), "%s%s%s",
+ (tag->lame.flags & TAG_LAME_NSPSYTUNE) ? "--nspsytune " : "",
+ (tag->lame.flags & TAG_LAME_NSSAFEJOINT) ? "--nssafejoint " : "",
+ (tag->lame.flags & (TAG_LAME_NOGAP_NEXT | TAG_LAME_NOGAP_PREV)) ?
+ "--nogap" : "");
+
+ text = 0;
+ switch (tag->lame.flags & (TAG_LAME_NOGAP_NEXT | TAG_LAME_NOGAP_PREV)) {
+ case TAG_LAME_NOGAP_NEXT:
+ text = _("following");
+ break;
+ case TAG_LAME_NOGAP_PREV:
+ text = _("preceding");
+ break;
+ case TAG_LAME_NOGAP_NEXT | TAG_LAME_NOGAP_PREV:
+ text = _("following or preceding");
+ break;
+ }
+ if (text)
+ detail(_("No Gap"), "%s", text);
+
+ text = _("Lowpass Filter");
+ if (tag->lame.lowpass_filter == 0)
+ detail(text, "%s", _("unknown"));
+ else
+ detail(text, _("%u Hz"), tag->lame.lowpass_filter);
+
+ detail(_("ATH Type"), "%u", tag->lame.ath_type);
+
+ detail(_("Noise Shaping"), "%u", tag->lame.noise_shaping);
+
+ switch (tag->lame.surround) {
+ case TAG_LAME_SURROUND_NONE:
+ text = _("none");
+ break;
+ case TAG_LAME_SURROUND_DPL:
+ text = _("DPL");
+ break;
+ case TAG_LAME_SURROUND_DPL2:
+ text = _("DPL2");
+ break;
+ case TAG_LAME_SURROUND_AMBISONIC:
+ text = _("Ambisonic");
+ break;
+ default:
+ text = _("unknown");
+ }
+ detail(_("Surround"), "%s", text);
+
+ detail(_("Start Delay"), _("%u samples"), tag->lame.start_delay);
+ detail(_("End Padding"), _("%u samples"), tag->lame.end_padding);
+
+ text = 0;
+ switch (tag->lame.source_samplerate) {
+ case TAG_LAME_SOURCE_32LOWER:
+ text = _("32 kHz or lower");
+ break;
+ case TAG_LAME_SOURCE_44_1:
+ text = _("44.1 kHz");
+ break;
+ case TAG_LAME_SOURCE_48:
+ text = _("48 kHz");
+ break;
+ case TAG_LAME_SOURCE_HIGHER48:
+ text = _("higher than 48 kHz");
+ break;
+ }
+ if (text)
+ detail(_("Source Rate"), "%s", text);
+
+ if (tag->lame.gain != 0)
+ detail(_("Gain"), _("%+.1f dB"), tag->lame.gain * 1.5);
+
+ /* Replay Gain */
+
+ if (tag->lame.peak > 0) {
+ double peak = mad_f_todouble(tag->lame.peak);
+
+ detail(_("Peak Amplitude"), _("%.8f (%+.1f dB)"),
+ peak, 20 * log10(peak));
+ }
+
+ if (tag->lame.replay_gain[0].name == RGAIN_NAME_RADIO)
+ show_rgain(&tag->lame.replay_gain[0]);
+ if (tag->lame.replay_gain[1].name == RGAIN_NAME_AUDIOPHILE)
+ show_rgain(&tag->lame.replay_gain[1]);
+
+ detail(_("Music Length"), _("%lu bytes"), tag->lame.music_length);
+# if 0
+ detail(_("Music CRC"), "0x%04x", tag->lame.music_crc);
+# endif
+ }
+
+ if (tag->flags & TAG_XING) {
+ if (tag->xing.flags & TAG_XING_FRAMES)
+ detail(_("Audio Frames"), "%lu", tag->xing.frames);
+
+ if ((tag->xing.flags & TAG_XING_BYTES) &&
+ (!(tag->flags & TAG_LAME) ||
+ tag->lame.music_length != tag->xing.bytes))
+ detail(_("Data Bytes"), "%lu", tag->xing.bytes);
+
+ if ((tag->flags & TAG_VBR) && (tag->xing.flags & TAG_XING_SCALE))
+ detail(_("VBR Scale"), _("%ld/100"), 100 - tag->xing.scale);
+ }
+}
+
+enum {
+ GAIN_VOLADJ = 0x0001,
+ GAIN_ATTAMP = 0x0002,
+ GAIN_RELATIVE = 0x0010
+};
+
+/*
+ * NAME: set_gain()
+ * DESCRIPTION: modify player gain information
+ */
+static
+double set_gain(struct player *player, int how, double db)
+{
+ double *gain_db = 0;
+
+ if (how & GAIN_ATTAMP)
+ gain_db = &player->output.attamp_db;
+ else if (how & GAIN_VOLADJ)
+ gain_db = &player->output.voladj_db;
+
+ if (gain_db) {
+ if (how & GAIN_RELATIVE)
+ *gain_db += db;
+ else
+ *gain_db = db;
+ }
+
+ db = player->output.voladj_db + player->output.attamp_db;
+ if (db > DB_MAX || db < DB_MIN) {
+ db = (db > DB_MAX) ? DB_MAX : DB_MIN;
+ player->output.attamp_db = db - player->output.voladj_db;
+ }
+
+ player->output.gain = db ? mad_f_tofixed(pow(10, db / 20)) : MAD_F_ONE;
+
+ return db;
+}
+
+/*
+ * NAME: use_rgain()
+ * DESCRIPTION: select and employ a Replay Gain volume adjustment
+ */
+static
+void use_rgain(struct player *player, struct rgain *list)
+{
+ struct rgain *rgain = &list[0];
+
+ if ((player->output.replay_gain & PLAYER_RGAIN_AUDIOPHILE) &&
+ list[1].name == RGAIN_NAME_AUDIOPHILE &&
+ list[1].originator != RGAIN_ORIGINATOR_UNSPECIFIED)
+ rgain = &list[1];
+
+ if (RGAIN_VALID(rgain)) {
+ double gain = RGAIN_DB(rgain);
+
+ set_gain(player, GAIN_VOLADJ, gain);
+
+ if (player->verbosity >= 0 ||
+ (player->options & PLAYER_OPTION_SHOWTAGSONLY)) {
+ char const *source;
+
+ source = rgain_originator(rgain);
+ assert(source);
+
+ detail(_("Replay Gain"), _("%+.1f dB %s adjustment (%s)"),
+ gain, (rgain->name == RGAIN_NAME_RADIO) ?
+ _("radio") : _("audiophile"), source);
+ }
+
+ player->output.replay_gain |= PLAYER_RGAIN_SET;
+ }
+}
+
+/*
+ * NAME: decode->filter()
+ * DESCRIPTION: perform filtering on decoded frame
+ */
+static
+enum mad_flow decode_filter(void *data, struct mad_stream const *stream,
+ struct mad_frame *frame)
+{
+ struct player *player = data;
+
+ /* output ancillary data */
+
+ if (player->ancillary.file && stream->anc_bitlen &&
+ write_ancillary(&player->ancillary,
+ stream->anc_ptr, stream->anc_bitlen) == -1)
+ return MAD_FLOW_BREAK;
+
+ /* first frame accounting */
+
+ if (player->stats.absolute_framecount == 0) {
+ if (player->input.tag.flags == 0 &&
+ tag_parse(&player->input.tag, stream) == 0) {
+ struct tag *tag = &player->input.tag;
+ unsigned int frame_size;
+
+ if (player->options & PLAYER_OPTION_SHOWTAGSONLY) {
+ if (player->verbosity > 0)
+ show_tag(tag);
+ }
+ else {
+ if ((tag->flags & TAG_LAME) &&
+ (player->output.replay_gain & PLAYER_RGAIN_ENABLED) &&
+ !(player->output.replay_gain & PLAYER_RGAIN_SET))
+ use_rgain(player, tag->lame.replay_gain);
+ }
+
+ if ((tag->flags & TAG_XING) &&
+ (tag->xing.flags & TAG_XING_FRAMES)) {
+ player->stats.total_time = frame->header.duration;
+ mad_timer_multiply(&player->stats.total_time, tag->xing.frames);
+ }
+
+ /* total stream byte size adjustment */
+
+ frame_size = stream->next_frame - stream->this_frame;
+
+ if (player->stats.total_bytes == 0) {
+ if ((tag->flags & TAG_XING) && (tag->xing.flags & TAG_XING_BYTES) &&
+ tag->xing.bytes > frame_size)
+ player->stats.total_bytes = tag->xing.bytes - frame_size;
+ }
+ else if (player->stats.total_bytes >=
+ stream->next_frame - stream->this_frame)
+ player->stats.total_bytes -= frame_size;
+
+ return (player->options & PLAYER_OPTION_SHOWTAGSONLY) ?
+ MAD_FLOW_STOP : MAD_FLOW_IGNORE;
+ }
+ else if (player->options & PLAYER_OPTION_SHOWTAGSONLY)
+ return MAD_FLOW_STOP;
+
+ ++player->stats.absolute_framecount;
+ mad_timer_add(&player->stats.absolute_timer, frame->header.duration);
+
+ ++player->stats.global_framecount;
+ mad_timer_add(&player->stats.global_timer, frame->header.duration);
+
+ if ((player->options & PLAYER_OPTION_SKIP) &&
+ mad_timer_compare(player->stats.global_timer,
+ player->global_start) < 0)
+ return MAD_FLOW_IGNORE;
+ }
+
+ /* run the filter chain */
+
+ return filter_run(player->output.filters, frame);
+}
+
+/*
+ * NAME: process_id3()
+ * DESCRIPTION: display and process ID3 tag information
+ */
+static
+void process_id3(struct id3_tag const *tag, struct player *player)
+{
+ struct id3_frame const *frame;
+
+ /* display the tag */
+
+ if (player->verbosity >= 0 || (player->options & PLAYER_OPTION_SHOWTAGSONLY))
+ show_id3(tag);
+
+ /*
+ * The following is based on information from the
+ * ID3 tag version 2.4.0 Native Frames informal standard.
+ */
+
+ /* length information */
+
+ frame = id3_tag_findframe(tag, "TLEN", 0);
+ if (frame) {
+ union id3_field const *field;
+ unsigned int nstrings;
+
+ field = id3_frame_field(frame, 1);
+ nstrings = id3_field_getnstrings(field);
+
+ if (nstrings > 0) {
+ id3_latin1_t *latin1;
+
+ latin1 = id3_ucs4_latin1duplicate(id3_field_getstrings(field, 0));
+ if (latin1) {
+ signed long ms;
+
+ /*
+ * "The 'Length' frame contains the length of the audio file
+ * in milliseconds, represented as a numeric string."
+ */
+
+ ms = atol(latin1);
+ if (ms > 0)
+ mad_timer_set(&player->stats.total_time, 0, ms, 1000);
+
+ free(latin1);
+ }
+ }
+ }
+
+ /* relative volume adjustment information */
+
+ if ((player->options & PLAYER_OPTION_SHOWTAGSONLY) ||
+ !(player->options & PLAYER_OPTION_IGNOREVOLADJ)) {
+ frame = id3_tag_findframe(tag, "RVA2", 0);
+ if (frame) {
+ id3_latin1_t const *id;
+ id3_byte_t const *data;
+ id3_length_t length;
+
+ enum {
+ CHANNEL_OTHER = 0x00,
+ CHANNEL_MASTER_VOLUME = 0x01,
+ CHANNEL_FRONT_RIGHT = 0x02,
+ CHANNEL_FRONT_LEFT = 0x03,
+ CHANNEL_BACK_RIGHT = 0x04,
+ CHANNEL_BACK_LEFT = 0x05,
+ CHANNEL_FRONT_CENTRE = 0x06,
+ CHANNEL_BACK_CENTRE = 0x07,
+ CHANNEL_SUBWOOFER = 0x08
+ };
+
+ id = id3_field_getlatin1(id3_frame_field(frame, 0));
+ data = id3_field_getbinarydata(id3_frame_field(frame, 1), &length);
+
+ assert(id && data);
+
+ /*
+ * "The 'identification' string is used to identify the situation
+ * and/or device where this adjustment should apply. The following is
+ * then repeated for every channel
+ *
+ * Type of channel $xx
+ * Volume adjustment $xx xx
+ * Bits representing peak $xx
+ * Peak volume $xx (xx ...)"
+ */
+
+ while (length >= 4) {
+ unsigned int peak_bytes;
+
+ peak_bytes = (data[3] + 7) / 8;
+ if (4 + peak_bytes > length)
+ break;
+
+ if (data[0] == CHANNEL_MASTER_VOLUME) {
+ signed int voladj_fixed;
+ double voladj_float;
+
+ /*
+ * "The volume adjustment is encoded as a fixed point decibel
+ * value, 16 bit signed integer representing (adjustment*512),
+ * giving +/- 64 dB with a precision of 0.001953125 dB."
+ */
+
+ voladj_fixed = (data[1] << 8) | (data[2] << 0);
+ voladj_fixed |= -(voladj_fixed & 0x8000);
+
+ voladj_float = (double) voladj_fixed / 512;
+
+ set_gain(player, GAIN_VOLADJ, voladj_float);
+
+ if (player->verbosity >= 0) {
+ detail(_("Relative Volume"),
+ _("%+.1f dB adjustment (%s)"), voladj_float, id);
+ }
+
+ break;
+ }
+
+ data += 4 + peak_bytes;
+ length -= 4 + peak_bytes;
+ }
+ }
+ }
+
+ /* Replay Gain */
+
+ if ((player->options & PLAYER_OPTION_SHOWTAGSONLY) ||
+ ((player->output.replay_gain & PLAYER_RGAIN_ENABLED) &&
+ !(player->output.replay_gain & PLAYER_RGAIN_SET))) {
+ frame = id3_tag_findframe(tag, "RGAD", 0);
+ if (frame) {
+ id3_byte_t const *data;
+ id3_length_t length;
+
+ data = id3_field_getbinarydata(id3_frame_field(frame, 0), &length);
+ assert(data);
+
+ /*
+ * Peak Amplitude $xx $xx $xx $xx
+ * Radio Replay Gain Adjustment $xx $xx
+ * Audiophile Replay Gain Adjustment $xx $xx
+ */
+
+ if (length >= 8) {
+ struct mad_bitptr ptr;
+ mad_fixed_t peak;
+ struct rgain rgain[2];
+
+ mad_bit_init(&ptr, data);
+
+ peak = mad_bit_read(&ptr, 32) << 5;
+
+ rgain_parse(&rgain[0], &ptr);
+ rgain_parse(&rgain[1], &ptr);
+
+ use_rgain(player, rgain);
+
+ mad_bit_finish(&ptr);
+ }
+ }
+ }
+}
+
+/*
+ * NAME: show_status()
+ * DESCRIPTION: generate and output stream statistics
+ */
+static
+void show_status(struct stats *stats,
+ struct mad_header const *header, char const *label, int now)
+{
+ signed long seconds;
+ static char const *const layer_str[3] = { N_("I"), N_("II"), N_("III") };
+ static char const *const mode_str[4] = {
+ N_("single channel"), N_("dual channel"), N_("joint stereo"), N_("stereo")
+ };
+
+ if (header) {
+ unsigned int bitrate;
+
+ bitrate = header->bitrate / 1000;
+
+ stats->vbr_rate += bitrate;
+ stats->vbr_frames++;
+
+ stats->vbr += (stats->bitrate && stats->bitrate != bitrate) ? 128 : -1;
+ if (stats->vbr < 0)
+ stats->vbr = 0;
+ else if (stats->vbr > 512)
+ stats->vbr = 512;
+
+ stats->bitrate = bitrate;
+ }
+
+ seconds = mad_timer_count(stats->global_timer, MAD_UNITS_SECONDS);
+ if (seconds != stats->nsecs || !on_same_line || now) {
+ mad_timer_t timer;
+ char time_str[18];
+ char const *joint_str = "";
+
+ stats->nsecs = seconds;
+
+ switch (stats->show) {
+ case STATS_SHOW_OVERALL:
+ timer = stats->global_timer;
+ break;
+
+ case STATS_SHOW_CURRENT:
+ timer = stats->absolute_timer;
+ break;
+
+ case STATS_SHOW_REMAINING:
+ timer = stats->total_time;
+
+ if (mad_timer_sign(timer) == 0 && stats->total_bytes) {
+ unsigned long rate;
+
+ /* estimate based on size and bitrate */
+
+ rate = stats->vbr ?
+ stats->vbr_rate * 125 / stats->vbr_frames : stats->bitrate * 125UL;
+
+ mad_timer_set(&timer, 0, stats->total_bytes, rate);
+ }
+
+ mad_timer_negate(&timer);
+ mad_timer_add(&timer, stats->absolute_timer);
+ break;
+ }
+
+ mad_timer_string(timer, time_str, " %02lu:%02u:%02u",
+ MAD_UNITS_HOURS, 0, 0);
+ if (mad_timer_sign(timer) < 0)
+ time_str[0] = '-';
+
+ if (label || stats->label) {
+ message("%s %s", time_str, label ? label : stats->label);
+ stats->label = now ? label : 0;
+ }
+ else if (header) {
+ if (header->mode == MAD_MODE_JOINT_STEREO) {
+ switch (header->flags & (MAD_FLAG_MS_STEREO | MAD_FLAG_I_STEREO)) {
+ case 0:
+ joint_str = _(" (LR)");
+ break;
+
+ case MAD_FLAG_MS_STEREO:
+ joint_str = _(" (MS)");
+ break;
+
+ case MAD_FLAG_I_STEREO:
+ joint_str = _(" (I)");
+ break;
+
+ case (MAD_FLAG_MS_STEREO | MAD_FLAG_I_STEREO):
+ joint_str = _(" (MS+I)");
+ break;
+ }
+ }
+
+ message(_("%s Layer %s, %s%u kbps%s, %u Hz, %s%s, %s"),
+ time_str, gettext(layer_str[header->layer - 1]),
+ stats->vbr ? _("VBR (avg ") : "",
+ stats->vbr ? ((stats->vbr_rate * 2) /
+ stats->vbr_frames + 1) / 2 : stats->bitrate,
+ stats->vbr ? _(")") : "",
+ header->samplerate, gettext(mode_str[header->mode]), joint_str,
+ (header->flags & MAD_FLAG_PROTECTION) ? _("CRC") : _("no CRC"));
+ }
+ else
+ message("%s", time_str);
+ }
+}
+
+/*
+ * NAME: decode->output()
+ * DESCRIPTION: configure audio module and output decoded samples
+ */
+static
+enum mad_flow decode_output(void *data, struct mad_header const *header,
+ struct mad_pcm *pcm)
+{
+ struct player *player = data;
+ struct output *output = &player->output;
+ mad_fixed_t const *ch1, *ch2;
+ unsigned int nchannels;
+ union audio_control control;
+
+ ch1 = pcm->samples[0];
+ ch2 = pcm->samples[1];
+
+ switch (nchannels = pcm->channels) {
+ case 1:
+ ch2 = 0;
+ if (output->select == PLAYER_CHANNEL_STEREO) {
+ ch2 = ch1;
+ nchannels = 2;
+ }
+ break;
+
+ case 2:
+ switch (output->select) {
+ case PLAYER_CHANNEL_RIGHT:
+ ch1 = ch2;
+ /* fall through */
+
+ case PLAYER_CHANNEL_LEFT:
+ ch2 = 0;
+ nchannels = 1;
+ /* fall through */
+
+ case PLAYER_CHANNEL_STEREO:
+ break;
+
+ default:
+ if (header->mode == MAD_MODE_DUAL_CHANNEL) {
+ if (output->select == PLAYER_CHANNEL_DEFAULT) {
+ if (player->verbosity >= -1) {
+ error("output",
+ _("no channel selected for dual channel; using first"));
+ }
+
+ output->select = -PLAYER_CHANNEL_LEFT;
+ }
+
+ ch2 = 0;
+ nchannels = 1;
+ }
+ }
+ }
+
+ if (output->channels_in != nchannels ||
+ output->speed_in != pcm->samplerate) {
+ unsigned int speed_request;
+
+ if (player->verbosity >= 1 &&
+ pcm->samplerate != header->samplerate) {
+ error("output", _("decoded sample frequency %u Hz"),
+ pcm->samplerate);
+ }
+
+ speed_request = output->speed_request ?
+ output->speed_request : pcm->samplerate;
+
+ audio_control_init(&control, AUDIO_COMMAND_CONFIG);
+
+ control.config.channels = nchannels;
+ control.config.speed = speed_request;
+ control.config.precision = output->precision_in;
+
+ if (output->command(&control) == -1) {
+ error("output", audio_error);
+ return MAD_FLOW_BREAK;
+ }
+
+ output->channels_in = nchannels;
+ output->speed_in = pcm->samplerate;
+
+ output->channels_out = control.config.channels;
+ output->speed_out = control.config.speed;
+ output->precision_out = control.config.precision;
+
+ if (player->verbosity >= -1 &&
+ output->channels_in != output->channels_out) {
+ if (output->channels_in == 1)
+ error("output", _("mono output not available; forcing stereo"));
+ else {
+ error("output", _("stereo output not available; using first channel "
+ "(use -m to mix)"));
+ }
+ }
+
+ if (player->verbosity >= -1 &&
+ output->precision_in &&
+ output->precision_in != output->precision_out) {
+ error("output", _("bit depth %u not available; using %u"),
+ output->precision_in, output->precision_out);
+ }
+
+ if (player->verbosity >= -1 &&
+ speed_request != output->speed_out) {
+ error("output", _("sample frequency %u Hz not available; using %u Hz"),
+ speed_request, output->speed_out);
+ }
+
+ /* check whether resampling is necessary */
+ if (abs(output->speed_out - output->speed_in) <
+ (long) FREQ_TOLERANCE * output->speed_in / 100) {
+ if (output->resampled) {
+ resample_finish(&output->resample[0]);
+ resample_finish(&output->resample[1]);
+
+ free(output->resampled);
+ output->resampled = 0;
+ }
+ }
+ else {
+ if (output->resampled) {
+ resample_finish(&output->resample[0]);
+ resample_finish(&output->resample[1]);
+ }
+ else {
+ output->resampled = malloc(sizeof(*output->resampled));
+ if (output->resampled == 0) {
+ error("output",
+ _("not enough memory to allocate resampling buffer"));
+
+ output->speed_in = 0;
+ return MAD_FLOW_BREAK;
+ }
+ }
+
+ if (resample_init(&output->resample[0],
+ output->speed_in, output->speed_out) == -1 ||
+ resample_init(&output->resample[1],
+ output->speed_in, output->speed_out) == -1) {
+ error("output", _("cannot resample %u Hz to %u Hz"),
+ output->speed_in, output->speed_out);
+
+ free(output->resampled);
+ output->resampled = 0;
+
+ output->speed_in = 0;
+ return MAD_FLOW_BREAK;
+ }
+ else if (player->verbosity >= -1) {
+ error("output", _("resampling %u Hz to %u Hz"),
+ output->speed_in, output->speed_out);
+ }
+ }
+ }
+
+ audio_control_init(&control, AUDIO_COMMAND_PLAY);
+
+ if (output->channels_in != output->channels_out)
+ ch2 = (output->channels_out == 2) ? ch1 : 0;
+
+ if (output->resampled) {
+ control.play.nsamples = resample_block(&output->resample[0],
+ pcm->length, ch1,
+ (*output->resampled)[0]);
+ control.play.samples[0] = (*output->resampled)[0];
+
+ if (ch2 == ch1)
+ control.play.samples[1] = control.play.samples[0];
+ else if (ch2) {
+ resample_block(&output->resample[1], pcm->length, ch2,
+ (*output->resampled)[1]);
+ control.play.samples[1] = (*output->resampled)[1];
+ }
+ else
+ control.play.samples[1] = 0;
+ }
+ else {
+ control.play.nsamples = pcm->length;
+ control.play.samples[0] = ch1;
+ control.play.samples[1] = ch2;
+ }
+
+ control.play.mode = output->mode;
+ control.play.stats = &player->stats.audio;
+
+ if (output->command(&control) == -1) {
+ error("output", audio_error);
+ return MAD_FLOW_BREAK;
+ }
+
+ ++player->stats.play_framecount;
+ mad_timer_add(&player->stats.play_timer, header->duration);
+
+ if (player->verbosity > 0)
+ show_status(&player->stats, header, 0, 0);
+
+ return MAD_FLOW_CONTINUE;
+}
+
+/*
+ * NAME: get_id3()
+ * DESCRIPTION: read and parse an ID3 tag from a stream
+ */
+static
+struct id3_tag *get_id3(struct mad_stream *stream, id3_length_t tagsize,
+ struct input *input)
+{
+ struct id3_tag *tag = 0;
+ id3_length_t count;
+ id3_byte_t const *data;
+ id3_byte_t *allocated = 0;
+
+ count = stream->bufend - stream->this_frame;
+
+ if (tagsize <= count) {
+ data = stream->this_frame;
+ mad_stream_skip(stream, tagsize);
+ }
+ else {
+ allocated = malloc(tagsize);
+ if (allocated == 0) {
+ error("id3", _("not enough memory to allocate tag data buffer"));
+ goto fail;
+ }
+
+ memcpy(allocated, stream->this_frame, count);
+ mad_stream_skip(stream, count);
+
+ while (count < tagsize) {
+ int len;
+
+ do
+ len = read(input->fd, allocated + count, tagsize - count);
+ while (len == -1 && errno == EINTR);
+
+ if (len == -1) {
+ error("id3", ":read");
+ goto fail;
+ }
+
+ if (len == 0) {
+ error("id3", _("EOF while reading tag data"));
+ goto fail;
+ }
+
+ count += len;
+ }
+
+ data = allocated;
+ }
+
+ tag = id3_tag_parse(data, tagsize);
+
+ fail:
+ if (allocated)
+ free(allocated);
+
+ return tag;
+}
+
+/*
+ * NAME: decode->error()
+ * DESCRIPTION: handle a decoding error
+ */
+static
+enum mad_flow decode_error(void *data, struct mad_stream *stream,
+ struct mad_frame *frame)
+{
+ struct player *player = data;
+ signed long tagsize;
+
+ switch (stream->error) {
+ case MAD_ERROR_BADDATAPTR:
+ return MAD_FLOW_CONTINUE;
+
+ case MAD_ERROR_LOSTSYNC:
+ tagsize = id3_tag_query(stream->this_frame,
+ stream->bufend - stream->this_frame);
+ if (tagsize > 0) {
+ if (player->options & PLAYER_OPTION_STREAMID3) {
+ struct id3_tag *tag;
+
+ tag = get_id3(stream, tagsize, &player->input);
+ if (tag) {
+ process_id3(tag, player);
+ id3_tag_delete(tag);
+ }
+ }
+ else
+ mad_stream_skip(stream, tagsize);
+
+ if (player->stats.total_bytes >= tagsize)
+ player->stats.total_bytes -= tagsize;
+
+ return MAD_FLOW_CONTINUE;
+ }
+
+ /* fall through */
+
+ default:
+ if (player->verbosity >= -1 &&
+ !(player->options & PLAYER_OPTION_SHOWTAGSONLY) &&
+ ((stream->error == MAD_ERROR_LOSTSYNC && !player->input.eof)
+ || stream->sync) &&
+ player->stats.global_framecount != player->stats.error_frame) {
+ error("error", _("frame %lu: %s"),
+ player->stats.absolute_framecount, mad_stream_errorstr(stream));
+ player->stats.error_frame = player->stats.global_framecount;
+ }
+ }
+
+ if (stream->error == MAD_ERROR_BADCRC) {
+ if (player->stats.global_framecount == player->stats.mute_frame)
+ mad_frame_mute(frame);
+
+ player->stats.mute_frame = player->stats.global_framecount + 1;
+
+ return MAD_FLOW_IGNORE;
+ }
+
+ return MAD_FLOW_CONTINUE;
+}
+
+/*
+ * NAME: decode()
+ * DESCRIPTION: decode and output audio for an open file
+ */
+static
+int decode(struct player *player)
+{
+ struct stat stat;
+ struct mad_decoder decoder;
+ int options, result;
+
+ if (fstat(player->input.fd, &stat) == -1) {
+ error("decode", ":fstat");
+ return -1;
+ }
+
+ if (S_ISREG(stat.st_mode))
+ player->stats.total_bytes = stat.st_size;
+
+ tag_init(&player->input.tag);
+
+ /* prepare input buffers */
+
+# if defined(HAVE_MMAP)
+ if (S_ISREG(stat.st_mode) && stat.st_size > 0) {
+ player->input.length = stat.st_size;
+
+ player->input.fdm = map_file(player->input.fd, player->input.length);
+ if (player->input.fdm == 0 && player->verbosity >= 0)
+ error("decode", ":mmap");
+
+ player->input.data = player->input.fdm;
+ }
+# endif
+
+ if (player->input.data == 0) {
+ player->input.data = malloc(MPEG_BUFSZ);
+ if (player->input.data == 0) {
+ error("decode", _("not enough memory to allocate input buffer"));
+ return -1;
+ }
+
+ player->input.length = 0;
+ }
+
+ player->input.eof = 0;
+
+ /* reset statistics */
+ player->stats.absolute_timer = mad_timer_zero;
+ player->stats.play_timer = mad_timer_zero;
+ player->stats.absolute_framecount = 0;
+ player->stats.play_framecount = 0;
+ player->stats.error_frame = -1;
+ player->stats.vbr = 0;
+ player->stats.bitrate = 0;
+ player->stats.vbr_frames = 0;
+ player->stats.vbr_rate = 0;
+ player->stats.audio.clipped_samples = 0;
+ player->stats.audio.peak_clipping = 0;
+ player->stats.audio.peak_sample = 0;
+
+ mad_decoder_init(&decoder, player,
+# if defined(HAVE_MMAP)
+ player->input.fdm ? decode_input_mmap :
+# endif
+ decode_input_read,
+ decode_header, decode_filter,
+ player->output.command ? decode_output : 0,
+ decode_error, 0);
+
+ options = 0;
+ if (player->options & PLAYER_OPTION_DOWNSAMPLE)
+ options |= MAD_OPTION_HALFSAMPLERATE;
+ if (player->options & PLAYER_OPTION_IGNORECRC)
+ options |= MAD_OPTION_IGNORECRC;
+
+ mad_decoder_options(&decoder, options);
+
+ result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
+
+ mad_decoder_finish(&decoder);
+
+# if defined(HAVE_MMAP)
+ if (player->input.fdm) {
+ if (unmap_file(player->input.fdm, player->input.length) == -1) {
+ error("decode", ":munmap");
+ result = -1;
+ }
+
+ player->input.fdm = 0;
+
+ if (!player->input.eof)
+ player->input.data = 0;
+ }
+# endif
+
+ if (player->input.data) {
+ free(player->input.data);
+ player->input.data = 0;
+ }
+
+ tag_finish(&player->input.tag);
+
+ return result;
+}
+
+/*
+ * NAME: play_one()
+ * DESCRIPTION: open and play a single file
+ */
+static
+int play_one(struct player *player)
+{
+ char const *file = player->playlist.entries[player->playlist.current];
+ int result;
+
+ if (strcmp(file, "-") == 0) {
+ if (isatty(STDIN_FILENO)) {
+ error(0, "%s: %s", _("stdin"), _("is a tty"));
+ return -1;
+ }
+
+ player->input.path = _("stdin");
+ player->input.fd = STDIN_FILENO;
+ }
+ else {
+ player->input.path = file;
+ player->input.fd = open(file, O_RDONLY | O_BINARY);
+ if (player->input.fd == -1) {
+ error(0, ":", file);
+ return -1;
+ }
+ }
+
+ if (player->verbosity >= 0 &&
+ player->playlist.length > 1)
+ message(">> %s\n", player->input.path);
+
+ /* reset file information */
+
+ player->stats.total_bytes = 0;
+ player->stats.total_time = mad_timer_zero;
+
+ if (!(player->options & PLAYER_OPTION_IGNOREVOLADJ))
+ set_gain(player, GAIN_VOLADJ, 0);
+
+ player->output.replay_gain &= ~PLAYER_RGAIN_SET;
+
+ /* try reading ID3 tag information now (else read later from stream) */
+ {
+ int fd;
+ struct id3_file *file;
+
+ player->options &= ~PLAYER_OPTION_STREAMID3;
+
+ fd = dup(player->input.fd);
+ file = id3_file_fdopen(fd, ID3_FILE_MODE_READONLY);
+ if (file == 0) {
+ close(fd);
+ player->options |= PLAYER_OPTION_STREAMID3;
+ }
+ else {
+ process_id3(id3_file_tag(file), player);
+ id3_file_close(file);
+ }
+ }
+
+ result = decode(player);
+
+ if (result == 0 && player->verbosity >= 0 &&
+ !(player->options & PLAYER_OPTION_SHOWTAGSONLY)) {
+ char time_str[19], db_str[7];
+ char const *peak_str;
+ mad_fixed_t peak;
+
+ mad_timer_string(player->stats.play_timer, time_str, "%lu:%02u:%02u.%1u",
+ MAD_UNITS_HOURS, MAD_UNITS_DECISECONDS, 0);
+
+# if defined(HAVE_LOCALECONV)
+ {
+ char *point;
+
+ point = strchr(time_str, '.');
+ if (point)
+ *point = *localeconv()->decimal_point;
+ }
+# endif
+
+ peak = MAD_F_ONE + player->stats.audio.peak_clipping;
+ if (peak == MAD_F_ONE)
+ peak = player->stats.audio.peak_sample;
+
+ if (peak == 0)
+ peak_str = "-inf";
+ else {
+ sprintf(db_str, "%+.1f", 20 * log10(mad_f_todouble(peak)));
+ peak_str = db_str;
+ }
+
+ message("%lu %s (%s), %s dB %s, %lu %s\n",
+ player->stats.play_framecount, player->stats.play_framecount == 1 ?
+ _("frame decoded") : _("frames decoded"), time_str,
+ peak_str, _("peak amplitude"), player->stats.audio.clipped_samples,
+ player->stats.audio.clipped_samples == 1 ?
+ _("clipped sample") : _("clipped samples"));
+ }
+
+ if (player->input.fd != STDIN_FILENO &&
+ close(player->input.fd) == -1 && result == 0) {
+ error(0, ":", player->input.path);
+ result = -1;
+ }
+
+ return result;
+}
+
+/*
+ * NAME: play_all()
+ * DESCRIPTION: run the player's playlist
+ */
+static
+int play_all(struct player *player)
+{
+ int count, i, j, result = 0;
+ struct playlist *playlist = &player->playlist;
+ char const *tmp;
+
+ /* set up playlist */
+
+ count = playlist->length;
+
+ if (player->options & PLAYER_OPTION_SHUFFLE) {
+ srand(time(0));
+
+ /* initial shuffle */
+ for (i = 0; i < count; ++i) {
+ j = rand() % count;
+
+ tmp = playlist->entries[i];
+ playlist->entries[i] = playlist->entries[j];
+ playlist->entries[j] = tmp;
+ }
+ }
+
+ /* run playlist */
+
+ while (count && (player->repeat == -1 || player->repeat--)) {
+ while (playlist->current < playlist->length) {
+ i = playlist->current;
+
+ if (playlist->entries[i] == 0) {
+ ++playlist->current;
+ continue;
+ }
+
+ player->control = PLAYER_CONTROL_DEFAULT;
+
+ if (play_one(player) == -1) {
+ playlist->entries[i] = 0;
+ --count;
+
+ result = -1;
+ }
+
+ if ((player->options & PLAYER_OPTION_TIMED) &&
+ mad_timer_compare(player->stats.global_timer,
+ player->global_stop) > 0) {
+ count = 0;
+ break;
+ }
+
+ switch (player->control) {
+ case PLAYER_CONTROL_DEFAULT:
+ if ((player->options & PLAYER_OPTION_SHUFFLE) && player->repeat &&
+ ++i < playlist->length) {
+ /* pick something from the next half only */
+ j = (i + rand() % ((playlist->length + 1) / 2)) % playlist->length;
+
+ tmp = playlist->entries[i];
+ playlist->entries[i] = playlist->entries[j];
+ playlist->entries[j] = tmp;
+ }
+ /* fall through */
+
+ case PLAYER_CONTROL_NEXT:
+ ++playlist->current;
+ break;
+
+ case PLAYER_CONTROL_PREVIOUS:
+ do {
+ if (playlist->current-- == 0)
+ playlist->current = playlist->length;
+ }
+ while (playlist->current < playlist->length &&
+ playlist->entries[playlist->current] == 0);
+ break;
+
+ case PLAYER_CONTROL_REPLAY:
+ break;
+
+ case PLAYER_CONTROL_STOP:
+ playlist->current = playlist->length;
+ count = 0;
+ break;
+ }
+ }
+
+ playlist->current = 0;
+ }
+
+ return result;
+}
+
+/*
+ * NAME: stop_audio()
+ * DESCRIPTION: stop playing the audio device immediately
+ */
+static
+int stop_audio(struct player *player, int flush)
+{
+ int result = 0;
+
+ if (player->output.command) {
+ union audio_control control;
+
+ audio_control_init(&control, AUDIO_COMMAND_STOP);
+ control.stop.flush = flush;
+
+ result = player->output.command(&control);
+ }
+
+ return result;
+}
+
+# if defined(USE_TTY)
+/*
+ * NAME: readkey()
+ * DESCRIPTION: read a keypress from the keyboard
+ */
+static
+int readkey(int blocking)
+{
+# if !defined(_WIN32)
+ unsigned char key;
+ ssize_t count;
+
+ if (!blocking) {
+ /* tty_fd should be a tty in noncanonical mode with VMIN = VTIME = 0 */
+
+ count = read(tty_fd, &key, 1);
+ if (count == -1 && errno != EINTR) {
+ error("tty", ":read");
+ return -1;
+ }
+
+ return (count == 1) ? key : 0;
+ }
+ else {
+ struct termios tty, save_tty;
+
+ if (tcgetattr(tty_fd, &tty) == -1) {
+ error("tty", ":tcgetattr");
+ return -1;
+ }
+
+ save_tty = tty;
+
+ /* change terminal temporarily to get a blocking read() */
+
+ tty.c_cc[VMIN] = 1;
+
+ if (tcsetattr(tty_fd, TCSANOW, &tty) == -1) {
+ error("tty", ":tcsetattr");
+ return -1;
+ }
+
+ do
+ count = read(tty_fd, &key, 1);
+ while (count == -1 && errno == EINTR);
+
+ if (count == -1)
+ error("tty", ":read");
+
+ if (tcsetattr(tty_fd, TCSANOW, &save_tty) == -1) {
+ error("tty", ":tcsetattr");
+ return -1;
+ }
+
+ if (count == -1)
+ return -1;
+
+ return (count == 1) ? key : 0;
+ }
+# elif defined(_WIN32)
+ HANDLE console;
+ INPUT_RECORD input;
+ DWORD count;
+
+ console = GetStdHandle(STD_INPUT_HANDLE);
+
+ do {
+ if (GetNumberOfConsoleInputEvents(console, &count) == 0) {
+ error("tty", "GetNumberOfConsoleInputEvents() failed");
+ return -1;
+ }
+
+ if (count == 0) {
+ if (!blocking)
+ return 0;
+ else {
+ /* this is necessary to keep Windows from hanging (!) */
+ Sleep(500);
+
+ switch (WaitForSingleObject(console, INFINITE)) {
+ case WAIT_ABANDONED:
+ case WAIT_OBJECT_0:
+ continue;
+
+ case WAIT_TIMEOUT:
+ default:
+ /* ? */
+ case WAIT_FAILED:
+ error("tty", "WaitForSingleObject() failed");
+ return -1;
+ }
+ }
+ }
+
+ if (ReadConsoleInput(console, &input, 1, &count) == 0 || count != 1) {
+ error("tty", "ReadConsoleInput() failed");
+ return -1;
+ }
+ }
+ while (input.EventType != KEY_EVENT || !input.Event.KeyEvent.bKeyDown ||
+ input.Event.KeyEvent.uChar.AsciiChar == 0);
+
+ return (unsigned char) input.Event.KeyEvent.uChar.AsciiChar;
+# endif
+
+ return blocking ? -1 : 0;
+}
+
+/*
+ * NAME: tty_filter()
+ * DESCRIPTION: process TTY commands
+ */
+static
+enum mad_flow tty_filter(void *data, struct mad_frame *frame)
+{
+ struct player *player = data;
+ enum mad_flow flow = MAD_FLOW_CONTINUE;
+ int command, stopped = 0;
+
+ command = readkey(0);
+ if (command == -1)
+ return MAD_FLOW_BREAK;
+
+ again:
+ switch (command) {
+ case KEY_STOP:
+ stopped = 1;
+
+ player->control = PLAYER_CONTROL_REPLAY;
+ flow = MAD_FLOW_STOP;
+
+ /* fall through */
+
+ case KEY_PAUSE:
+ stop_audio(player, stopped);
+ message(" --%s--", stopped ? _("Stopped") : _("Paused"));
+
+ command = readkey(1);
+
+ message("");
+
+ if (command == -1)
+ return MAD_FLOW_BREAK;
+
+ if (command != KEY_PAUSE)
+ goto again;
+
+ break;
+
+ case KEY_FORWARD:
+ case KEY_CTRL('n'):
+ case '>':
+ player->control = PLAYER_CONTROL_NEXT;
+ goto stop;
+
+ case KEY_BACK:
+ case KEY_CTRL('p'):
+ case '<':
+ {
+ mad_timer_t threshold;
+
+ mad_timer_set(&threshold, 4, 0, 0);
+
+ player->control =
+ (stopped ||
+ mad_timer_compare(player->stats.play_timer, threshold) < 0) ?
+ PLAYER_CONTROL_PREVIOUS : PLAYER_CONTROL_REPLAY;
+ }
+ goto stop;
+
+ case KEY_QUIT:
+ case KEY_CTRL('c'):
+ case 'Q':
+ player->control = PLAYER_CONTROL_STOP;
+ goto stop;
+
+ case KEY_INFO:
+ case '?':
+ if (player->verbosity <= 0) {
+ show_status(&player->stats, 0, player->input.path, 1);
+ message("\n");
+ }
+ break;
+
+ case KEY_TIME:
+ if (player->verbosity > 0) {
+ char const *label = 0;
+
+ switch (player->stats.show) {
+ case STATS_SHOW_OVERALL:
+ player->stats.show = STATS_SHOW_REMAINING;
+ label = N_("[Current Time Remaining]");
+ break;
+
+ case STATS_SHOW_REMAINING:
+ player->stats.show = STATS_SHOW_CURRENT;
+ label = N_("[Current Time]");
+ break;
+
+ case STATS_SHOW_CURRENT:
+ player->stats.show = STATS_SHOW_OVERALL;
+ label = N_("[Overall Time]");
+ break;
+ }
+
+ show_status(&player->stats, 0, gettext(label), 1);
+ }
+ break;
+
+ case KEY_GAINDECR:
+ case KEY_GAININCR:
+ case KEY_GAINZERO:
+ case KEY_GAININFO:
+ {
+ double db;
+
+ switch (command) {
+ case KEY_GAINDECR:
+ db = set_gain(player, GAIN_ATTAMP | GAIN_RELATIVE, -0.5);
+ break;
+
+ case KEY_GAININCR:
+ db = set_gain(player, GAIN_ATTAMP | GAIN_RELATIVE, +0.5);
+ break;
+
+ case KEY_GAINZERO:
+ db = set_gain(player, GAIN_ATTAMP, 0);
+ break;
+
+ default:
+ db = set_gain(player, 0, 0);
+ break;
+ }
+
+ if (player->verbosity > 0) {
+ static char status[15];
+
+ sprintf(status, "%+.1f dB gain", db);
+ show_status(&player->stats, 0, status, 1);
+ }
+ }
+ break;
+ }
+
+ return flow;
+
+ stop:
+ stop_audio(player, 1);
+ return MAD_FLOW_STOP;
+}
+# endif
+
+/*
+ * NAME: addfilter()
+ * DESCRIPTION: insert a filter at the beginning of the filter chain
+ */
+static
+int addfilter(struct player *player, filter_func_t *func, void *data)
+{
+ struct filter *filter;
+
+ filter = filter_new(func, data, player->output.filters);
+ if (filter == 0)
+ return -1;
+
+ player->output.filters = filter;
+
+ return 0;
+}
+
+/*
+ * NAME: setup_filters()
+ * DESCRIPTION: create output filters
+ */
+static
+int setup_filters(struct player *player)
+{
+ /* filters must be added in reverse order */
+
+# if defined(EXPERIMENTAL)
+ if ((player->options & PLAYER_OPTION_EXTERNALMIX) &&
+ addfilter(player, mixer_filter, stdout) == -1)
+ return -1;
+
+ if ((player->options & PLAYER_OPTION_EXPERIMENTAL) &&
+ addfilter(player, experimental_filter, 0) == -1)
+ return -1;
+# endif
+
+ if ((player->options & PLAYER_OPTION_FADEIN) &&
+ addfilter(player, fadein_filter, player) == -1)
+ return -1;
+
+ addfilter(player, gain_filter, &player->output.gain);
+
+ if (player->output.select == PLAYER_CHANNEL_MONO &&
+ addfilter(player, mono_filter, player) == -1)
+ return -1;
+
+# if defined(USE_TTY)
+ if ((player->options & PLAYER_OPTION_TTYCONTROL) &&
+ addfilter(player, tty_filter, player) == -1)
+ return -1;
+# endif
+
+ return 0;
+}
+
+# if defined(USE_TTY) && !defined(_WIN32)
+/*
+ * NAME: restore_tty()
+ * DESCRIPTION: revert to previous terminal settings
+ */
+static
+int restore_tty(int interrupt)
+{
+ struct termios tty;
+ struct sigaction action;
+ int result = 0;
+
+ if (tcgetattr(tty_fd, &tty) == 0 &&
+ tcsetattr(tty_fd, interrupt ? TCSAFLUSH : TCSADRAIN,
+ &save_tty) == -1) {
+ if (!interrupt)
+ error("tty", ":tcsetattr");
+ result = -1;
+ }
+
+ save_tty = tty;
+
+ if (sigaction(SIGINT, 0, &action) == 0 &&
+ sigaction(SIGINT, &save_sigint, 0) == -1) {
+ if (!interrupt)
+ error("tty", ":sigaction(SIGINT)");
+ result = -1;
+ }
+
+ save_sigint = action;
+
+ if (sigaction(SIGTSTP, 0, &action) == 0 &&
+ sigaction(SIGTSTP, &save_sigtstp, 0) == -1) {
+ if (!interrupt)
+ error("tty", ":sigaction(SIGTSTP)");
+ result = -1;
+ }
+
+ save_sigtstp = action;
+
+ if (!interrupt) {
+ if (close(tty_fd) == -1) {
+ error("tty", ":close");
+ result = -1;
+ }
+
+ tty_fd = -1;
+ }
+
+ return result;
+}
+
+/*
+ * NAME: signal_handler()
+ * DESCRIPTION: restore tty state after software interrupt
+ */
+static
+void signal_handler(int signal)
+{
+ static struct sigaction save_sigcont;
+
+ /* restore tty state and previous signal actions */
+
+ restore_tty(1);
+
+ /* handle SIGCONT after SIGTSTP */
+
+ switch (signal) {
+ case SIGTSTP:
+ {
+ struct sigaction action;
+
+ sigaction(SIGCONT, 0, &save_sigcont);
+
+ action = save_sigcont;
+ action.sa_handler = signal_handler;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SIGTSTP);
+ sigaddset(&action.sa_mask, SIGINT);
+ action.sa_flags = 0;
+
+ sigaction(SIGCONT, &action, 0);
+ }
+ break;
+
+ case SIGCONT:
+ sigaction(SIGCONT, &save_sigcont, 0);
+ on_same_line = 0; /* redraw status line */
+ break;
+ }
+
+ /* re-send signal, which is currently blocked */
+
+ kill(getpid(), signal);
+
+ /* return to previous thread, which should immediately receive the signal */
+
+ return;
+}
+
+/*
+ * NAME: setup_tty()
+ * DESCRIPTION: change terminal parameters and signal handlers
+ */
+static
+int setup_tty(void)
+{
+ struct termios tty;
+ struct sigaction action;
+
+ /* open controlling terminal */
+
+ tty_fd = open(TTY_DEVICE, O_RDONLY);
+ if (tty_fd == -1) {
+ error("tty", ":", TTY_DEVICE);
+ return -1;
+ }
+
+ /* save current terminal and signal settings */
+
+ if (tcgetattr(tty_fd, &save_tty) == -1) {
+ error("tty", ":tcgetattr");
+ return -1;
+ }
+
+ if (sigaction(SIGTSTP, 0, &save_sigtstp) == -1) {
+ error("tty", ":sigaction(SIGTSTP)");
+ return -1;
+ }
+
+ if (sigaction(SIGINT, 0, &save_sigint) == -1) {
+ error("tty", ":sigaction(SIGINT)");
+ return -1;
+ }
+
+ /* catch SIGTSTP and SIGINT so the tty state can be restored */
+
+ action = save_sigtstp;
+ action.sa_handler = signal_handler;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SIGINT);
+# if 0 /* on some systems (Mac OS X) this remains masked upon continue (?!) */
+ sigaddset(&action.sa_mask, SIGCONT);
+# endif
+ action.sa_flags = 0;
+
+ if (sigaction(SIGTSTP, &action, 0) == -1) {
+ error("tty", ":sigaction(SIGTSTP)");
+ goto fail;
+ }
+
+ action = save_sigint;
+ action.sa_handler = signal_handler;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SIGTSTP);
+ sigaddset(&action.sa_mask, SIGCONT);
+ action.sa_flags = 0;
+
+ if (sigaction(SIGINT, &action, 0) == -1) {
+ error("tty", ":sigaction(SIGINT)");
+ goto fail;
+ }
+
+ /* turn off echo and canonical mode */
+
+ tty = save_tty;
+
+ tty.c_lflag &= ~(ECHO | ICANON);
+
+ /* set VMIN = VTIME = 0 so read() always returns immediately */
+
+ tty.c_cc[VMIN] = 0;
+ tty.c_cc[VTIME] = 0;
+
+ if (tcsetattr(tty_fd, TCSAFLUSH, &tty) == -1) {
+ error("tty", ":tcsetattr");
+ goto fail;
+ }
+
+ return 0;
+
+ fail:
+ sigaction(SIGINT, &save_sigint, 0);
+ sigaction(SIGTSTP, &save_sigtstp, 0);
+ return -1;
+}
+# endif
+
+/*
+ * NAME: silence()
+ * DESCRIPTION: output silence for a period of time
+ */
+static
+int silence(struct player *player, mad_timer_t duration, char const *label)
+{
+ union audio_control control;
+ unsigned int nchannels, speed, nsamples;
+ mad_fixed_t *samples;
+ mad_timer_t unit;
+ int result = 0;
+
+ audio_control_init(&control, AUDIO_COMMAND_CONFIG);
+ control.config.channels = 2;
+ control.config.speed = 44100;
+
+ if (player->output.command(&control) == -1) {
+ error("audio", audio_error);
+ return -1;
+ }
+
+ nchannels = control.config.channels;
+ speed = control.config.speed;
+ nsamples = speed > MAX_NSAMPLES ? MAX_NSAMPLES : speed;
+
+ player->output.channels_in = nchannels;
+ player->output.channels_out = nchannels;
+ player->output.speed_in = speed;
+ player->output.speed_out = speed;
+
+ samples = calloc(nsamples, sizeof(mad_fixed_t));
+ if (samples == 0) {
+ error("silence", _("not enough memory to allocate sample buffer"));
+ return -1;
+ }
+
+ audio_control_init(&control, AUDIO_COMMAND_PLAY);
+ control.play.nsamples = nsamples;
+ control.play.samples[0] = samples;
+ control.play.samples[1] = (nchannels == 2) ? samples : 0;
+ control.play.mode = player->output.mode;
+ control.play.stats = &player->stats.audio;
+
+ mad_timer_set(&unit, 0, nsamples, speed);
+
+ for (mad_timer_negate(&duration);
+ mad_timer_sign(duration) < 0;
+ mad_timer_add(&duration, unit)) {
+ if (mad_timer_compare(unit, mad_timer_abs(duration)) > 0) {
+ unit = mad_timer_abs(duration);
+ control.play.nsamples = mad_timer_fraction(unit, speed);
+ }
+
+# if defined(USE_TTY)
+ if ((player->options & PLAYER_OPTION_TTYCONTROL) &&
+ tty_filter(player, 0) != MAD_FLOW_CONTINUE)
+ goto fail;
+# endif
+
+ if (player->output.command(&control) == -1) {
+ error("audio", audio_error);
+ goto fail;
+ }
+
+ mad_timer_add(&player->stats.global_timer, unit);
+
+ if (player->verbosity > 0)
+ show_status(&player->stats, 0, label, 0);
+ }
+
+ if (0) {
+ fail:
+ result = -1;
+ }
+
+ free(samples);
+
+ return result;
+}
+
+/*
+ * NAME: player->run()
+ * DESCRIPTION: begin playback
+ */
+int player_run(struct player *player, int argc, char const *argv[])
+{
+ int result = 0;
+ union audio_control control;
+
+ player->playlist.entries = argv;
+ player->playlist.length = argc;
+
+ /* set up terminal settings */
+
+# if defined(USE_TTY) && !defined(_WIN32)
+ if ((player->options & PLAYER_OPTION_TTYCONTROL) &&
+ setup_tty() == -1)
+ player->options &= ~PLAYER_OPTION_TTYCONTROL;
+# endif
+
+ /* initialize ancillary data output file */
+
+ if (player->ancillary.path) {
+ if (player->output.path &&
+ strcmp(player->ancillary.path, player->output.path) == 0) {
+ error("output", _("ancillary and audio output have same path"));
+ goto fail;
+ }
+
+ if (strcmp(player->ancillary.path, "-") == 0)
+ player->ancillary.file = stdout;
+ else {
+ player->ancillary.file = fopen(player->ancillary.path, "wb");
+ if (player->ancillary.file == 0) {
+ error("ancillary", ":", player->ancillary.path);
+ goto fail;
+ }
+ }
+ }
+
+ /* set up filters */
+
+ if (setup_filters(player) == -1) {
+ error("filter", _("not enough memory to allocate filters"));
+ goto fail;
+ }
+
+ set_gain(player, 0, 0);
+
+ /* initialize audio */
+
+ if (player->output.command) {
+ audio_control_init(&control, AUDIO_COMMAND_INIT);
+ control.init.path = player->output.path;
+
+ if (player->output.command(&control) == -1) {
+ error("audio", audio_error, control.init.path);
+ goto fail;
+ }
+
+ if ((player->options & PLAYER_OPTION_SKIP) &&
+ mad_timer_sign(player->global_start) < 0) {
+ player->stats.global_timer = player->global_start;
+
+ if (silence(player, mad_timer_abs(player->global_start),
+ _("lead-in")) == -1)
+ result = -1;
+ }
+ }
+
+ /* run playlist */
+
+ if (result == 0)
+ result = play_all(player);
+
+ /* drain and close audio */
+
+ if (player->output.command) {
+ audio_control_init(&control, AUDIO_COMMAND_FINISH);
+
+ if (player->output.command(&control) == -1) {
+ error("audio", audio_error);
+ goto fail;
+ }
+ }
+
+ if (0) {
+ fail:
+ result = -1;
+ }
+
+ /* drain and close ancillary data output file */
+
+ if (player->ancillary.file) {
+ if (player->ancillary.length) {
+ if (fputc(player->ancillary.buffer << (8 - player->ancillary.length),
+ player->ancillary.file) == EOF &&
+ result == 0) {
+ error("ancillary", ":fputc");
+ result = -1;
+ }
+
+ player->ancillary.length = 0;
+ }
+
+ if (player->ancillary.file != stdout &&
+ fclose(player->ancillary.file) == EOF &&
+ result == 0) {
+ error("ancillary", ":fclose");
+ result = -1;
+ }
+
+ player->ancillary.file = 0;
+ }
+
+ /* restore terminal settings */
+
+# if defined(USE_TTY) && !defined(_WIN32)
+ if (player->options & PLAYER_OPTION_TTYCONTROL)
+ restore_tty(0);
+# endif
+
+ return result;
+}