From dabbc01437855b426b1f7debe793e05b4d1e628c Mon Sep 17 00:00:00 2001 From: thor Date: Fri, 31 Jan 2020 23:48:50 +0000 Subject: [PATCH] mpg123, libout123, fmt123: Make a very special friend happy, introduce output mute. This introduces software muting in libout123, to be triggered via terminal control key 'u' (m was taken) or the remote control commands 'mute' and 'unmute'. For this, libout123 needs to know what a zero looks like in the current encoding. I hope I handled that smartly enough with the MPG123_ZEROSAMPLE macro in fmt123. I explicitly decided against linking in libsyn123. That makes only sense when going all-in and deciding that libout123 shall convert, resample, and mix on-the-fly to make input data match the output. This might be nice to have, but it is also nice to have a library that does not really care about the content it transports. It is a simple transporter with a buffer. Said buffer necessitates that the transporter knows what empty sound looks like, but I really don't want to burden it with more knowledge for simplicity. This muting needs to be inside libout123 whe the buffer is used. Feeding silence from the client application does not have latency you expect when (un)pause is already negotiated with the buffer. git-svn-id: svn://scm.orgis.org/mpg123/trunk@4589 35dc7657-300d-0410-a2e5-dc2837fedb53 --- README | 12 ++++-- doc/README.remote | 20 +++++++++- man1/mpg123.1 | 6 ++- src/audio.c | 7 ++++ src/audio.h | 2 + src/common.c | 8 ++-- src/common.h | 3 +- src/control_generic.c | 14 +++++++ src/libmpg123/fmt123.h | 26 +++++++++++- src/libout123/libout123.c | 81 +++++++++++++++++++++++++++++--------- src/libout123/out123.h.in | 9 +++-- src/libout123/out123_int.h | 1 + src/term.c | 8 +++- src/term.h | 1 + 14 files changed, 162 insertions(+), 36 deletions(-) diff --git a/README b/README index 830b0ab2..c0a40291 100644 --- a/README +++ b/README @@ -73,13 +73,14 @@ Note that this Ctrl+C behaviour is special to this mode; when any of the followi 3.2 Advanced Console Usage You can specify the option -C to enable a terminal control interface enabling to influence playback on current title/playlist by pressing some key: - -= terminal control keys =- -[s] or [ ] interrupt/restart playback (i.e. 'pause') +[s] or [ ] interrupt/restart playback (i.e. '(un)pause') [f] next track [d] previous track +[]] next directory (next track until directory part changes) +[[] previous directory (previous track until directory part changes) [b] back to beginning of track -[p] pause while looping current sound chunk +[p] loop around current position (don't combine with output buffer) [.] forward [,] rewind [:] fast forward @@ -88,11 +89,16 @@ You can specify the option -C to enable a terminal control interface enabling to [<] fine rewind [+] volume up [-] volume down +[u] (un)mute volume [r] RVA switch [v] verbose switch [l] list current playlist, indicating current track there [t] display tag info (again) [m] print MPEG header info (again) +[c] or [C] pitch up (small step, big step) +[x] or [X] pitch down (small step, big step) +[w] reset pitch to zero +[k] print out current position in playlist and track, for the benefit of some external tool to store bookmarks [h] this help [q] quit diff --git a/doc/README.remote b/doc/README.remote index c4a77fe5..4da8c702 100644 --- a/doc/README.remote +++ b/doc/README.remote @@ -22,6 +22,7 @@ COMMAND CODES You can get this info via the control command "help". + HELP/H: command listing (LONG/SHORT forms), command case insensitve LOAD/L : load and start playing resource @@ -38,6 +39,10 @@ JUMP/J |<+offset>|<-offset>|<[+|-]seconds>s: jump to mpeg frame o VOLUME/V : set volume in % (0..100...); float value +MUTE: turn on software mute in output + +UNMUTE: turn off software mute in output + RVA off|(mix|radio)|(album|audiophile): set rva mode EQ/E : set equalizer value for frequency band 0 to 31 on channel 1 (left) or 2 (right) or 3 (both) @@ -52,25 +57,36 @@ SCAN: scan through the file, building seek index SAMPLE: print out the sample position and total number of samples +FORMAT: print out sampling rate in Hz and channel count + SEQ : simple eq setting... PITCH <[+|-]value>: adjust playback speed (+0.01 is 1 % faster) SILENCE: be silent during playback (meaning silence in text form) -STATE: Print auxilliary state info in several lines (just try it to see what info is there). +STATE: Print auxiliary state info in several lines (just try it to see what info is there). + +TAG/T: Print all available (ID3) tag info, for ID3v2 that gives output of all collected text fields, using the ID3v2.3/4 4-character names. NOTE: ID3v2 data will be deleted on non-forward seeks. -TAG/T: Print all available (ID3) tag info, for ID3v2 that gives output of all collected text fields, using the ID3v2.3/4 4-character names. The output is multiple lines, begin marked by "@T {", end by "@T }". + ID3v1 data is like in the @I info lines (see below), just with "@T" in front. + An ID3v2 data field is introduced via ([ ... ] means optional): + @T ID3v2.[ [lang()] desc()]: + The lines of data follow with "=" prefixed: + @T = + meaning of the @S stream info: S + The @I lines after loading a track give some ID3 info, the format: + @I ID3:artist album year comment genretext where artist,album and comment are exactly 30 characters each, year is 4 characters, genre text unspecified. You will encounter "@I ID3.genre:" and "@I ID3.track:". diff --git a/man1/mpg123.1 b/man1/mpg123.1 index 28480bb6..b34bd1ea 100644 --- a/man1/mpg123.1 +++ b/man1/mpg123.1 @@ -561,8 +561,10 @@ complete junk on the input side. Fatal errors were only considered for output. With version 1.26.0, this changed to the behaviour described below. .P -The process exit code is zero (success) if all tracks in a playlist -had at least one frame parsed, even if it did not decode cleanly, or +When not using the remote control interface (which returns input +errors as text messages), the process exit code is zero (success) +only if all tracks in a playlist had at least one frame parsed, +even if it did not decode cleanly, or are empty, MPEG-wise (perhaps only metadata, or really an empty file). When you decode nothing, nothing is the result and that is fine. When a track later aborts because of parser errors or breakdown of the diff --git a/src/audio.c b/src/audio.c index 4042d617..6e6e5093 100644 --- a/src/audio.c +++ b/src/audio.c @@ -239,3 +239,10 @@ int set_pitch(mpg123_handle *fr, out123_handle *ao, double new_pitch) } return out123_start(ao, pitch_rate(rate), channels, format); } + +int set_mute(out123_handle *ao, int mutestate) +{ + return out123_param( ao + , mutestate ? OUT123_ADD_FLAGS : OUT123_REMOVE_FLAGS + , OUT123_MUTE, 0, NULL ); +} diff --git a/src/audio.h b/src/audio.h index 724f00e7..da4e9673 100644 --- a/src/audio.h +++ b/src/audio.h @@ -33,6 +33,8 @@ void print_capabilities(out123_handle *ao, mpg123_handle *mh); Returns 1 if pitch setting succeeded, 0 otherwise. */ int set_pitch(mpg123_handle *fr, out123_handle *ao, double new_pitch); +// Enable/disable software mute state. +int set_mute(out123_handle *ao, int mutestate); #endif diff --git a/src/common.c b/src/common.c index f47500e7..e2941af5 100644 --- a/src/common.c +++ b/src/common.c @@ -31,6 +31,7 @@ int stopped = 0; int paused = 0; +int muted = 0; static int term_is_fun = -1; int term_have_fun(int fd, struct parameter *param) @@ -338,9 +339,10 @@ void print_stat(mpg123_handle *fr, long offset, out123_handle *ao, int draw_bar if(len >= 0 && len < linelen) { /* Volume info. */ int len_add = snprintf( line+len, linelen-len - , " %s %03u=%03u" - , rva_statname[param->rva], roundui(basevol*100), roundui(realvol*100) - ); + , " %s %03u%c%03u" + , rva_statname[param->rva] + , roundui(basevol*100), muted ? 'm' : '=' + , roundui(realvol*100) ); if(len_add > 0) len += len_add; } diff --git a/src/common.h b/src/common.h index 07b8bc43..1dbc2381 100644 --- a/src/common.h +++ b/src/common.h @@ -1,7 +1,7 @@ /* common: anything can happen here... frame reading, output, messages - copyright ?-2006 by the mpg123 project - free software under the terms of the LGPL 2.1 + copyright ?-2020 by the mpg123 project - free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Michael Hipp */ @@ -14,6 +14,7 @@ extern int stopped; extern int paused; +extern int muted; /* Return non-zero if full terminal fun is desired/possible. */ int term_have_fun(int fd, struct parameter *param); diff --git a/src/control_generic.c b/src/control_generic.c index 61dfa469..ee0d22db 100644 --- a/src/control_generic.c +++ b/src/control_generic.c @@ -513,6 +513,18 @@ int control_generic (mpg123_handle *fr) continue; } + if(!strcasecmp(comstr, "MUTE")) { + set_mute(ao, muted=TRUE); + generic_sendmsg("mute"); + continue; + } + + if(!strcasecmp(comstr, "UNMUTE")) { + set_mute(ao, muted=FALSE); + generic_sendmsg("unmute"); + continue; + } + if(!strcasecmp(comstr, "T") || !strcasecmp(comstr, "TAG")) { generic_sendalltag(fr); continue; @@ -594,6 +606,8 @@ int control_generic (mpg123_handle *fr) generic_sendmsg("H STOP/S: stop playback (closes file)"); generic_sendmsg("H JUMP/J |<+offset>|<-offset>|<[+|-]seconds>s: jump to mpeg frame or change position by offset, same in seconds if number followed by \"s\""); generic_sendmsg("H VOLUME/V : set volume in % (0..100...); float value"); + generic_sendmsg("H MUTE: turn on software mute in output"); + generic_sendmsg("H UNMUTE: turn off software mute in output"); generic_sendmsg("H RVA off|(mix|radio)|(album|audiophile): set rva mode"); generic_sendmsg("H EQ/E : set equalizer value for frequency band 0 to 31 on channel %i (left) or %i (right) or %i (both)", MPG123_LEFT, MPG123_RIGHT, MPG123_LR); generic_sendmsg("H EQFILE : load EQ settings from a file"); diff --git a/src/libmpg123/fmt123.h b/src/libmpg123/fmt123.h index 3e55a124..0033bbba 100644 --- a/src/libmpg123/fmt123.h +++ b/src/libmpg123/fmt123.h @@ -100,8 +100,8 @@ enum mpg123_enc_enum */ #define MPG123_SAMPLESIZE(enc) ( \ (enc) < 1 \ - ? 0 \ - : ( (enc) & MPG123_ENC_8 \ + ? 0 \ + : ( (enc) & MPG123_ENC_8 \ ? 1 \ : ( (enc) & MPG123_ENC_16 \ ? 2 \ @@ -115,6 +115,28 @@ enum mpg123_enc_enum : 0 \ ) ) ) ) ) ) +/** Representation of zero in differing encodings. + * This exists to define proper silence in various encodings without + * having to link to libsyn123 to do actual conversions at runtime. + * You have to handle big/little endian order yourself, though. + * This takes the shortcut that any signed encoding has a zero with + * all-zero bits. Unsigned linear encodings just have the highest bit set + * (2^(n-1) for n bits), while the nonlinear 8-bit ones are special. + * \param enc the encoding (mpg123_enc_enum value) + * \param siz bytes per sample (return value of MPG123_SAMPLESIZE(enc)) + * \param off byte (octet) offset counted from LSB + * \return unsigned byte value for the designated octet + */ +#define MPG123_ZEROSAMPLE(enc, siz, off) ( \ + (enc) == MPG123_ENC_ULAW_8 \ + ? (off == 0 ? 0xff : 0x00) \ + : ( (enc) == MPG123_ENC_ALAW_8 \ + ? (off == 0 ? 0xd5 : 0x00) \ + : ( (((enc) & (MPG123_ENC_SIGNED|MPG123_ENC_FLOAT)) || (siz) != ((off)+1)) \ + ? 0x00 \ + : 0x80 \ + ) ) ) + /** Structure defining an audio format. * Providing the members as individual function arguments to define a certain * output format is easy enough. This struct makes is more comfortable to deal diff --git a/src/libout123/libout123.c b/src/libout123/libout123.c index 46cb7ae4..0adf17b3 100644 --- a/src/libout123/libout123.c +++ b/src/libout123/libout123.c @@ -95,6 +95,7 @@ out123_handle* attribute_align_arg out123_new(void) ao->channels = -1; ao->format = -1; ao->framesize = 0; + memset(ao->zerosample, 0, 8); ao->state = play_dead; ao->auxflags = 0; ao->preload = 0.; @@ -220,6 +221,12 @@ out123_param( out123_handle *ao, enum out123_parms code case OUT123_FLAGS: ao->flags = (int)value; break; + case OUT123_ADD_FLAGS: + ao->flags |= (int)value; + break; + case OUT123_REMOVE_FLAGS: + ao->flags &= ~((int)value); + break; case OUT123_PRELOAD: ao->preload = fvalue; break; @@ -279,6 +286,7 @@ out123_getparam( out123_handle *ao, enum out123_parms code switch(code) { case OUT123_FLAGS: + case OUT123_ADD_FLAGS: value = ao->flags; break; case OUT123_PRELOAD: @@ -528,7 +536,16 @@ out123_start(out123_handle *ao, long rate, int channels, int encoding) ao->rate = rate; ao->channels = channels; ao->format = encoding; - ao->framesize = out123_encsize(encoding)*channels; + int samplesize = out123_encsize(encoding); + ao->framesize = samplesize*channels; + // The most convoluted way to say nothing at all. + for(int i=0; izerosample[samplesize-1-i] = +#else + ao->zerosample[i] = +#endif + MPG123_ZEROSAMPLE(ao->format, samplesize, i); #ifndef NOXFERMEM if(have_buffer(ao)) @@ -617,6 +634,30 @@ void attribute_align_arg out123_stop(out123_handle *ao) ao->state = play_stopped; } +// Replace the data in a given block of audio data with zeroes +// in the correct encoding. +static void mute_block( unsigned char *bytes, int count +, unsigned char* zerosample, int samplesize ) +{ + // The count is expected to be a multiple of samplesize, + // this is just to ensure that the loop ends properly, should be noop. + count -= count % samplesize; + if(!count) + return; + // Initialize with one zero sample, then multiply that + // to eventually cover the whole buffer. + memcpy(bytes, zerosample, samplesize); + int offset = samplesize; + count -= samplesize; + while(count) + { + int block = offset > count ? count : offset; + memcpy(bytes+offset, bytes, block); + offset += block; + count -= block; + } +} + size_t attribute_align_arg out123_play(out123_handle *ao, void *bytes, size_t count) { @@ -649,25 +690,29 @@ out123_play(out123_handle *ao, void *bytes, size_t count) return buffer_write(ao, bytes, count); else #endif - do /* Playback in a loop to be able to continue after interruptions. */ { - errno = 0; - int block = count > INT_MAX ? INT_MAX : count; - written = ao->write(ao, (unsigned char*)bytes, block); - debug4( "written: %d errno: %i (%s), keep_on=%d" - , written, errno, strerror(errno) - , ao->flags & OUT123_KEEP_PLAYING ); - if(written > 0){ sum+=written; count -= written; } - if(written < block && errno != EINTR) + if(ao->flags & OUT123_MUTE) + mute_block( bytes, count, ao->zerosample + , MPG123_SAMPLESIZE(ao->format) ); + do /* Playback in a loop to be able to continue after interruptions. */ { - ao->errcode = OUT123_DEV_PLAY; - if(!AOQUIET) - error1("Error in writing audio (%s?)!", strerror(errno)); - /* This is a serious issue ending this playback round. */ - break; - } - } while(count && ao->flags & OUT123_KEEP_PLAYING); - + errno = 0; + int block = count > INT_MAX ? INT_MAX : count; + written = ao->write(ao, bytes, block); + debug4( "written: %d errno: %i (%s), keep_on=%d" + , written, errno, strerror(errno) + , ao->flags & OUT123_KEEP_PLAYING ); + if(written > 0){ sum+=written; count -= written; } + if(written < block && errno != EINTR) + { + ao->errcode = OUT123_DEV_PLAY; + if(!AOQUIET) + error1("Error in writing audio (%s?)!", strerror(errno)); + /* This is a serious issue ending this playback round. */ + break; + } + } while(count && ao->flags & OUT123_KEEP_PLAYING); + } debug3( "out123_play(%p, %p, ...) = %"SIZE_P , (void*)ao, bytes, (size_p)sum ); return sum; diff --git a/src/libout123/out123.h.in b/src/libout123/out123.h.in index 6931f7ab..e25c7bd5 100644 --- a/src/libout123/out123.h.in +++ b/src/libout123/out123.h.in @@ -117,6 +117,8 @@ enum out123_parms * (e.g. ../lib/mpg123 or ./plugins). The environment variable MPG123_MODDIR * is always tried first and the in-built installation path last. */ +, OUT123_ADD_FLAGS /**< enable given flags */ +, OUT123_REMOVE_FLAGS /**< disable diven flags */ }; /** Flags to tune out123 behaviour */ @@ -125,8 +127,8 @@ enum out123_flags OUT123_HEADPHONES = 0x01 /**< output to headphones (if supported) */ , OUT123_INTERNAL_SPEAKER = 0x02 /**< output to speaker (if supported) */ , OUT123_LINE_OUT = 0x04 /**< output to line out (if supported) */ -, OUT123_QUIET = 0x08 /**< no printouts to standard error */ -, OUT123_KEEP_PLAYING = 0x10 /**< +, OUT123_QUIET = 0x08 /**< no printouts to standard error */ +, OUT123_KEEP_PLAYING = 0x10 /**< * When this is set (default), playback continues in a loop when the device * does not consume all given data at once. This happens when encountering * signals (like SIGSTOP, SIGCONT) that cause interruption of the underlying @@ -136,6 +138,7 @@ enum out123_flags * over the data given to it via out123_play(), unless a communication error * arises. */ +, OUT123_MUTE = 0x20 /**< software mute (play silent audio) */ }; /** Read-only output driver/device property flags (OUT123_PROPFLAGS). */ @@ -510,7 +513,7 @@ void out123_stop(out123_handle *ao); * Also note that it is no accident that the buffer parameter is not marked * as constant. Some output drivers might need to do things like swap * byte order. This is done in-place instead of wasting memory on yet - * another copy. + * another copy. Software muting also overwrites the data. * \param ao handle * \param buffer pointer to raw audio data to be played * \param bytes number of bytes to read from the buffer diff --git a/src/libout123/out123_int.h b/src/libout123/out123_int.h index 9c5aeed1..7b58e1e8 100644 --- a/src/libout123/out123_int.h +++ b/src/libout123/out123_int.h @@ -83,6 +83,7 @@ struct out123_struct int channels; /* number of channels */ int format; /* encoding (TODO: rename this to "encoding"!) */ int framesize; /* Output needs data in chunks of framesize bytes. */ + char zerosample[8]; /* Zero in current encoding, max 64 bit. */ enum playstate state; /* ... */ int auxflags; /* For now just one: quiet mode (for probing). */ int propflags; /* Property flags, set by driver. */ diff --git a/src/term.c b/src/term.c index bdb28cc1..ded518b9 100644 --- a/src/term.c +++ b/src/term.c @@ -49,17 +49,18 @@ struct keydef term_help[] = ,{ MPG123_FINE_REWIND_KEY, 0, "fine rewind" } ,{ MPG123_VOL_UP_KEY, 0, "volume up" } ,{ MPG123_VOL_DOWN_KEY, 0, "volume down" } + ,{ MPG123_VOL_MUTE_KEY, 0, "(un)mute volume" } ,{ MPG123_RVA_KEY, 0, "RVA switch" } ,{ MPG123_VERBOSE_KEY, 0, "verbose switch" } ,{ MPG123_PLAYLIST_KEY, 0, "list current playlist, indicating current track there" } ,{ MPG123_TAG_KEY, 0, "display tag info (again)" } ,{ MPG123_MPEG_KEY, 0, "print MPEG header info (again)" } - ,{ MPG123_HELP_KEY, 0, "this help" } - ,{ MPG123_QUIT_KEY, 0, "quit" } ,{ MPG123_PITCH_UP_KEY, MPG123_PITCH_BUP_KEY, "pitch up (small step, big step)" } ,{ MPG123_PITCH_DOWN_KEY, MPG123_PITCH_BDOWN_KEY, "pitch down (small step, big step)" } ,{ MPG123_PITCH_ZERO_KEY, 0, "reset pitch to zero" } ,{ MPG123_BOOKMARK_KEY, 0, "print out current position in playlist and track, for the benefit of some external tool to store bookmarks" } + ,{ MPG123_HELP_KEY, 0, "this help" } + ,{ MPG123_QUIT_KEY, 0, "quit" } }; void term_sigcont(int sig); @@ -386,6 +387,9 @@ static void term_handle_key(mpg123_handle *fr, out123_handle *ao, char val) case MPG123_VOL_DOWN_KEY: mpg123_volume_change(fr, -0.02); break; + case MPG123_VOL_MUTE_KEY: + set_mute(ao, muted=!muted); + break; case MPG123_PITCH_UP_KEY: case MPG123_PITCH_BUP_KEY: case MPG123_PITCH_DOWN_KEY: diff --git a/src/term.h b/src/term.h index 53d13f12..5e8e7548 100644 --- a/src/term.h +++ b/src/term.h @@ -46,6 +46,7 @@ #define MPG123_VOL_UP_KEY '+' #define MPG123_VOL_DOWN_KEY '-' +#define MPG123_VOL_MUTE_KEY 'u' #define MPG123_VERBOSE_KEY 'v' #define MPG123_RVA_KEY 'r' #define MPG123_PLAYLIST_KEY 'l'