mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-02-12 04:28:02 -05:00
pw-cat: add a container option and some --list options
Add a container option to override the extension check and force a container when saving. Add some more formats that are supported by libsndfile. Add some options to list all supported formats, extensions/containers, layouts and channel names. Fixes #5117
This commit is contained in:
parent
0470f96887
commit
a2df282086
2 changed files with 188 additions and 29 deletions
|
|
@ -24,9 +24,9 @@ Play and record media with PipeWire
|
|||
|
||||
**pw-cat** is a simple tool for playing back or capturing raw or encoded
|
||||
media files on a PipeWire server. It understands all audio file formats
|
||||
supported by `libsndfile` for PCM capture and playback. When capturing
|
||||
PCM, the filename extension is used to guess the file format with the
|
||||
WAV file format as the default.
|
||||
supported by `libsndfile` for PCM capture and playback. When no container
|
||||
is specified for capturing PCM, the filename extension is used to guess
|
||||
the file format with the WAV file format as the default.
|
||||
|
||||
It understands standard MIDI files and MIDI 2.0 clip files for playback
|
||||
and recording. This tool will not render MIDI files, it will simply make
|
||||
|
|
@ -37,8 +37,15 @@ DSD playback is supported with the DSF file format. This tool will only
|
|||
work with native DSD capable hardware and will produce an error when no
|
||||
such hardware was found.
|
||||
|
||||
When the *FILE* is - input and output will be raw data from STDIN and
|
||||
STDOUT respectively.
|
||||
When the *FILE* is - input will be from STDIN. If no format is specified,
|
||||
libsndfile will attempt to parse and stream the format from STDIN. For
|
||||
some formats, this is not possible and libsndfile will give an error.
|
||||
Raw, MIDI and DSD formats are all streamable from STDIN.
|
||||
|
||||
When the *FILE* is - output will be to STDOUT. If no format is specified,
|
||||
libsndfile is instructed to output the .au format, which is streamble and
|
||||
preserves the format, rate and channels.
|
||||
Raw and DSD formats are all streamable to STDOUT.
|
||||
|
||||
# OPTIONS
|
||||
|
||||
|
|
@ -87,6 +94,11 @@ DSD mode. *FILE* is a DSF file. If the tool is called under the name
|
|||
render the DSD audio. You need a DSD capable device to play DSD content
|
||||
or this program will exit with an error.
|
||||
|
||||
\par -s | \--sysex
|
||||
SysEx mode. *FILE* is a File that contains a raw SysEx MIDI message.
|
||||
If the tool is called under the name **pw-sysex** this is the default.
|
||||
The File is read and sent as a MIDI control message into the graph.
|
||||
|
||||
\par \--media-type=VALUE
|
||||
Set the media type property (default Audio/Midi depending on mode). The
|
||||
media type is used by the session manager to select a suitable target to
|
||||
|
|
@ -138,6 +150,17 @@ does not match the samplerate of the server, the data will be resampled.
|
|||
Higher quality uses more CPU. Values between 0 and 15 are allowed, the
|
||||
default quality is 4.
|
||||
|
||||
\par -a | \--raw
|
||||
Raw samples will be read or written. The \--rate, \--format, \--channels
|
||||
and \--channelmap can be used to specify the raw format.
|
||||
|
||||
\par -M | \--force-midi
|
||||
Force midi format, one of "midi" or "ump", (default ump).
|
||||
When reading or writing midi, for one of midi or UMP.
|
||||
|
||||
\par -n | \--sample-count=COUNT
|
||||
Stop after COUNT samples.
|
||||
|
||||
\par \--rate=VALUE
|
||||
The sample rate, default 48000.
|
||||
|
||||
|
|
@ -145,19 +168,38 @@ The sample rate, default 48000.
|
|||
The number of channels, default 2.
|
||||
|
||||
\par \--channel-map=VALUE
|
||||
The channelmap. Possible values include: **mono**, **stereo**,
|
||||
The channelmap. Possible values include are either a channel layout
|
||||
such as **mono**, **stereo**,
|
||||
**surround-21**, **quad**, **surround-22**, **surround-40**,
|
||||
**surround-31**, **surround-41**, **surround-50**, **surround-51**,
|
||||
**surround-51r**, **surround-70**, **surround-71** or a comma separated
|
||||
list of channel names: **FL**, **FR**, **FC**, **LFE**, **SL**, **SR**,
|
||||
**FLC**, **FRC**, **RC**, **RL**, **RR**, **TC**, **TFL**, **TFC**,
|
||||
**TFR**, **TRL**, **TRC**, **TRR**, **RLC**, **RRC**, **FLW**, **FRW**,
|
||||
**LFE2**, **FLH**, **FCH**, **FRH**, **TFLC**, **TFRC**, **TSL**,
|
||||
**TSR**, **LLFR**, **RLFE**, **BC**, **BLC**, **BRC**
|
||||
or comma separated array of channel names such as **FL,FR**.
|
||||
See \--list-layouts and \--list-channel-names to get a complete
|
||||
list of possible values.
|
||||
|
||||
\par \--list-layouts
|
||||
List all known channel layouts. One of these can be used as the
|
||||
\--channel-map value.
|
||||
|
||||
\par \--list-channel-names
|
||||
List all known channel names. An array of these can be used as the
|
||||
\--channel-map value.
|
||||
|
||||
\par \--format=VALUE
|
||||
The sample format to use. One of: **u8**, **s8**, **s16** (default),
|
||||
**s24**, **s32**, **f32**, **f64**.
|
||||
The sample format to use. Some possible values include: **u8**, **s8**,
|
||||
**s16** (default), **s24**, **s32**, **f32**, **f64**. See
|
||||
\--list-formats to get a complete list of values.
|
||||
|
||||
\par \--list-formats
|
||||
List all known format values.
|
||||
|
||||
\par \--container=VALUE
|
||||
Specify the container to use when saving. This is usually guessed from
|
||||
the filename extension but can be specified explicitly. When using
|
||||
STDOUT and no container is specified, the AU container will be used.
|
||||
Then using a filename and the container was not specified and it could
|
||||
not be derived from the filename, the WAV container is used.
|
||||
|
||||
\par \--list-containers
|
||||
List all known container values.
|
||||
|
||||
\par \--volume=VALUE
|
||||
The stream volume, default 1.000. Depending on the locale you have
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ struct data {
|
|||
const char *media_role;
|
||||
const char *channel_map;
|
||||
const char *format;
|
||||
const char *container;
|
||||
const char *target;
|
||||
const char *latency;
|
||||
struct pw_properties *props;
|
||||
|
|
@ -194,8 +195,6 @@ struct data {
|
|||
uint64_t samples_processed;
|
||||
};
|
||||
|
||||
#define STR_FMTS "(ulaw|alaw|u8|s8|s16|s32|f32|f64)"
|
||||
|
||||
static const struct format_info {
|
||||
const char *name;
|
||||
int sf_format;
|
||||
|
|
@ -217,6 +216,29 @@ static const struct format_info {
|
|||
{ "mp3", SF_FORMAT_MPEG_LAYER_III, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "vorbis", SF_FORMAT_VORBIS, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "opus", SF_FORMAT_OPUS, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
|
||||
{ "ima-adpcm", SF_FORMAT_IMA_ADPCM, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "ms-adpcm", SF_FORMAT_MS_ADPCM, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "nms-adpcm-16", SF_FORMAT_NMS_ADPCM_16, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "nms-adpcm-24", SF_FORMAT_NMS_ADPCM_24, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "nms-adpcm-32", SF_FORMAT_NMS_ADPCM_32, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
|
||||
{ "alac-16", SF_FORMAT_ALAC_16, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "alac-20", SF_FORMAT_ALAC_20, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "alac-24", SF_FORMAT_ALAC_24, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "alac-32", SF_FORMAT_ALAC_32, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
|
||||
{ "gsm610", SF_FORMAT_GSM610, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "g721-32", SF_FORMAT_G721_32, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "g723-24", SF_FORMAT_G723_24, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "g723-40", SF_FORMAT_G723_40, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "dwvw-12", SF_FORMAT_DWVW_12, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "dwvw-16", SF_FORMAT_DWVW_16, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "dwvw-24", SF_FORMAT_DWVW_24, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "vox", SF_FORMAT_VOX_ADPCM, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "dpcm-16", SF_FORMAT_DPCM_16, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
{ "dpcm-8", SF_FORMAT_DPCM_8, SPA_AUDIO_FORMAT_F32, 1 },
|
||||
|
||||
};
|
||||
|
||||
static const struct format_info *format_info_by_name(const char *str)
|
||||
|
|
@ -236,6 +258,14 @@ static const struct format_info *format_info_by_sf_format(int format)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static void list_formats(struct data *d)
|
||||
{
|
||||
|
||||
fprintf(stdout, _("Supported formats:\n"));
|
||||
SPA_FOR_EACH_ELEMENT_VAR(format_info, i)
|
||||
fprintf(stdout, " %s\n", i->name);
|
||||
}
|
||||
|
||||
static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames, bool *null_frame)
|
||||
{
|
||||
sf_count_t rn;
|
||||
|
|
@ -714,6 +744,34 @@ static int parse_channelmap(const char *channel_map, struct spa_audio_layout_inf
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void list_layouts(struct data *d)
|
||||
{
|
||||
fprintf(stderr, _("Supported channel layouts:\n"));
|
||||
SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_layout_info, i) {
|
||||
if (i->name == NULL)
|
||||
break;
|
||||
fprintf(stdout, " %s: [", i->name);
|
||||
for (uint32_t j = 0; j < i->layout.n_channels; j++)
|
||||
fprintf(stdout, "%s%s", j == 0 ? " " : ", ",
|
||||
spa_type_audio_channel_to_short_name(i->layout.position[j]));
|
||||
fprintf(stdout, " ]\n");
|
||||
}
|
||||
fprintf(stderr, _("Supported channel layout aliases:\n"));
|
||||
SPA_FOR_EACH_ELEMENT_VAR(maps, m)
|
||||
fprintf(stdout, _(" %s -> %s\n"), m->name, m->alias);
|
||||
}
|
||||
|
||||
static void list_channel_names(struct data *d)
|
||||
{
|
||||
fprintf(stderr, _("Supported channel names:\n"));
|
||||
SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_channel, i) {
|
||||
if (i->name == NULL || SPA_AUDIO_CHANNEL_IS_AUX(i->type))
|
||||
break;
|
||||
fprintf(stdout, " %s\n", spa_type_short_name(i->name));
|
||||
}
|
||||
fprintf(stderr, " AUX0 ... AUX4095\n");
|
||||
}
|
||||
|
||||
static int channelmap_default(struct spa_audio_layout_info *map, int n_channels)
|
||||
{
|
||||
switch(n_channels) {
|
||||
|
|
@ -1054,6 +1112,11 @@ enum {
|
|||
OPT_CHANNELMAP,
|
||||
OPT_FORMAT,
|
||||
OPT_VOLUME,
|
||||
OPT_CONTAINER,
|
||||
OPT_LISTFORMATS,
|
||||
OPT_LISTCONTAINERS,
|
||||
OPT_LISTLAYOUTS,
|
||||
OPT_LISTCHANNELNAMES,
|
||||
};
|
||||
|
||||
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||
|
|
@ -1088,13 +1151,18 @@ static const struct option long_options[] = {
|
|||
{ "rate", required_argument, NULL, OPT_RATE },
|
||||
{ "channels", required_argument, NULL, OPT_CHANNELS },
|
||||
{ "channel-map", required_argument, NULL, OPT_CHANNELMAP },
|
||||
{ "list-layouts", no_argument, NULL, OPT_LISTLAYOUTS },
|
||||
{ "list-channel-names", no_argument, NULL, OPT_LISTCHANNELNAMES },
|
||||
{ "format", required_argument, NULL, OPT_FORMAT },
|
||||
{ "list-formats", no_argument, NULL, OPT_LISTFORMATS },
|
||||
{ "container", required_argument, NULL, OPT_CONTAINER },
|
||||
{ "list-containers", no_argument, NULL, OPT_LISTCONTAINERS },
|
||||
{ "volume", required_argument, NULL, OPT_VOLUME },
|
||||
{ "quality", required_argument, NULL, 'q' },
|
||||
{ "raw", no_argument, NULL, 'a' },
|
||||
{ "raw", no_argument, NULL, 'a' },
|
||||
{ "force-midi", required_argument, NULL, 'M' },
|
||||
{ "sample-count", required_argument, NULL, 'n' },
|
||||
{ "midi-clip", no_argument, NULL, 'c' },
|
||||
{ "midi-clip", no_argument, NULL, 'c' },
|
||||
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
|
@ -1131,12 +1199,17 @@ static void show_usage(const char *name, bool is_error)
|
|||
DEFAULT_TARGET, DEFAULT_LATENCY_PLAY);
|
||||
|
||||
fprintf(fp,
|
||||
_(" --rate Sample rate (req. for rec) (default %u)\n"
|
||||
" --channels Number of channels (req. for rec) (default %u)\n"
|
||||
_(" --rate Sample rate (default %u)\n"
|
||||
" --channels Number of channels (default %u)\n"
|
||||
" --channel-map Channel map\n"
|
||||
" one of: \"Stereo\", \"5.1\",... or\n"
|
||||
" a channel layout: \"Stereo\", \"5.1\",... or\n"
|
||||
" comma separated list of channel names: eg. \"FL,FR\"\n"
|
||||
" --format Sample format %s (req. for rec) (default %s)\n"
|
||||
" --list-layouts List supported channel layouts\n"
|
||||
" --list-channel-names List supported channel maps\n"
|
||||
" --format Sample format (default %s)\n"
|
||||
" --list-formats List supported sample formats\n"
|
||||
" --container Container format\n"
|
||||
" --list-containers List supported containers and extensions\n"
|
||||
" --volume Stream volume 0-1.0 (default %.3f)\n"
|
||||
" -q --quality Resampler quality (0 - 15) (default %d)\n"
|
||||
" -a, --raw RAW mode\n"
|
||||
|
|
@ -1145,7 +1218,7 @@ static void show_usage(const char *name, bool is_error)
|
|||
"\n"),
|
||||
DEFAULT_RATE,
|
||||
DEFAULT_CHANNELS,
|
||||
STR_FMTS, DEFAULT_FORMAT,
|
||||
DEFAULT_FORMAT,
|
||||
DEFAULT_VOLUME,
|
||||
DEFAULT_QUALITY);
|
||||
|
||||
|
|
@ -1679,11 +1752,20 @@ static int fill_properties(struct data *data)
|
|||
|
||||
return 0;
|
||||
}
|
||||
static void format_from_filename(SF_INFO *info, const char *filename)
|
||||
static void format_from_filename(SF_INFO *info, const char *filename, const char *container)
|
||||
{
|
||||
int i, count = 0;
|
||||
int format = -1;
|
||||
const char *extension;
|
||||
|
||||
if (spa_streq(filename, "-"))
|
||||
extension = container ? container : "au";
|
||||
else if (container)
|
||||
extension = container;
|
||||
else
|
||||
extension = filename;
|
||||
|
||||
fprintf(stderr, "%s\n", filename);
|
||||
if (sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) != 0)
|
||||
count = 0;
|
||||
|
||||
|
|
@ -1695,7 +1777,7 @@ static void format_from_filename(SF_INFO *info, const char *filename)
|
|||
if (sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) != 0)
|
||||
continue;
|
||||
|
||||
if (spa_strendswith(filename, fi.extension)) {
|
||||
if (spa_strendswith(extension, fi.extension)) {
|
||||
format = fi.format;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1712,7 +1794,7 @@ static void format_from_filename(SF_INFO *info, const char *filename)
|
|||
if (sf_command(NULL, SFC_GET_SIMPLE_FORMAT, &fi, sizeof(fi)) != 0)
|
||||
continue;
|
||||
|
||||
if (spa_strendswith(filename, fi.extension)) {
|
||||
if (spa_strendswith(extension, fi.extension)) {
|
||||
format = fi.format;
|
||||
info->format = 0;
|
||||
break;
|
||||
|
|
@ -1720,7 +1802,7 @@ static void format_from_filename(SF_INFO *info, const char *filename)
|
|||
}
|
||||
}
|
||||
if (format == -1)
|
||||
format = spa_streq(filename, "-") ? SF_FORMAT_AU : SF_FORMAT_WAV;
|
||||
format = SF_FORMAT_WAV;
|
||||
if (format == SF_FORMAT_WAV && info->channels > 2)
|
||||
format = SF_FORMAT_WAVEX;
|
||||
|
||||
|
|
@ -1738,6 +1820,26 @@ static void format_from_filename(SF_INFO *info, const char *filename)
|
|||
info->format |= format;
|
||||
}
|
||||
|
||||
static void list_containers(struct data *d)
|
||||
{
|
||||
int i, count = 0;
|
||||
|
||||
fprintf(stderr, _("Supported containers and extensions:\n"));
|
||||
if (sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) != 0)
|
||||
count = 0;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
SF_FORMAT_INFO fi;
|
||||
|
||||
spa_zero(fi);
|
||||
fi.format = i;
|
||||
if (sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) != 0)
|
||||
continue;
|
||||
|
||||
fprintf(stderr, " %s: %s\n", fi.extension, fi.name);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||
static int setup_encodedfile(struct data *data)
|
||||
{
|
||||
|
|
@ -1859,7 +1961,7 @@ static int setup_sndfile(struct data *data)
|
|||
info.samplerate = data->rate;
|
||||
info.channels = data->channels;
|
||||
info.format = fi->sf_format;
|
||||
format_from_filename(&info, data->filename);
|
||||
format_from_filename(&info, data->filename, data->container);
|
||||
}
|
||||
|
||||
data->sndfile.file = sf_open(data->filename,
|
||||
|
|
@ -2220,6 +2322,9 @@ int main(int argc, char *argv[])
|
|||
case OPT_FORMAT:
|
||||
data.format = optarg;
|
||||
break;
|
||||
case OPT_CONTAINER:
|
||||
data.container = optarg;
|
||||
break;
|
||||
|
||||
case OPT_VOLUME:
|
||||
if (!spa_atof(optarg, &data.volume))
|
||||
|
|
@ -2231,6 +2336,18 @@ int main(int argc, char *argv[])
|
|||
case 'c':
|
||||
data.data_type = TYPE_MIDI2;
|
||||
break;
|
||||
case OPT_LISTFORMATS:
|
||||
list_formats(&data);
|
||||
return EXIT_SUCCESS;
|
||||
case OPT_LISTCONTAINERS:
|
||||
list_containers(&data);
|
||||
return EXIT_SUCCESS;
|
||||
case OPT_LISTLAYOUTS:
|
||||
list_layouts(&data);
|
||||
return EXIT_SUCCESS;
|
||||
case OPT_LISTCHANNELNAMES:
|
||||
list_channel_names(&data);
|
||||
return EXIT_SUCCESS;
|
||||
default:
|
||||
goto error_usage;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue