pw-cat: add support for streaming

sndfile actually supports reading and writing from/to stdin/out with the -
filename, so allow that.

Add support for streaming in dsf, dff and midifile as well.

Add a -a option to pw-cat use a pipe with raw bytes, otherwise try to
use the parsers and sndfile to read/write from/to stdin/stdout.

You can then do things like:

 sox 2L-053_04_stereo-DSD64.dff -t dsf -  | pw-cat -pdv -

 pw-cat 07.Joe.Satriani.Clouds.race.across.the.sky.wav | pw-cat -pv -

 pw-cat -rmv --target=0 - | pw-mididump -

Fixes: #4204
This commit is contained in:
Wim Taymans 2024-08-23 15:36:34 +02:00
parent d9bd2628d9
commit 19f4fac1e1
4 changed files with 343 additions and 233 deletions

View file

@ -14,22 +14,25 @@
#include "dfffile.h" #include "dfffile.h"
#define BLOCKSIZE 8192
struct dff_file { struct dff_file {
uint8_t *data; uint8_t *buffer;
size_t size; size_t blocksize;
size_t offset;
int mode; int mode;
int fd; bool close;
FILE *file;
size_t pos;
struct dff_file_info info; struct dff_file_info info;
uint8_t *p;
size_t offset;
}; };
struct dff_chunk { struct dff_chunk {
uint32_t id; uint32_t id;
uint64_t size; uint64_t size;
uint64_t pos;
void *data; void *data;
}; };
@ -57,28 +60,44 @@ static inline uint64_t parse_be64(const uint8_t *in)
return res; return res;
} }
static inline int f_avail(struct dff_file *f) static inline int f_read(struct dff_file *f, void *data, size_t size)
{ {
if (f->p < f->data + f->size) size_t s = fread(data, 1, size, f->file);
return f->size + f->data - f->p; f->pos += s;
if (s < size)
return -1;
return 0; return 0;
} }
static int read_chunk(struct dff_file *f, struct dff_chunk *c) static int read_chunk(struct dff_file *f, struct dff_chunk *c)
{ {
if (f_avail(f) < 12) uint8_t data[12];
if (f_read(f, data, sizeof(data)) < 0)
return -ENOSPC; return -ENOSPC;
c->id = parse_be32(f->p); /* id of this chunk */ c->id = parse_be32(data); /* id of this chunk */
c->size = parse_be64(f->p + 4); /* size of this chunk */ c->size = parse_be64(data + 4); /* size of this chunk */
f->p += 12; c->pos = f->pos;
c->data = f->p;
return 0; return 0;
} }
static int skip_chunk(struct dff_file *f, const struct dff_chunk *c) static int skip_chunk(struct dff_file *f, const struct dff_chunk *c)
{ {
f->p = SPA_PTROFF(c->data, c->size, uint8_t); size_t bytes;
uint8_t data[256];
if (c->pos + c->size <= f->pos)
return 0;
bytes = c->size + c->pos - f->pos;
while (bytes > 0) {
size_t s = fread(data, 1, SPA_MIN(bytes, sizeof(data)), f->file);
if (s == 0)
break;
f->pos += s;
bytes -= s;
}
return 0; return 0;
} }
@ -86,22 +105,26 @@ static int read_PROP(struct dff_file *f, struct dff_chunk *prop)
{ {
struct dff_chunk c[1]; struct dff_chunk c[1];
int res; int res;
uint8_t data[4];
if (f_avail(f) < 4 || if (f_read(f, data, sizeof(data)) < 0 ||
memcmp(prop->data, "SND ", 4) != 0) memcmp(data, "SND ", 4) != 0)
return -EINVAL; return -EINVAL;
f->p += 4;
while (f->p < SPA_PTROFF(prop->data, prop->size, uint8_t)) { while (f->pos < prop->pos + prop->size) {
if ((res = read_chunk(f, &c[0])) < 0) if ((res = read_chunk(f, &c[0])) < 0)
return res; return res;
switch (c[0].id) { switch (c[0].id) {
case FOURCC('F', 'S', ' ', ' '): case FOURCC('F', 'S', ' ', ' '):
f->info.rate = parse_be32(f->p); if (f_read(f, data, 4) < 0)
return -EINVAL;
f->info.rate = parse_be32(data);
break; break;
case FOURCC('C', 'H', 'N', 'L'): case FOURCC('C', 'H', 'N', 'L'):
f->info.channels = parse_be16(f->p); if (f_read(f, data, 2) < 0)
return -EINVAL;
f->info.channels = parse_be16(data);
switch (f->info.channels) { switch (f->info.channels) {
case 2: case 2:
f->info.channel_type = 2; f->info.channel_type = 2;
@ -116,7 +139,9 @@ static int read_PROP(struct dff_file *f, struct dff_chunk *prop)
break; break;
case FOURCC('C', 'M', 'P', 'R'): case FOURCC('C', 'M', 'P', 'R'):
{ {
uint32_t cmpr = parse_be32(f->p); if (f_read(f, data, 4) < 0)
return -EINVAL;
uint32_t cmpr = parse_be32(data);
if (cmpr != FOURCC('D', 'S', 'D', ' ')) if (cmpr != FOURCC('D', 'S', 'D', ' '))
return -ENOTSUP; return -ENOTSUP;
break; break;
@ -138,15 +163,16 @@ static int read_FRM8(struct dff_file *f)
struct dff_chunk c[2]; struct dff_chunk c[2];
int res; int res;
bool found_dsd = false; bool found_dsd = false;
uint8_t data[4];
if ((res = read_chunk(f, &c[0])) < 0) if ((res = read_chunk(f, &c[0])) < 0)
return res; return res;
if (c[0].id != FOURCC('F','R','M','8')) if (c[0].id != FOURCC('F','R','M','8'))
return -EINVAL; return -EINVAL;
if (f_avail(f) < 4 ||
memcmp(c[0].data, "DSD ", 4) != 0) if (f_read(f, data, sizeof(data)) < 0 ||
memcmp(data, "DSD ", 4) != 0)
return -EINVAL; return -EINVAL;
f->p += 4;
while (true) { while (true) {
if ((res = read_chunk(f, &c[1])) < 0) if ((res = read_chunk(f, &c[1])) < 0)
@ -181,37 +207,33 @@ static int read_FRM8(struct dff_file *f)
static int open_read(struct dff_file *f, const char *filename, struct dff_file_info *info) static int open_read(struct dff_file *f, const char *filename, struct dff_file_info *info)
{ {
int res; int res;
struct stat st;
if ((f->fd = open(filename, O_RDONLY)) < 0) { if (strcmp(filename, "-") != 0) {
res = -errno; if ((f->file = fopen(filename, "r")) == NULL) {
goto exit; res = -errno;
goto exit;
}
f->close = true;
} else {
f->close = false;
f->file = stdin;
} }
if (fstat(f->fd, &st) < 0) {
res = -errno;
goto exit_close;
}
f->size = st.st_size;
f->data = mmap(NULL, f->size, PROT_READ, MAP_SHARED, f->fd, 0);
if (f->data == MAP_FAILED) {
res = -errno;
goto exit_close;
}
f->p = f->data;
if ((res = read_FRM8(f)) < 0) if ((res = read_FRM8(f)) < 0)
goto exit_unmap; goto exit_close;
f->blocksize = BLOCKSIZE * f->info.channels;
f->buffer = calloc(1, f->blocksize);
if (f->buffer == NULL) {
res = -errno;
goto exit_close;
}
f->mode = 1; f->mode = 1;
*info = f->info; *info = f->info;
return 0; return 0;
exit_unmap:
munmap(f->data, f->size);
exit_close: exit_close:
close(f->fd); if (f->close)
fclose(f->file);
exit: exit:
return res; return res;
} }
@ -267,18 +289,26 @@ dff_file_read(struct dff_file *f, void *data, size_t samples, const struct dff_l
int32_t step = SPA_ABS(layout->interleave); int32_t step = SPA_ABS(layout->interleave);
uint32_t channels = f->info.channels; uint32_t channels = f->info.channels;
bool rev = layout->lsb != f->info.lsb; bool rev = layout->lsb != f->info.lsb;
size_t total, offset, scale; size_t total, offset, scale, pos;
offset = f->offset; offset = f->offset;
pos = offset % f->blocksize;
scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u); scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u);
samples *= step; samples *= step;
samples *= scale; samples *= scale;
for (total = 0; total < samples && offset < f->info.length; total++) { for (total = 0; total < samples; total++) {
uint32_t i; uint32_t i;
int32_t j; int32_t j;
const uint8_t *s = f->p + offset; const uint8_t *s = f->buffer + pos;
if (pos == 0) {
if (fread(f->buffer, 1, f->blocksize, f->file) != f->blocksize)
break;
}
if (f->info.length > 0 && offset >= f->info.length)
break;
for (i = 0; i < layout->channels; i++) { for (i = 0; i < layout->channels; i++) {
if (layout->interleave > 0) { if (layout->interleave > 0) {
@ -294,6 +324,9 @@ dff_file_read(struct dff_file *f, void *data, size_t samples, const struct dff_l
} }
} }
offset += step * channels; offset += step * channels;
pos += step * channels;
if (pos == f->blocksize)
pos = 0;
} }
f->offset = offset; f->offset = offset;
@ -302,12 +335,9 @@ dff_file_read(struct dff_file *f, void *data, size_t samples, const struct dff_l
int dff_file_close(struct dff_file *f) int dff_file_close(struct dff_file *f)
{ {
if (f->mode == 1) { if (f->close)
munmap(f->data, f->size); fclose(f->file);
} else free(f->buffer);
return -EINVAL;
close(f->fd);
free(f); free(f);
return 0; return 0;
} }

View file

@ -14,16 +14,14 @@
#include "dsffile.h" #include "dsffile.h"
struct dsf_file { struct dsf_file {
uint8_t *data; uint8_t *buffer;
size_t size; size_t offset;
int mode; int mode;
int fd; bool close;
FILE *file;
struct dsf_file_info info; struct dsf_file_info info;
uint8_t *p;
size_t offset;
}; };
static inline uint32_t parse_le32(const uint8_t *in) static inline uint32_t parse_le32(const uint8_t *in)
@ -44,104 +42,110 @@ static inline uint64_t parse_le64(const uint8_t *in)
return res; return res;
} }
static inline int f_avail(struct dsf_file *f) static inline int f_skip(struct dsf_file *f, size_t bytes)
{ {
if (f->p < f->data + f->size) uint8_t data[256];
return f->size + f->data - f->p; while (bytes > 0) {
size_t s = fread(data, 1, SPA_MIN(bytes, sizeof(data)), f->file);
bytes -= s;
}
return 0; return 0;
} }
static int read_DSD(struct dsf_file *f) static int read_DSD(struct dsf_file *f)
{ {
size_t s;
uint64_t size; uint64_t size;
uint8_t data[28];
if (f_avail(f) < 28 || s = fread(data, 1, 28, f->file);
memcmp(f->p, "DSD ", 4) != 0) if (s < 28 || memcmp(data, "DSD ", 4) != 0)
return -EINVAL; return -EINVAL;
size = parse_le64(f->p + 4); /* size of this chunk */ size = parse_le64(data + 4); /* size of this chunk */
parse_le64(f->p + 12); /* total size */ parse_le64(data + 12); /* total size */
parse_le64(f->p + 20); /* metadata */ parse_le64(data + 20); /* metadata */
f->p += size; if (size > s)
f_skip(f, size - s);
return 0; return 0;
} }
static int read_fmt(struct dsf_file *f) static int read_fmt(struct dsf_file *f)
{ {
size_t s;
uint64_t size; uint64_t size;
uint8_t data[52];
if (f_avail(f) < 52 || s = fread(data, 1, 52, f->file);
memcmp(f->p, "fmt ", 4) != 0) if (s < 52 || memcmp(data, "fmt ", 4) != 0)
return -EINVAL; return -EINVAL;
size = parse_le64(f->p + 4); /* size of this chunk */ size = parse_le64(data + 4); /* size of this chunk */
if (parse_le32(f->p + 12) != 1) /* version */ if (parse_le32(data + 12) != 1) /* version */
return -EINVAL; return -EINVAL;
if (parse_le32(f->p + 16) != 0) /* format id */ if (parse_le32(data + 16) != 0) /* format id */
return -EINVAL; return -EINVAL;
f->info.channel_type = parse_le32(f->p + 20); f->info.channel_type = parse_le32(data + 20);
f->info.channels = parse_le32(f->p + 24); f->info.channels = parse_le32(data + 24);
f->info.rate = parse_le32(f->p + 28); f->info.rate = parse_le32(data + 28);
f->info.lsb = parse_le32(f->p + 32) == 1; f->info.lsb = parse_le32(data + 32) == 1;
f->info.samples = parse_le64(f->p + 36); f->info.samples = parse_le64(data + 36);
f->info.blocksize = parse_le32(f->p + 44); f->info.blocksize = parse_le32(data + 44);
f->p += size; if (size > s)
f_skip(f, size - s);
f->buffer = calloc(1, f->info.blocksize * f->info.channels);
if (f->buffer == NULL)
return -errno;
return 0; return 0;
} }
static int read_data(struct dsf_file *f) static int read_data(struct dsf_file *f)
{ {
size_t s;
uint64_t size; uint64_t size;
uint8_t data[12];
if (f_avail(f) < 12 || s = fread(data, 1, 12, f->file);
memcmp(f->p, "data", 4) != 0) if (s < 12 || memcmp(data, "data", 4) != 0)
return -EINVAL; return -EINVAL;
size = parse_le64(f->p + 4); /* size of this chunk */ size = parse_le64(data + 4); /* size of this chunk */
f->info.length = size - 12; f->info.length = size - 12;
f->p += 12;
return 0; return 0;
} }
static int open_read(struct dsf_file *f, const char *filename, struct dsf_file_info *info) static int open_read(struct dsf_file *f, const char *filename, struct dsf_file_info *info)
{ {
int res; int res;
struct stat st;
if ((f->fd = open(filename, O_RDONLY)) < 0) { if (strcmp(filename, "-") != 0) {
res = -errno; if ((f->file = fopen(filename, "r")) == NULL) {
goto exit; res = -errno;
goto exit;
}
f->close = true;
} else {
f->close = false;
f->file = stdin;
} }
if (fstat(f->fd, &st) < 0) {
res = -errno;
goto exit_close;
}
f->size = st.st_size;
f->data = mmap(NULL, f->size, PROT_READ, MAP_SHARED, f->fd, 0);
if (f->data == MAP_FAILED) {
res = -errno;
goto exit_close;
}
f->p = f->data;
if ((res = read_DSD(f)) < 0) if ((res = read_DSD(f)) < 0)
goto exit_unmap; goto exit_close;
if ((res = read_fmt(f)) < 0) if ((res = read_fmt(f)) < 0)
goto exit_unmap; goto exit_close;
if ((res = read_data(f)) < 0) if ((res = read_data(f)) < 0)
goto exit_unmap; goto exit_close;
f->mode = 1; f->mode = 1;
*info = f->info; *info = f->info;
return 0; return 0;
exit_unmap:
munmap(f->data, f->size);
exit_close: exit_close:
close(f->fd); if (f->close)
fclose(f->file);
exit: exit:
return res; return res;
} }
@ -197,21 +201,28 @@ dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_l
int step = SPA_ABS(layout->interleave); int step = SPA_ABS(layout->interleave);
bool rev = layout->lsb != f->info.lsb; bool rev = layout->lsb != f->info.lsb;
size_t total, block, offset, pos, scale; size_t total, block, offset, pos, scale;
size_t blocksize = f->info.blocksize * f->info.channels;
block = f->offset / f->info.blocksize; block = f->offset / f->info.blocksize;
offset = block * f->info.blocksize * f->info.channels; offset = block * blocksize;
pos = f->offset % f->info.blocksize; pos = f->offset % f->info.blocksize;
scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u); scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u);
samples *= step; samples *= step;
samples *= scale; samples *= scale;
for (total = 0; total < samples && offset + pos < f->info.length; total++) { for (total = 0; total < samples; total++) {
const uint8_t *s = f->p + offset + pos;
uint32_t i; uint32_t i;
if (pos == 0) {
if (fread(f->buffer, 1, blocksize, f->file) != blocksize)
break;
}
if (f->info.length > 0 && offset + pos >= f->info.length) {
break;
}
for (i = 0; i < layout->channels; i++) { for (i = 0; i < layout->channels; i++) {
const uint8_t *c = &s[f->info.blocksize * i]; const uint8_t *c = &f->buffer[f->info.blocksize * i + pos];
int j; int j;
if (layout->interleave > 0) { if (layout->interleave > 0) {
@ -225,7 +236,7 @@ dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_l
pos += step; pos += step;
if (pos == f->info.blocksize) { if (pos == f->info.blocksize) {
pos = 0; pos = 0;
offset += f->info.blocksize * f->info.channels; offset += blocksize;
} }
} }
f->offset += total * step; f->offset += total * step;
@ -235,12 +246,9 @@ dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_l
int dsf_file_close(struct dsf_file *f) int dsf_file_close(struct dsf_file *f)
{ {
if (f->mode == 1) { if (f->close)
munmap(f->data, f->size); fclose(f->file);
} else free(f->buffer);
return -EINVAL;
close(f->fd);
free(f); free(f);
return 0; return 0;
} }

View file

@ -19,27 +19,28 @@
struct midi_track { struct midi_track {
uint16_t id; uint16_t id;
uint8_t *data; long start;
uint32_t size; uint32_t size;
long pos;
uint8_t *p;
int64_t tick; int64_t tick;
unsigned int eof:1; unsigned int eof:1;
uint8_t event[4]; uint8_t event[4];
}; };
struct midi_file { struct midi_file {
uint8_t *data;
size_t size;
int mode; int mode;
int fd; FILE *file;
bool close;
long pos;
uint8_t *buffer;
size_t buffer_size;
struct midi_file_info info; struct midi_file_info info;
uint32_t length; uint32_t length;
uint32_t tempo; uint32_t tempo;
uint8_t *p;
int64_t tick; int64_t tick;
double tick_sec; double tick_sec;
double tick_start; double tick_start;
@ -57,138 +58,157 @@ static inline uint32_t parse_be32(const uint8_t *in)
return (in[0] << 24) | (in[1] << 16) | (in[2] << 8) | in[3]; return (in[0] << 24) | (in[1] << 16) | (in[2] << 8) | in[3];
} }
static inline int mf_avail(struct midi_file *mf) static inline int mf_read(struct midi_file *mf, void *data, size_t size)
{ {
if (mf->p < mf->data + mf->size) if (fread(data, size, 1, mf->file) != 1)
return mf->size + mf->data - mf->p; return 0;
return 0; mf->pos += size;
return 1;
} }
static inline int tr_avail(struct midi_track *tr) static inline int tr_avail(struct midi_track *tr)
{ {
if (tr->eof) if (tr->eof)
return 0; return 0;
if (tr->p < tr->data + tr->size) if (tr->size == 0)
return tr->size + tr->data - tr->p; return 1;
if (tr->pos < tr->start + tr->size)
return tr->size + tr->start - tr->pos;
tr->eof = true; tr->eof = true;
return 0; return 0;
} }
static int read_mthd(struct midi_file *mf) static int read_mthd(struct midi_file *mf)
{ {
if (mf_avail(mf) < 14 || uint8_t data[14];
memcmp(mf->p, "MThd", 4) != 0)
return -EINVAL; if (mf_read(mf, data, sizeof(data)) != 1 ||
memcmp(data, "MThd", 4) != 0)
mf->length = parse_be32(mf->p + 4);
mf->info.format = parse_be16(mf->p + 8);
mf->info.ntracks = parse_be16(mf->p + 10);
mf->info.division = parse_be16(mf->p + 12);
mf->p += 14;
return 0;
}
static int read_mtrk(struct midi_file *mf, struct midi_track *track)
{
if (mf_avail(mf) < 8 ||
memcmp(mf->p, "MTrk", 4) != 0)
return -EINVAL;
track->data = track->p = mf->p + 8;
track->size = parse_be32(mf->p + 4);
mf->p = track->data + track->size;
if (mf->p > mf->data + mf->size)
return -EINVAL; return -EINVAL;
mf->length = parse_be32(data + 4);
mf->info.format = parse_be16(data + 8);
mf->info.ntracks = parse_be16(data + 10);
mf->info.division = parse_be16(data + 12);
return 0; return 0;
} }
static int parse_varlen(struct midi_file *mf, struct midi_track *tr, uint32_t *result) static int parse_varlen(struct midi_file *mf, struct midi_track *tr, uint32_t *result)
{ {
uint32_t value = 0; uint32_t value = 0;
uint8_t data[1];
while (tr_avail(tr) > 0) { while (mf_read(mf, data, 1) == 1) {
uint8_t b = *tr->p++; value = (value << 7) | (data[0] & 0x7f);
value = (value << 7) | (b & 0x7f); if ((data[0] & 0x80) == 0)
if ((b & 0x80) == 0)
break; break;
} }
*result = value; *result = value;
return 0; return 0;
} }
static int read_delta_time(struct midi_file *mf, struct midi_track *tr)
{
int res;
uint32_t delta_time;
if ((res = parse_varlen(mf, tr, &delta_time)) < 0)
return res;
tr->tick += delta_time;
tr->pos = mf->pos;
return 0;
}
static int read_mtrk(struct midi_file *mf, struct midi_track *track)
{
uint8_t data[8];
if (mf_read(mf, data, sizeof(data)) != 1 ||
memcmp(data, "MTrk", 4) != 0)
return -EINVAL;
track->start = track->pos = mf->pos;
track->size = parse_be32(data + 4);
return read_delta_time(mf, track);
}
static uint8_t *ensure_buffer(struct midi_file *mf, struct midi_track *tr, size_t size)
{
if (size <= 4)
return tr->event;
if (size > mf->buffer_size) {
mf->buffer = realloc(mf->buffer, size);
mf->buffer_size = size;
}
return mf->buffer;
}
static int open_read(struct midi_file *mf, const char *filename, struct midi_file_info *info) static int open_read(struct midi_file *mf, const char *filename, struct midi_file_info *info)
{ {
int res; int res;
uint16_t i; uint16_t i;
struct stat st;
if ((mf->fd = open(filename, O_RDONLY)) < 0) { if (strcmp(filename, "-") != 0) {
res = -errno; if ((mf->file = fopen(filename, "r")) == NULL) {
goto exit; res = -errno;
goto exit;
}
mf->close = true;
} else {
mf->file = stdin;
mf->close = false;
} }
if (fstat(mf->fd, &st) < 0) {
res = -errno;
goto exit_close;
}
mf->size = st.st_size;
mf->data = mmap(NULL, mf->size, PROT_READ, MAP_SHARED, mf->fd, 0);
if (mf->data == MAP_FAILED) {
res = -errno;
goto exit_close;
}
mf->p = mf->data;
if ((res = read_mthd(mf)) < 0) if ((res = read_mthd(mf)) < 0)
goto exit_unmap; goto exit_close;
mf->tempo = DEFAULT_TEMPO; mf->tempo = DEFAULT_TEMPO;
mf->tick = 0; mf->tick = 0;
for (i = 0; i < mf->info.ntracks; i++) { for (i = 0; i < mf->info.ntracks; i++) {
struct midi_track *tr = &mf->tracks[i]; struct midi_track *tr = &mf->tracks[i];
uint32_t delta_time;
if ((res = read_mtrk(mf, tr)) < 0) if ((res = read_mtrk(mf, tr)) < 0)
goto exit_unmap; goto exit_close;
if ((res = parse_varlen(mf, tr, &delta_time)) < 0)
goto exit_unmap;
tr->tick = delta_time;
tr->id = i; tr->id = i;
if (i + 1 < mf->info.ntracks &&
fseek(mf->file, tr->start + tr->size, SEEK_SET) != 0) {
res = -errno;
goto exit_close;
}
} }
mf->mode = 1; mf->mode = 1;
*info = mf->info; *info = mf->info;
return 0; return 0;
exit_unmap:
munmap(mf->data, mf->size);
exit_close: exit_close:
close(mf->fd); if (mf->close)
fclose(mf->file);
exit: exit:
return res; return res;
} }
static inline int write_n(int fd, const void *buf, int count) static inline int write_n(FILE *file, const void *buf, int count)
{ {
return write(fd, buf, count) == (ssize_t)count ? count : -errno; return fwrite(buf, 1, count, file) == (size_t)count ? count : -errno;
} }
static inline int write_be16(int fd, uint16_t val) static inline int write_be16(FILE *file, uint16_t val)
{ {
uint8_t buf[2] = { val >> 8, val }; uint8_t buf[2] = { val >> 8, val };
return write_n(fd, buf, 2); return write_n(file, buf, 2);
} }
static inline int write_be32(int fd, uint32_t val) static inline int write_be32(FILE *file, uint32_t val)
{ {
uint8_t buf[4] = { val >> 24, val >> 16, val >> 8, val }; uint8_t buf[4] = { val >> 24, val >> 16, val >> 8, val };
return write_n(fd, buf, 4); return write_n(file, buf, 4);
} }
#define CHECK_RES(expr) if ((res = (expr)) < 0) return res #define CHECK_RES(expr) if ((res = (expr)) < 0) return res
@ -198,17 +218,17 @@ static int write_headers(struct midi_file *mf)
struct midi_track *tr = &mf->tracks[0]; struct midi_track *tr = &mf->tracks[0];
int res; int res;
lseek(mf->fd, 0, SEEK_SET); fseek(mf->file, 0, SEEK_SET);
mf->length = 6; mf->length = 6;
CHECK_RES(write_n(mf->fd, "MThd", 4)); CHECK_RES(write_n(mf->file, "MThd", 4));
CHECK_RES(write_be32(mf->fd, mf->length)); CHECK_RES(write_be32(mf->file, mf->length));
CHECK_RES(write_be16(mf->fd, mf->info.format)); CHECK_RES(write_be16(mf->file, mf->info.format));
CHECK_RES(write_be16(mf->fd, mf->info.ntracks)); CHECK_RES(write_be16(mf->file, mf->info.ntracks));
CHECK_RES(write_be16(mf->fd, mf->info.division)); CHECK_RES(write_be16(mf->file, mf->info.division));
CHECK_RES(write_n(mf->fd, "MTrk", 4)); CHECK_RES(write_n(mf->file, "MTrk", 4));
CHECK_RES(write_be32(mf->fd, tr->size)); CHECK_RES(write_be32(mf->file, tr->size));
return 0; return 0;
} }
@ -226,9 +246,15 @@ static int open_write(struct midi_file *mf, const char *filename, struct midi_fi
if (info->division == 0) if (info->division == 0)
info->division = 96; info->division = 96;
if ((mf->fd = open(filename, O_WRONLY | O_CREAT, 0660)) < 0) { if (strcmp(filename, "-") != 0) {
res = -errno; if ((mf->file = fopen(filename, "w")) == NULL) {
goto exit; res = -errno;
goto exit;
}
mf->close = true;
} else {
mf->file = stdout;
mf->close = false;
} }
mf->mode = 2; mf->mode = 2;
mf->tempo = DEFAULT_TEMPO; mf->tempo = DEFAULT_TEMPO;
@ -271,17 +297,17 @@ int midi_file_close(struct midi_file *mf)
{ {
int res; int res;
if (mf->mode == 1) { if (mf->mode == 2) {
munmap(mf->data, mf->size);
} else if (mf->mode == 2) {
uint8_t buf[4] = { 0x00, 0xff, 0x2f, 0x00 }; uint8_t buf[4] = { 0x00, 0xff, 0x2f, 0x00 };
CHECK_RES(write_n(mf->fd, buf, 4)); CHECK_RES(write_n(mf->file, buf, 4));
mf->tracks[0].size += 4; mf->tracks[0].size += 4;
CHECK_RES(write_headers(mf)); CHECK_RES(write_headers(mf));
} else } else
return -EINVAL; return -EINVAL;
close(mf->fd); if (mf->close)
fclose(mf->file);
free(mf->buffer);
free(mf); free(mf);
return 0; return 0;
} }
@ -322,22 +348,31 @@ int midi_file_next_time(struct midi_file *mf, double *sec)
int midi_file_read_event(struct midi_file *mf, struct midi_event *event) int midi_file_read_event(struct midi_file *mf, struct midi_event *event)
{ {
struct midi_track *tr; struct midi_track *tr;
uint32_t delta_time, size; uint32_t size;
uint8_t status, meta; uint8_t status, meta;
int res, running; int res, running;
long offs;
event->data = NULL;
if ((res = peek_next(mf, event)) <= 0) if ((res = peek_next(mf, event)) <= 0)
return res; return res;
tr = &mf->tracks[event->track]; tr = &mf->tracks[event->track];
status = *tr->p;
offs = tr->pos;
if (offs != mf->pos) {
if (fseek(mf->file, offs, SEEK_SET) != 0)
return -errno;
}
mf_read(mf, &status, 1);
running = (status & 0x80) == 0; running = (status & 0x80) == 0;
if (running) { if (running) {
tr->event[1] = status;
status = tr->event[0]; status = tr->event[0];
event->data = tr->event;
} else { } else {
event->data = tr->p++;
tr->event[0] = status; tr->event[0] = status;
} }
@ -352,52 +387,82 @@ int midi_file_read_event(struct midi_file *mf, struct midi_event *event)
break; break;
case 0xff: case 0xff:
meta = *tr->p++; if (running)
return -EINVAL;
mf_read(mf, &meta, 1);
if ((res = parse_varlen(mf, tr, &size)) < 0) if ((res = parse_varlen(mf, tr, &size)) < 0)
return res; return res;
event->meta.offset = tr->p - event->data; event->meta.offset = 2;
event->meta.size = size; event->meta.size = size;
if ((event->data = ensure_buffer(mf, tr, size + event->meta.offset)) == NULL)
return -ENOMEM;
event->data[0] = status;
event->data[1] = meta;
if (size > 0 && mf_read(mf, &event->data[2], size) != 1)
return -EINVAL;
switch (meta) { switch (meta) {
case 0x2f: case 0x2f:
tr->eof = true; tr->eof = true;
break; break;
case 0x51: case 0x51:
{
if (size < 3) if (size < 3)
return -EINVAL; return -EINVAL;
mf->tick_sec = event->sec; mf->tick_sec = event->sec;
mf->tick_start = tr->tick; mf->tick_start = tr->tick;
event->meta.parsed.tempo.uspqn = mf->tempo = (tr->p[0]<<16) | (tr->p[1]<<8) | tr->p[2]; event->meta.parsed.tempo.uspqn = mf->tempo =
(event->data[2]<<16) | (event->data[3]<<8) | event->data[4];
break; break;
} }
size += tr->p - event->data; }
size += event->meta.offset;
break; break;
case 0xf0: case 0xf0:
case 0xf7: case 0xf7:
if (running)
return -EINVAL;
if ((res = parse_varlen(mf, tr, &size)) < 0) if ((res = parse_varlen(mf, tr, &size)) < 0)
return res; return res;
size += tr->p - event->data;
if ((event->data = ensure_buffer(mf, tr, size + 1)) == NULL)
return -ENOMEM;
event->data[0] = status;
if (mf_read(mf, &event->data[1], size) != 1)
return -EINVAL;
size += 1;
break; break;
default: default:
return -EINVAL; return -EINVAL;
} }
event->size = size; event->size = size;
if (event->data == NULL) {
if (running) { if ((event->data = ensure_buffer(mf, tr, size)) == NULL)
memcpy(&event->data[1], tr->p, size - 1); return -ENOMEM;
tr->p += size - 1; event->data[0] = tr->event[0];
} else { if (running) {
tr->p = event->data + event->size; event->data[1] = tr->event[1];
if (size > 2 && mf_read(mf, &event->data[2], size - 2) != 1)
return -EINVAL;
} else {
if (size > 1 && mf_read(mf, &event->data[1], size - 1) != 1)
return -EINVAL;
}
} }
if ((res = parse_varlen(mf, tr, &delta_time)) < 0) if ((res = read_delta_time(mf, tr)) < 0)
return res; return res;
tr->tick += delta_time;
return 1; return 1;
} }
@ -414,7 +479,7 @@ static int write_varlen(struct midi_file *mf, struct midi_track *tr, uint32_t va
} }
do { do {
b = buffer & 0xff; b = buffer & 0xff;
CHECK_RES(write_n(mf->fd, &b, 1)); CHECK_RES(write_n(mf->file, &b, 1));
tr->size++; tr->size++;
buffer >>= 8; buffer >>= 8;
} while (b & 0x80); } while (b & 0x80);
@ -458,7 +523,7 @@ int midi_file_write_event(struct midi_file *mf, const struct midi_event *event)
CHECK_RES(write_varlen(mf, tr, tick - tr->tick)); CHECK_RES(write_varlen(mf, tr, tick - tr->tick));
tr->tick = tick; tr->tick = tick;
CHECK_RES(write_n(mf->fd, data, size)); CHECK_RES(write_n(mf->file, data, size));
tr->size += size; tr->size += size;
return 0; return 0;

View file

@ -103,6 +103,7 @@ struct data {
#define TYPE_ENCODED 3 #define TYPE_ENCODED 3
#endif #endif
int data_type; int data_type;
bool raw;
const char *remote_name; const char *remote_name;
const char *media_type; const char *media_type;
const char *media_category; const char *media_category;
@ -980,6 +981,7 @@ static const struct option long_options[] = {
{ "format", required_argument, NULL, OPT_FORMAT }, { "format", required_argument, NULL, OPT_FORMAT },
{ "volume", required_argument, NULL, OPT_VOLUME }, { "volume", required_argument, NULL, OPT_VOLUME },
{ "quality", required_argument, NULL, 'q' }, { "quality", required_argument, NULL, 'q' },
{ "raw", no_argument, NULL, 'a' },
{ NULL, 0, NULL, 0 } { NULL, 0, NULL, 0 }
}; };
@ -1024,6 +1026,7 @@ static void show_usage(const char *name, bool is_error)
" --format Sample format %s (req. for rec) (default %s)\n" " --format Sample format %s (req. for rec) (default %s)\n"
" --volume Stream volume 0-1.0 (default %.3f)\n" " --volume Stream volume 0-1.0 (default %.3f)\n"
" -q --quality Resampler quality (0 - 15) (default %d)\n" " -q --quality Resampler quality (0 - 15) (default %d)\n"
" -a, --raw RAW mode\n"
"\n"), "\n"),
DEFAULT_RATE, DEFAULT_RATE,
DEFAULT_CHANNELS, DEFAULT_CHANNELS,
@ -1691,9 +1694,9 @@ int main(int argc, char *argv[])
} }
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
while ((c = getopt_long(argc, argv, "hvprmdoR:q:P:", long_options, NULL)) != -1) { while ((c = getopt_long(argc, argv, "hvprmdoR:q:P:a", long_options, NULL)) != -1) {
#else #else
while ((c = getopt_long(argc, argv, "hvprmdR:q:P:", long_options, NULL)) != -1) { while ((c = getopt_long(argc, argv, "hvprmdR:q:P:a", long_options, NULL)) != -1) {
#endif #endif
switch (c) { switch (c) {
@ -1745,6 +1748,10 @@ int main(int argc, char *argv[])
data.quality = atoi(optarg); data.quality = atoi(optarg);
break; break;
case 'a':
data.raw = true;
break;
case OPT_MEDIA_TYPE: case OPT_MEDIA_TYPE:
data.media_type = optarg; data.media_type = optarg;
break; break;
@ -1893,7 +1900,7 @@ int main(int argc, char *argv[])
} }
pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
if (spa_streq(data.filename, "-")) { if (data.raw) {
ret = setup_pipe(&data); ret = setup_pipe(&data);
} else { } else {
switch (data.data_type) { switch (data.data_type) {