diff --git a/src/tools/meson.build b/src/tools/meson.build index d90d282f0..562048e33 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -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', diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index bb39cfd73..1cb56ffd5 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -52,6 +52,7 @@ #include #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); diff --git a/src/tools/pw-dsdplay.c b/src/tools/pw-dsdplay.c deleted file mode 100644 index c2d9be070..000000000 --- a/src/tools/pw-dsdplay.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#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; -}