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'