Skip to content

Commit

Permalink
mpg123, libout123, fmt123: Make a very special friend happy, introduc…
Browse files Browse the repository at this point in the history
…e 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
  • Loading branch information
thor committed Jan 31, 2020
1 parent 737a128 commit dabbc01
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 36 deletions.
12 changes: 9 additions & 3 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
20 changes: 18 additions & 2 deletions doc/README.remote
Original file line number Diff line number Diff line change
Expand Up @@ -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 <trackname>: load and start playing resource <trackname>
Expand All @@ -38,6 +39,10 @@ JUMP/J <frame>|<+offset>|<-offset>|<[+|-]seconds>s: jump to mpeg frame <frame> o

VOLUME/V <percent>: 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 <channel> <band> <value>: set equalizer value for frequency band 0 to 31 on channel 1 (left) or 2 (right) or 3 (both)
Expand All @@ -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 <bass> <mid> <treble>: 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.<NAME>[ [lang(<LANG>)] desc(<description>)]:

The lines of data follow with "=" prefixed:

@T =<one line of content in UTF-8 encoding>

meaning of the @S stream info:

S <mpeg-version> <layer> <sampling freq> <mode(stereo/mono/...)> <mode_ext> <framesize> <stereo> <copyright> <error_protected> <emphasis> <bitrate> <extension> <vbr(0/1=yes/no)>

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:<number>" and "@I ID3.track:<number>".
Expand Down
6 changes: 4 additions & 2 deletions man1/mpg123.1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
2 changes: 2 additions & 0 deletions src/audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

8 changes: 5 additions & 3 deletions src/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 2 additions & 1 deletion src/common.h
Original file line number Diff line number Diff line change
@@ -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
*/
Expand All @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions src/control_generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -594,6 +606,8 @@ int control_generic (mpg123_handle *fr)
generic_sendmsg("H STOP/S: stop playback (closes file)");
generic_sendmsg("H JUMP/J <frame>|<+offset>|<-offset>|<[+|-]seconds>s: jump to mpeg frame <frame> or change position by offset, same in seconds if number followed by \"s\"");
generic_sendmsg("H VOLUME/V <percent>: 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 <channel> <band> <value>: 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 <filename>: load EQ settings from a file");
Expand Down
26 changes: 24 additions & 2 deletions src/libmpg123/fmt123.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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
Expand Down
81 changes: 63 additions & 18 deletions src/libout123/libout123.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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; i<samplesize; ++i)
#ifdef WORDS_BIGENDIAN
ao->zerosample[samplesize-1-i] =
#else
ao->zerosample[i] =
#endif
MPG123_ZEROSAMPLE(ao->format, samplesize, i);

#ifndef NOXFERMEM
if(have_buffer(ao))
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 6 additions & 3 deletions src/libout123/out123.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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
Expand All @@ -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). */
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit dabbc01

Please sign in to comment.