[PATCH v4] sound: rawmidi: Add framing mode
David Henningsson
coding at diwic.se
Sat Apr 10 14:02:29 CEST 2021
This commit adds a new framing mode that frames all MIDI data into
32-byte frames with a timestamp.
The main benefit is that we can get accurate timestamps even if
userspace wakeup and processing is not immediate.
Testing on a Celeron N3150 with this mode has a max jitter of 2.8 ms,
compared to the in-kernel seq implementation which has a max jitter
of 5 ms during idle and much worse when running scheduler stress tests
in parallel.
Signed-off-by: David Henningsson <coding at diwic.se>
---
include/sound/rawmidi.h | 2 ++
include/uapi/sound/asound.h | 26 ++++++++++++++--
sound/core/rawmidi.c | 60 +++++++++++++++++++++++++++++++++++--
3 files changed, 84 insertions(+), 4 deletions(-)
diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h
index 334842daa904..b0057a193c31 100644
--- a/include/sound/rawmidi.h
+++ b/include/sound/rawmidi.h
@@ -81,6 +81,8 @@ struct snd_rawmidi_substream {
bool opened; /* open flag */
bool append; /* append flag (merge more streams) */
bool active_sensing; /* send active sensing when close */
+ u8 framing; /* whether to frame input data */
+ clockid_t clock_type; /* clock source to use for input framing */
int use_count; /* use counter (for output) */
size_t bytes;
struct snd_rawmidi *rmidi;
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 535a7229e1d9..af8e60740218 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -710,7 +710,7 @@ enum {
* Raw MIDI section - /dev/snd/midi??
*/
-#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 1)
+#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 2)
enum {
SNDRV_RAWMIDI_STREAM_OUTPUT = 0,
@@ -736,12 +736,34 @@ struct snd_rawmidi_info {
unsigned char reserved[64]; /* reserved for future use */
};
+enum {
+ SNDRV_RAWMIDI_FRAMING_NONE = 0,
+ SNDRV_RAWMIDI_FRAMING_TSTAMP,
+ SNDRV_RAWMIDI_FRAMING_LAST = SNDRV_RAWMIDI_FRAMING_TSTAMP,
+};
+
+#define SND_RAWMIDI_FRAMING_DATA_LENGTH 16
+
+struct snd_rawmidi_framing_tstamp {
+ /* For now, frame_type is always 0. Midi 2.0 is expected to add new
+ * types here. Applications are expected to skip unknown frame types.
+ */
+ unsigned char frame_type;
+ unsigned char length; /* number of valid bytes in data field */
+ unsigned char reserved[2];
+ unsigned int tv_nsec; /* nanoseconds */
+ unsigned long long tv_sec; /* seconds */
+ unsigned char data[SND_RAWMIDI_FRAMING_DATA_LENGTH];
+};
+
struct snd_rawmidi_params {
int stream;
size_t buffer_size; /* queue size in bytes */
size_t avail_min; /* minimum avail bytes for wakeup */
unsigned int no_active_sensing: 1; /* do not send active sensing byte in close() */
- unsigned char reserved[16]; /* reserved for future use */
+ unsigned char framing; /* For input data only, frame incoming data */
+ unsigned char clock_type; /* Type of clock to use for framing, same as clockid_t */
+ unsigned char reserved[14]; /* reserved for future use */
};
#ifndef __KERNEL__
diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c
index aca00af93afe..d4b6b9b5c0e4 100644
--- a/sound/core/rawmidi.c
+++ b/sound/core/rawmidi.c
@@ -683,6 +683,8 @@ static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime,
if (params->buffer_size < 32 || params->buffer_size > 1024L * 1024L)
return -EINVAL;
+ if (params->framing == SNDRV_RAWMIDI_FRAMING_TSTAMP && params->buffer_size & 0x1f)
+ return -EINVAL;
if (params->avail_min < 1 || params->avail_min > params->buffer_size)
return -EINVAL;
if (params->buffer_size != runtime->buffer_size) {
@@ -720,7 +722,16 @@ EXPORT_SYMBOL(snd_rawmidi_output_params);
int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream,
struct snd_rawmidi_params *params)
{
+ if (params->framing) {
+ if (params->framing > SNDRV_RAWMIDI_FRAMING_LAST)
+ return -EINVAL;
+ /* framing requires a valid clock type */
+ if (params->clock_type != CLOCK_MONOTONIC_RAW && params->clock_type != CLOCK_MONOTONIC)
+ return -EINVAL;
+ }
snd_rawmidi_drain_input(substream);
+ substream->framing = params->framing;
+ substream->clock_type = params->clock_type;
return resize_runtime_buffer(substream->runtime, params, true);
}
EXPORT_SYMBOL(snd_rawmidi_input_params);
@@ -963,6 +974,42 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card,
return -ENOIOCTLCMD;
}
+static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream,
+ const unsigned char *buffer, int src_count, struct timespec64 *tstamp)
+{
+ struct snd_rawmidi_runtime *runtime = substream->runtime;
+ struct snd_rawmidi_framing_tstamp *dest_ptr;
+ struct snd_rawmidi_framing_tstamp frame = { .tv_sec = tstamp->tv_sec, .tv_nsec = tstamp->tv_nsec };
+
+ int dest_frames = 0;
+ int frame_size = sizeof(struct snd_rawmidi_framing_tstamp);
+
+ if (snd_BUG_ON(runtime->hw_ptr & 0x1f || runtime->buffer_size & 0x1f || frame_size != 0x20))
+ return -EINVAL;
+ while (src_count > 0) {
+ if ((int)(runtime->buffer_size - runtime->avail) < frame_size) {
+ runtime->xruns += src_count;
+ return dest_frames * frame_size;
+ }
+ if (src_count >= SND_RAWMIDI_FRAMING_DATA_LENGTH)
+ frame.length = SND_RAWMIDI_FRAMING_DATA_LENGTH;
+ else {
+ frame.length = src_count;
+ memset(frame.data, 0, SND_RAWMIDI_FRAMING_DATA_LENGTH);
+ }
+ memcpy(frame.data, buffer, frame.length);
+ buffer += frame.length;
+ src_count -= frame.length;
+ dest_ptr = (struct snd_rawmidi_framing_tstamp *) (runtime->buffer + runtime->hw_ptr);
+ *dest_ptr = frame;
+ runtime->avail += frame_size;
+ runtime->hw_ptr += frame_size;
+ runtime->hw_ptr %= runtime->buffer_size;
+ dest_frames++;
+ }
+ return dest_frames * frame_size;
+}
+
/**
* snd_rawmidi_receive - receive the input data from the device
* @substream: the rawmidi substream
@@ -977,6 +1024,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream,
const unsigned char *buffer, int count)
{
unsigned long flags;
+ struct timespec64 ts64;
int result = 0, count1;
struct snd_rawmidi_runtime *runtime = substream->runtime;
@@ -987,8 +1035,15 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream,
"snd_rawmidi_receive: input is not active!!!\n");
return -EINVAL;
}
- spin_lock_irqsave(&runtime->lock, flags);
- if (count == 1) { /* special case, faster code */
+ if (substream->framing == SNDRV_RAWMIDI_FRAMING_TSTAMP) {
+ if (substream->clock_type == CLOCK_MONOTONIC_RAW)
+ ktime_get_raw_ts64(&ts64);
+ else
+ ktime_get_ts64(&ts64);
+ spin_lock_irqsave(&runtime->lock, flags);
+ result = receive_with_tstamp_framing(substream, buffer, count, &ts64);
+ } else if (count == 1) { /* special case, faster code */
+ spin_lock_irqsave(&runtime->lock, flags);
substream->bytes++;
if (runtime->avail < runtime->buffer_size) {
runtime->buffer[runtime->hw_ptr++] = buffer[0];
@@ -999,6 +1054,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream,
runtime->xruns++;
}
} else {
+ spin_lock_irqsave(&runtime->lock, flags);
substream->bytes += count;
count1 = runtime->buffer_size - runtime->hw_ptr;
if (count1 > count)
--
2.25.1
More information about the Alsa-devel
mailing list