Commit 9727b490e543de956b8ba356e2d5499097d0b7a2

Authored by Jeeja KP
Committed by Takashi Iwai
1 parent 8be69efacd

ALSA: compress: add support for gapless playback

this add new API for sound compress to support gapless playback.
As noted in Documentation change, we add API to send metadata of encoder and
padding delay to DSP. Also add API for indicating EOF and switching to
subsequent track

Also bump the compress API version

Signed-off-by: Jeeja KP <jeeja.kp@intel.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>

Showing 4 changed files with 180 additions and 1 deletions Side-by-side Diff

Documentation/sound/alsa/compress_offload.txt
... ... @@ -145,6 +145,52 @@
145 145 - Addition of encoding options when required (derived from OpenMAX IL)
146 146 - Addition of rateControlSupported (missing in OpenMAX AL)
147 147  
  148 +Gapless Playback
  149 +================
  150 +When playing thru an album, the decoders have the ability to skip the encoder
  151 +delay and padding and directly move from one track content to another. The end
  152 +user can perceive this as gapless playback as we dont have silence while
  153 +switching from one track to another
  154 +
  155 +Also, there might be low-intensity noises due to encoding. Perfect gapless is
  156 +difficult to reach with all types of compressed data, but works fine with most
  157 +music content. The decoder needs to know the encoder delay and encoder padding.
  158 +So we need to pass this to DSP. This metadata is extracted from ID3/MP4 headers
  159 +and are not present by default in the bitstream, hence the need for a new
  160 +interface to pass this information to the DSP. Also DSP and userspace needs to
  161 +switch from one track to another and start using data for second track.
  162 +
  163 +The main additions are:
  164 +
  165 +- set_metadata
  166 +This routine sets the encoder delay and encoder padding. This can be used by
  167 +decoder to strip the silence. This needs to be set before the data in the track
  168 +is written.
  169 +
  170 +- set_next_track
  171 +This routine tells DSP that metadata and write operation sent after this would
  172 +correspond to subsequent track
  173 +
  174 +- partial drain
  175 +This is called when end of file is reached. The userspace can inform DSP that
  176 +EOF is reached and now DSP can start skipping padding delay. Also next write
  177 +data would belong to next track
  178 +
  179 +Sequence flow for gapless would be:
  180 +- Open
  181 +- Get caps / codec caps
  182 +- Set params
  183 +- Set metadata of the first track
  184 +- Fill data of the first track
  185 +- Trigger start
  186 +- User-space finished sending all,
  187 +- Indicaite next track data by sending set_next_track
  188 +- Set metadata of the next track
  189 +- then call partial_drain to flush most of buffer in DSP
  190 +- Fill data of the next track
  191 +- DSP switches to second track
  192 +(note: order for partial_drain and write for next track can be reversed as well)
  193 +
148 194 Not supported:
149 195  
150 196 - Support for VoIP/circuit-switched calls is not the target of this
include/sound/compress_driver.h
... ... @@ -71,6 +71,8 @@
71 71 * @runtime: pointer to runtime structure
72 72 * @device: device pointer
73 73 * @direction: stream direction, playback/recording
  74 + * @metadata_set: metadata set flag, true when set
  75 + * @next_track: has userspace signall next track transistion, true when set
74 76 * @private_data: pointer to DSP private data
75 77 */
76 78 struct snd_compr_stream {
... ... @@ -79,6 +81,8 @@
79 81 struct snd_compr_runtime *runtime;
80 82 struct snd_compr *device;
81 83 enum snd_compr_direction direction;
  84 + bool metadata_set;
  85 + bool next_track;
82 86 void *private_data;
83 87 };
84 88  
... ... @@ -110,6 +114,10 @@
110 114 struct snd_compr_params *params);
111 115 int (*get_params)(struct snd_compr_stream *stream,
112 116 struct snd_codec *params);
  117 + int (*set_metadata)(struct snd_compr_stream *stream,
  118 + struct snd_compr_metadata *metadata);
  119 + int (*get_metadata)(struct snd_compr_stream *stream,
  120 + struct snd_compr_metadata *metadata);
113 121 int (*trigger)(struct snd_compr_stream *stream, int cmd);
114 122 int (*pointer)(struct snd_compr_stream *stream,
115 123 struct snd_compr_tstamp *tstamp);
include/uapi/sound/compress_offload.h
... ... @@ -30,7 +30,7 @@
30 30 #include <sound/compress_params.h>
31 31  
32 32  
33   -#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 1, 0)
  33 +#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 1, 1)
34 34 /**
35 35 * struct snd_compressed_buffer: compressed buffer
36 36 * @fragment_size: size of buffer fragment in bytes
... ... @@ -122,6 +122,27 @@
122 122 };
123 123  
124 124 /**
  125 + * @SNDRV_COMPRESS_ENCODER_PADDING: no of samples appended by the encoder at the
  126 + * end of the track
  127 + * @SNDRV_COMPRESS_ENCODER_DELAY: no of samples inserted by the encoder at the
  128 + * beginning of the track
  129 + */
  130 +enum {
  131 + SNDRV_COMPRESS_ENCODER_PADDING = 1,
  132 + SNDRV_COMPRESS_ENCODER_DELAY = 2,
  133 +};
  134 +
  135 +/**
  136 + * struct snd_compr_metadata: compressed stream metadata
  137 + * @key: key id
  138 + * @value: key value
  139 + */
  140 +struct snd_compr_metadata {
  141 + __u32 key;
  142 + __u32 value[8];
  143 +};
  144 +
  145 +/**
125 146 * compress path ioctl definitions
126 147 * SNDRV_COMPRESS_GET_CAPS: Query capability of DSP
127 148 * SNDRV_COMPRESS_GET_CODEC_CAPS: Query capability of a codec
... ... @@ -145,6 +166,10 @@
145 166 struct snd_compr_codec_caps)
146 167 #define SNDRV_COMPRESS_SET_PARAMS _IOW('C', 0x12, struct snd_compr_params)
147 168 #define SNDRV_COMPRESS_GET_PARAMS _IOR('C', 0x13, struct snd_codec)
  169 +#define SNDRV_COMPRESS_SET_METADATA _IOW('C', 0x14,\
  170 + struct snd_compr_metadata)
  171 +#define SNDRV_COMPRESS_GET_METADATA _IOWR('C', 0x15,\
  172 + struct snd_compr_metadata)
148 173 #define SNDRV_COMPRESS_TSTAMP _IOR('C', 0x20, struct snd_compr_tstamp)
149 174 #define SNDRV_COMPRESS_AVAIL _IOR('C', 0x21, struct snd_compr_avail)
150 175 #define SNDRV_COMPRESS_PAUSE _IO('C', 0x30)
151 176  
... ... @@ -152,11 +177,15 @@
152 177 #define SNDRV_COMPRESS_START _IO('C', 0x32)
153 178 #define SNDRV_COMPRESS_STOP _IO('C', 0x33)
154 179 #define SNDRV_COMPRESS_DRAIN _IO('C', 0x34)
  180 +#define SNDRV_COMPRESS_NEXT_TRACK _IO('C', 0x35)
  181 +#define SNDRV_COMPRESS_PARTIAL_DRAIN _IO('C', 0x36)
155 182 /*
156 183 * TODO
157 184 * 1. add mmap support
158 185 *
159 186 */
160 187 #define SND_COMPR_TRIGGER_DRAIN 7 /*FIXME move this to pcm.h */
  188 +#define SND_COMPR_TRIGGER_NEXT_TRACK 8
  189 +#define SND_COMPR_TRIGGER_PARTIAL_DRAIN 9
161 190 #endif
sound/core/compress_offload.c
... ... @@ -486,6 +486,8 @@
486 486 if (retval)
487 487 goto out;
488 488 stream->runtime->state = SNDRV_PCM_STATE_SETUP;
  489 + stream->metadata_set = false;
  490 + stream->next_track = false;
489 491 } else {
490 492 return -EPERM;
491 493 }
... ... @@ -517,6 +519,49 @@
517 519 return retval;
518 520 }
519 521  
  522 +static int
  523 +snd_compr_get_metadata(struct snd_compr_stream *stream, unsigned long arg)
  524 +{
  525 + struct snd_compr_metadata metadata;
  526 + int retval;
  527 +
  528 + if (!stream->ops->get_metadata)
  529 + return -ENXIO;
  530 +
  531 + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata)))
  532 + return -EFAULT;
  533 +
  534 + retval = stream->ops->get_metadata(stream, &metadata);
  535 + if (retval != 0)
  536 + return retval;
  537 +
  538 + if (copy_to_user((void __user *)arg, &metadata, sizeof(metadata)))
  539 + return -EFAULT;
  540 +
  541 + return 0;
  542 +}
  543 +
  544 +static int
  545 +snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg)
  546 +{
  547 + struct snd_compr_metadata metadata;
  548 + int retval;
  549 +
  550 + if (!stream->ops->set_metadata)
  551 + return -ENXIO;
  552 + /*
  553 + * we should allow parameter change only when stream has been
  554 + * opened not in other cases
  555 + */
  556 + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata)))
  557 + return -EFAULT;
  558 +
  559 + retval = stream->ops->set_metadata(stream, &metadata);
  560 + stream->metadata_set = true;
  561 +
  562 + return retval;
  563 +}
  564 +
520 565 static inline int
521 566 snd_compr_tstamp(struct snd_compr_stream *stream, unsigned long arg)
522 567 {
... ... @@ -600,6 +645,44 @@
600 645 return retval;
601 646 }
602 647  
  648 +static int snd_compr_next_track(struct snd_compr_stream *stream)
  649 +{
  650 + int retval;
  651 +
  652 + /* only a running stream can transition to next track */
  653 + if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING)
  654 + return -EPERM;
  655 +
  656 + /* you can signal next track isf this is intended to be a gapless stream
  657 + * and current track metadata is set
  658 + */
  659 + if (stream->metadata_set == false)
  660 + return -EPERM;
  661 +
  662 + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_NEXT_TRACK);
  663 + if (retval != 0)
  664 + return retval;
  665 + stream->metadata_set = false;
  666 + stream->next_track = true;
  667 + return 0;
  668 +}
  669 +
  670 +static int snd_compr_partial_drain(struct snd_compr_stream *stream)
  671 +{
  672 + int retval;
  673 + if (stream->runtime->state == SNDRV_PCM_STATE_PREPARED ||
  674 + stream->runtime->state == SNDRV_PCM_STATE_SETUP)
  675 + return -EPERM;
  676 + /* stream can be drained only when next track has been signalled */
  677 + if (stream->next_track == false)
  678 + return -EPERM;
  679 +
  680 + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_PARTIAL_DRAIN);
  681 +
  682 + stream->next_track = false;
  683 + return retval;
  684 +}
  685 +
603 686 static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
604 687 {
605 688 struct snd_compr_file *data = f->private_data;
... ... @@ -629,6 +712,12 @@
629 712 case _IOC_NR(SNDRV_COMPRESS_GET_PARAMS):
630 713 retval = snd_compr_get_params(stream, arg);
631 714 break;
  715 + case _IOC_NR(SNDRV_COMPRESS_SET_METADATA):
  716 + retval = snd_compr_set_metadata(stream, arg);
  717 + break;
  718 + case _IOC_NR(SNDRV_COMPRESS_GET_METADATA):
  719 + retval = snd_compr_get_metadata(stream, arg);
  720 + break;
632 721 case _IOC_NR(SNDRV_COMPRESS_TSTAMP):
633 722 retval = snd_compr_tstamp(stream, arg);
634 723 break;
... ... @@ -650,6 +739,13 @@
650 739 case _IOC_NR(SNDRV_COMPRESS_DRAIN):
651 740 retval = snd_compr_drain(stream);
652 741 break;
  742 + case _IOC_NR(SNDRV_COMPRESS_PARTIAL_DRAIN):
  743 + retval = snd_compr_partial_drain(stream);
  744 + break;
  745 + case _IOC_NR(SNDRV_COMPRESS_NEXT_TRACK):
  746 + retval = snd_compr_next_track(stream);
  747 + break;
  748 +
653 749 }
654 750 mutex_unlock(&stream->device->lock);
655 751 return retval;