[PATCH v4] sound: rawmidi: Add framing mode
Jaroslav Kysela
perex at perex.cz
Sun Apr 11 19:52:21 CEST 2021
Dne 11. 04. 21 v 6:34 David Henningsson napsal(a):
>
> On 2021-04-10 14:23, Jaroslav Kysela wrote:
>> Dne 10. 04. 21 v 14:02 David Henningsson napsal(a):
>>> 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 */
>> We can move this flag to above 32-bit word (no_active_sensing). I'm not sure,
>> if we need 8 bits for this. It's first change after 20 years. Another flag may
>> obsolete this one.
>
> Not sure what you mean by this, could you show the code? Framing is an
> enum rather than a flag, in case we find other framing formats with
> other sizes that would obsolete this one.
unsigned int no_active_sensing: 1;
unsigned int framing32: 1;
unsigned int framing64: 1; /* future extension, framing32 == 0 in this case */
or:
unsigned int framing_mode: 3; /* three bits for future types */
perhaps also:
unsigned int clock_type: 3; /* three bits for the clock type selection */
>>> + 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;
>> The CLOCK_REALTIME may be supported, too. For example, the input subsystem
>> supports those three timestamps and we support this in the PCM interface, too.
> OTOH, the seq subsystem supports only the monotonic clock. And nobody
> has complained so far. This can be added in a later patch if there is a
> need for it.
The monotonic clock source is used only internally for diffs - the seq timer
starts with zero. So the monotonic clock is _NOT_ exported.
In PCM interface, we have also all three clock sources. We're using own enum
there, too (SNDRV_PCM_TSTAMP_TYPE_...).
>>> +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);
>> We know the length here, so we can skip the zeroing the copied bytes with
>> memcpy().
>
> True, but I believe this would generate slightly faster code because
> SND_RAWMIDI_FRAMING_DATA_LENGTH is a constant.
It's questionable.
Jaroslav
--
Jaroslav Kysela <perex at perex.cz>
Linux Sound Maintainer; ALSA Project; Red Hat, Inc.
More information about the Alsa-devel
mailing list