pw-play: move dsdplay into pw-cat

This commit is contained in:
Wim Taymans 2021-09-21 12:29:41 +02:00
parent 0ec77ab09c
commit d8aec1c7b4
3 changed files with 146 additions and 283 deletions

View file

@ -5,7 +5,6 @@ tools_sources = [
[ 'pw-dump', [ 'pw-dump.c' ] ],
[ 'pw-profiler', [ 'pw-profiler.c' ] ],
[ 'pw-mididump', [ 'pw-mididump.c', 'midifile.c' ] ],
[ 'pw-dsdplay', [ 'pw-dsdplay.c', 'dsffile.c' ] ],
[ 'pw-metadata', [ 'pw-metadata.c' ] ],
[ 'pw-loopback', [ 'pw-loopback.c' ] ],
[ 'pw-link', [ 'pw-link.c' ] ],
@ -42,6 +41,7 @@ if not get_option('pw-cat').disabled() and sndfile_dep.found()
'pw-record',
'pw-midiplay',
'pw-midirecord',
'pw-dsdplay',
]
executable('pw-cat',

View file

@ -52,6 +52,7 @@
#include <pipewire/extensions/metadata.h>
#include "midifile.h"
#include "dsffile.h"
#define DEFAULT_MEDIA_TYPE "Audio"
#define DEFAULT_MIDI_MEDIA_TYPE "Midi"
@ -116,7 +117,10 @@ struct data {
enum mode mode;
bool verbose;
bool is_midi;
#define TYPE_PCM 0
#define TYPE_MIDI 1
#define TYPE_DSD 2
int data_type;
const char *remote_name;
const char *media_type;
const char *media_category;
@ -161,6 +165,11 @@ struct data {
struct midi_file *file;
struct midi_file_info info;
} midi;
struct {
struct dsf_file *file;
struct dsf_file_info info;
struct dsf_layout layout;
} dsf;
};
static inline int
@ -851,7 +860,8 @@ on_io_changed(void *userdata, uint32_t id, void *data, uint32_t size)
d->position = data;
break;
case SPA_IO_RateMatch:
d->rate_match = data;
if (d->data_type == TYPE_PCM)
d->rate_match = data;
break;
default:
break;
@ -862,8 +872,37 @@ static void
on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param)
{
struct data *data = userdata;
struct spa_audio_info info = { 0 };
int err;
if (data->verbose)
printf("stream param change: id=%"PRIu32"\n", id);
if (id != SPA_PARAM_Format || param == NULL)
return;
if ((err = spa_format_parse(param, &info.media_type, &info.media_subtype)) < 0)
return;
if (info.media_type != SPA_MEDIA_TYPE_audio ||
info.media_subtype != SPA_MEDIA_SUBTYPE_dsd)
return;
if (spa_format_audio_dsd_parse(param, &info.info.dsd) < 0)
return;
data->dsf.layout.interleave = info.info.dsd.interleave,
data->dsf.layout.channels = info.info.dsd.channels;
data->dsf.layout.lsb = info.info.dsd.bitorder == SPA_PARAM_BITORDER_lsb;
data->stride = data->dsf.info.channels * SPA_ABS(data->dsf.layout.interleave);
if (data->verbose) {
printf("DSD out: channels:%d bitorder:%s interleave:%d\n",
data->dsf.layout.channels,
data->dsf.layout.lsb ? "lsb" : "msb",
data->dsf.layout.interleave);
}
}
static void on_process(void *userdata)
@ -1058,6 +1097,7 @@ static void show_usage(const char *name, bool is_error)
_(" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
" -m, --midi Midi mode\n"
" -d, --dsd DSD mode\n"
"\n"), fp);
}
}
@ -1177,6 +1217,47 @@ static int setup_midifile(struct data *data)
return 0;
}
struct dsd_layout_info {
uint32_t type;
struct spa_audio_layout_info info;
};
static const struct dsd_layout_info dsd_layouts[] = {
{ 1, { SPA_AUDIO_LAYOUT_Mono, }, },
{ 2, { SPA_AUDIO_LAYOUT_Stereo, }, },
{ 3, { SPA_AUDIO_LAYOUT_2FC }, },
{ 4, { SPA_AUDIO_LAYOUT_Quad }, },
{ 5, { SPA_AUDIO_LAYOUT_3_1 }, },
{ 6, { SPA_AUDIO_LAYOUT_5_0R }, },
{ 7, { SPA_AUDIO_LAYOUT_5_1R }, },
};
static int dsf_play(struct data *d, void *src, unsigned int n_frames)
{
return dsf_file_read(d->dsf.file, src, n_frames, &d->dsf.layout);
}
static int setup_dsffile(struct data *data)
{
if (data->mode == mode_record)
return -ENOTSUP;
data->dsf.file = dsf_file_open(data->filename, "r", &data->dsf.info);
if (data->dsf.file == NULL) {
fprintf(stderr, "error: can't read dsf file '%s': %m\n", data->filename);
return -errno;
}
if (data->verbose)
printf("opened file \"%s\" channels:%d rate:%d bitorder:%s\n",
data->filename,
data->dsf.info.channels, data->dsf.info.rate,
data->dsf.info.lsb ? "lsb" : "msb");
data->fill = dsf_play;
;
return 0;
}
static int fill_properties(struct data *data)
{
static const char * const table[] = {
@ -1398,16 +1479,21 @@ int main(int argc, char *argv[])
prog = argv[0];
/* prime the mode from the program name */
if (spa_streq(prog, "pw-play"))
if (spa_streq(prog, "pw-play")) {
data.mode = mode_playback;
else if (spa_streq(prog, "pw-record"))
data.data_type = TYPE_PCM;
} else if (spa_streq(prog, "pw-record")) {
data.mode = mode_record;
else if (spa_streq(prog, "pw-midiplay")) {
data.data_type = TYPE_PCM;
} else if (spa_streq(prog, "pw-midiplay")) {
data.mode = mode_playback;
data.is_midi = true;
data.data_type = TYPE_MIDI;
} else if (spa_streq(prog, "pw-midirecord")) {
data.mode = mode_record;
data.is_midi = true;
data.data_type = TYPE_MIDI;
} else if (spa_streq(prog, "pw-dsdplay")) {
data.mode = mode_playback;
data.data_type = TYPE_DSD;
} else
data.mode = mode_none;
@ -1418,7 +1504,7 @@ int main(int argc, char *argv[])
/* initialize list every time */
spa_list_init(&data.targets);
while ((c = getopt_long(argc, argv, "hvprmR:q:", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "hvprmdR:q:", long_options, NULL)) != -1) {
switch (c) {
@ -1448,7 +1534,11 @@ int main(int argc, char *argv[])
break;
case 'm':
data.is_midi = true;
data.data_type = TYPE_MIDI;
break;
case 'd':
data.data_type = TYPE_DSD;
break;
case 'R':
@ -1538,10 +1628,14 @@ int main(int argc, char *argv[])
}
if (!data.media_type) {
if (data.is_midi)
switch (data.data_type) {
case TYPE_MIDI:
data.media_type = DEFAULT_MIDI_MEDIA_TYPE;
else
break;
default:
data.media_type = DEFAULT_MEDIA_TYPE;
break;
}
}
if (!data.media_category)
data.media_category = data.mode == mode_playback ?
@ -1637,12 +1731,21 @@ int main(int argc, char *argv[])
data.sync = pw_core_sync(data.core, 0, data.sync);
if (!data.list_targets) {
struct spa_audio_info_raw info;
if (data.is_midi)
ret = setup_midifile(&data);
else
switch (data.data_type) {
case TYPE_PCM:
ret = setup_sndfile(&data);
break;
case TYPE_MIDI:
ret = setup_midifile(&data);
break;
case TYPE_DSD:
ret = setup_dsffile(&data);
break;
default:
ret = -ENOTSUP;
break;
}
if (ret < 0) {
fprintf(stderr, "error: open failed: %s\n", spa_strerror(ret));
@ -1654,8 +1757,10 @@ int main(int argc, char *argv[])
goto error_usage;
}
}
if (!data.is_midi) {
switch (data.data_type) {
case TYPE_PCM:
{
struct spa_audio_info_raw info;
info = SPA_AUDIO_INFO_RAW_INIT(
.flags = data.channelmap.n_channels ? 0 : SPA_AUDIO_FLAG_UNPOSITIONED,
.format = data.spa_format,
@ -1666,13 +1771,35 @@ int main(int argc, char *argv[])
memcpy(info.position, data.channelmap.channels, data.channels * sizeof(int));
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
} else {
break;
}
case TYPE_MIDI:
params[0] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "8 bit raw midi");
break;
case TYPE_DSD:
{
struct spa_audio_info_dsd info;
size_t i;
spa_zero(info);
info.channels = data.dsf.info.channels;
info.rate = data.dsf.info.rate / 8;
for (i = 0; i < SPA_N_ELEMENTS(dsd_layouts); i++) {
if (dsd_layouts[i].type != data.dsf.info.channel_type)
continue;
info.channels = dsd_layouts[i].info.n_channels;
memcpy(info.position, dsd_layouts[i].info.position,
info.channels * sizeof(uint32_t));
}
params[0] = spa_format_audio_dsd_build(&b, SPA_PARAM_EnumFormat, &info);
break;
}
}
data.stream = pw_stream_new(data.core, prog, data.props);

View file

@ -1,264 +0,0 @@
/* PipeWire
*
* Copyright © 2021 Wim Taymans
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include <spa/param/audio/format-utils.h>
#include <spa/param/audio/layout.h>
#include <pipewire/pipewire.h>
#include "dsffile.h"
struct data {
struct pw_main_loop *loop;
struct pw_stream *stream;
const char *opt_filename;
const char *opt_remote;
struct dsf_file *f;
struct dsf_file_info info;
struct dsf_layout layout;
};
static void on_process(void *userdata)
{
struct data *data = userdata;
struct pw_buffer *b;
struct spa_buffer *buf;
ssize_t stride;
uint32_t samples;
uint8_t *d;
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
pw_log_warn("out of buffers: %m");
return;
}
buf = b->buffer;
if ((d = buf->datas[0].data) == NULL)
return;
stride = data->info.channels * SPA_ABS(data->layout.interleave);
samples = dsf_file_read(data->f, d, buf->datas[0].maxsize / stride,
&data->layout);
buf->datas[0].chunk->offset = 0;
buf->datas[0].chunk->stride = stride;
buf->datas[0].chunk->size = samples * stride;
pw_stream_queue_buffer(data->stream, b);
}
static void
on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param)
{
struct data *data = userdata;
struct spa_audio_info info = { 0 };
int err;
if (id != SPA_PARAM_Format || param == NULL)
return;
if ((err = spa_format_parse(param, &info.media_type, &info.media_subtype)) < 0)
return;
if (info.media_type != SPA_MEDIA_TYPE_audio ||
info.media_subtype != SPA_MEDIA_SUBTYPE_dsd)
return;
if (spa_format_audio_dsd_parse(param, &info.info.dsd) < 0)
return;
data->layout.interleave = info.info.dsd.interleave,
data->layout.channels = info.info.dsd.channels;
data->layout.lsb = info.info.dsd.bitorder == SPA_PARAM_BITORDER_lsb;
fprintf(stderr, "output:\n");
fprintf(stderr, " bitorder: %s\n", data->layout.lsb ? "lsb" : "msb");
fprintf(stderr, " channels: %u\n", data->layout.channels);
fprintf(stderr, " interleave: %d\n", data->layout.interleave);
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.param_changed = on_param_changed,
.process = on_process,
};
static void do_quit(void *userdata, int signal_number)
{
struct data *data = userdata;
pw_main_loop_quit(data->loop);
}
struct layout_info {
uint32_t type;
struct spa_audio_layout_info info;
};
static const struct layout_info layouts[] = {
{ 1, { SPA_AUDIO_LAYOUT_Mono, }, },
{ 2, { SPA_AUDIO_LAYOUT_Stereo, }, },
{ 3, { SPA_AUDIO_LAYOUT_2FC }, },
{ 4, { SPA_AUDIO_LAYOUT_Quad }, },
{ 5, { SPA_AUDIO_LAYOUT_3_1 }, },
{ 6, { SPA_AUDIO_LAYOUT_5_0R }, },
{ 7, { SPA_AUDIO_LAYOUT_5_1R }, },
};
int handle_dsd_playback(struct data *data)
{
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
struct spa_audio_info_dsd info;
size_t i;
data->loop = pw_main_loop_new(NULL);
pw_loop_add_signal(pw_main_loop_get_loop(data->loop), SIGINT, do_quit, data);
pw_loop_add_signal(pw_main_loop_get_loop(data->loop), SIGTERM, do_quit, data);
data->stream = pw_stream_new_simple(
pw_main_loop_get_loop(data->loop),
"audio-src",
pw_properties_new(
PW_KEY_REMOTE_NAME, data->opt_remote,
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Playback",
PW_KEY_MEDIA_ROLE, "Music",
NULL),
&stream_events,
data);
spa_zero(info);
info.channels = data->info.channels;
info.rate = data->info.rate / 8;
for (i = 0; i < SPA_N_ELEMENTS(layouts); i++) {
if (layouts[i].type != data->info.channel_type)
continue;
info.channels = layouts[i].info.n_channels;
memcpy(info.position, layouts[i].info.position,
info.channels * sizeof(uint32_t));
}
params[0] = spa_format_audio_dsd_build(&b, SPA_PARAM_EnumFormat, &info);
/* Now connect this stream. We ask that our process function is
* called in a realtime thread. */
pw_stream_connect(data->stream,
PW_DIRECTION_OUTPUT,
PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS,
params, 1);
/* and wait while we let things run */
pw_main_loop_run(data->loop);
pw_stream_destroy(data->stream);
pw_main_loop_destroy(data->loop);
return 0;
}
static void show_help(const char *name)
{
fprintf(stdout, "%s [options] FILE\n"
" -h, --help Show this help\n"
" --version Show version\n"
" -r, --remote Remote daemon name\n",
name);
}
int main(int argc, char *argv[])
{
struct data data = { 0, };
int c;
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "remote", required_argument, NULL, 'r' },
{ NULL, 0, NULL, 0}
};
pw_init(&argc, &argv);
while ((c = getopt_long(argc, argv, "hVr:", long_options, NULL)) != -1) {
switch (c) {
case 'h':
show_help(argv[0]);
return 0;
case 'V':
fprintf(stdout, "%s\n"
"Compiled with libpipewire %s\n"
"Linked with libpipewire %s\n",
argv[0],
pw_get_headers_version(),
pw_get_library_version());
return 0;
case 'r':
data.opt_remote = optarg;
break;
default:
show_help(argv[0]);
return -1;
}
}
if (optind < argc)
data.opt_filename = argv[optind];
data.f = dsf_file_open(data.opt_filename, "r", &data.info);
if (data.f == NULL) {
fprintf(stderr, "can't open file %s: %m", data.opt_filename);
return -1;
}
fprintf(stderr, "file details:\n");
fprintf(stderr, " channel_type: %u\n", data.info.channel_type);
fprintf(stderr, " channels: %u\n", data.info.channels);
fprintf(stderr, " rate: %u\n", data.info.rate);
fprintf(stderr, " lsb: %u\n", data.info.lsb);
fprintf(stderr, " samples: %"PRIu64"\n", data.info.samples);
fprintf(stderr, " length: %"PRIu64"\n", data.info.length);
fprintf(stderr, " blocksize: %u\n", data.info.blocksize);
handle_dsd_playback(&data);
dsf_file_close(data.f);
pw_deinit();
return 0;
}