[alsa-devel] [PATCH] compress: add support for gapless playback
Takashi Iwai
tiwai at suse.de
Thu Feb 14 12:31:33 CET 2013
At Thu, 14 Feb 2013 16:52:51 +0530,
Vinod Koul wrote:
>
> From: Jeeja KP <jeeja.kp at intel.com>
>
> 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 at intel.com>
> Signed-off-by: Vinod Koul <vinod.koul at intel.com>
Thanks, I'm going to apply it to for-next branch.
Is there any driver requesting this new API for 3.9 kernel?
Takashi
> ----
> v6:
> - update ioctls GET_METADATA to IOWR
> - add documenetation of enum
>
> v5:
> - update metadata value with 8 words
> - add reset of new states in set_params
> - use statically allocated structs
>
> v4:
> - update metadata struct with single key/value
>
> v3:
> - Change back to ioctl struct with padding
> - adding states for next_track and metadata for proper transistion
>
> v2:
> - Make it a patch, not RFC
> - split metadata to key/value pairs, and send multiple keys
> - add get_metadata api
> - split partial_drain to next_data & partial_drain
> - add stream states for transistion
> - update documentation
> -----
> Documentation/sound/alsa/compress_offload.txt | 46 ++++++++++++
> include/sound/compress_driver.h | 8 ++
> include/uapi/sound/compress_offload.h | 31 ++++++++-
> sound/core/compress_offload.c | 96 +++++++++++++++++++++++++
> 4 files changed, 180 insertions(+), 1 deletions(-)
>
> diff --git a/Documentation/sound/alsa/compress_offload.txt b/Documentation/sound/alsa/compress_offload.txt
> index 90e9b3a..0bcc551 100644
> --- a/Documentation/sound/alsa/compress_offload.txt
> +++ b/Documentation/sound/alsa/compress_offload.txt
> @@ -145,6 +145,52 @@ Modifications include:
> - Addition of encoding options when required (derived from OpenMAX IL)
> - Addition of rateControlSupported (missing in OpenMAX AL)
>
> +Gapless Playback
> +================
> +When playing thru an album, the decoders have the ability to skip the encoder
> +delay and padding and directly move from one track content to another. The end
> +user can perceive this as gapless playback as we dont have silence while
> +switching from one track to another
> +
> +Also, there might be low-intensity noises due to encoding. Perfect gapless is
> +difficult to reach with all types of compressed data, but works fine with most
> +music content. The decoder needs to know the encoder delay and encoder padding.
> +So we need to pass this to DSP. This metadata is extracted from ID3/MP4 headers
> +and are not present by default in the bitstream, hence the need for a new
> +interface to pass this information to the DSP. Also DSP and userspace needs to
> +switch from one track to another and start using data for second track.
> +
> +The main additions are:
> +
> +- set_metadata
> +This routine sets the encoder delay and encoder padding. This can be used by
> +decoder to strip the silence. This needs to be set before the data in the track
> +is written.
> +
> +- set_next_track
> +This routine tells DSP that metadata and write operation sent after this would
> +correspond to subsequent track
> +
> +- partial drain
> +This is called when end of file is reached. The userspace can inform DSP that
> +EOF is reached and now DSP can start skipping padding delay. Also next write
> +data would belong to next track
> +
> +Sequence flow for gapless would be:
> +- Open
> +- Get caps / codec caps
> +- Set params
> +- Set metadata of the first track
> +- Fill data of the first track
> +- Trigger start
> +- User-space finished sending all,
> +- Indicaite next track data by sending set_next_track
> +- Set metadata of the next track
> +- then call partial_drain to flush most of buffer in DSP
> +- Fill data of the next track
> +- DSP switches to second track
> +(note: order for partial_drain and write for next track can be reversed as well)
> +
> Not supported:
>
> - Support for VoIP/circuit-switched calls is not the target of this
> diff --git a/include/sound/compress_driver.h b/include/sound/compress_driver.h
> index f2912ab..ff6c741 100644
> --- a/include/sound/compress_driver.h
> +++ b/include/sound/compress_driver.h
> @@ -71,6 +71,8 @@ struct snd_compr_runtime {
> * @runtime: pointer to runtime structure
> * @device: device pointer
> * @direction: stream direction, playback/recording
> + * @metadata_set: metadata set flag, true when set
> + * @next_track: has userspace signall next track transistion, true when set
> * @private_data: pointer to DSP private data
> */
> struct snd_compr_stream {
> @@ -79,6 +81,8 @@ struct snd_compr_stream {
> struct snd_compr_runtime *runtime;
> struct snd_compr *device;
> enum snd_compr_direction direction;
> + bool metadata_set;
> + bool next_track;
> void *private_data;
> };
>
> @@ -110,6 +114,10 @@ struct snd_compr_ops {
> struct snd_compr_params *params);
> int (*get_params)(struct snd_compr_stream *stream,
> struct snd_codec *params);
> + int (*set_metadata)(struct snd_compr_stream *stream,
> + struct snd_compr_metadata *metadata);
> + int (*get_metadata)(struct snd_compr_stream *stream,
> + struct snd_compr_metadata *metadata);
> int (*trigger)(struct snd_compr_stream *stream, int cmd);
> int (*pointer)(struct snd_compr_stream *stream,
> struct snd_compr_tstamp *tstamp);
> diff --git a/include/uapi/sound/compress_offload.h b/include/uapi/sound/compress_offload.h
> index 05341a4..d630163 100644
> --- a/include/uapi/sound/compress_offload.h
> +++ b/include/uapi/sound/compress_offload.h
> @@ -30,7 +30,7 @@
> #include <sound/compress_params.h>
>
>
> -#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 1, 0)
> +#define SNDRV_COMPRESS_VERSION SNDRV_PROTOCOL_VERSION(0, 1, 1)
> /**
> * struct snd_compressed_buffer: compressed buffer
> * @fragment_size: size of buffer fragment in bytes
> @@ -122,6 +122,27 @@ struct snd_compr_codec_caps {
> };
>
> /**
> + * @SNDRV_COMPRESS_ENCODER_PADDING: no of samples appended by the encoder at the
> + * end of the track
> + * @SNDRV_COMPRESS_ENCODER_DELAY: no of samples inserted by the encoder at the
> + * beginning of the track
> + */
> +enum {
> + SNDRV_COMPRESS_ENCODER_PADDING = 1,
> + SNDRV_COMPRESS_ENCODER_DELAY = 2,
> +};
> +
> +/**
> + * struct snd_compr_metadata: compressed stream metadata
> + * @key: key id
> + * @value: key value
> + */
> +struct snd_compr_metadata {
> + __u32 key;
> + __u32 value[8];
> +};
> +
> +/**
> * compress path ioctl definitions
> * SNDRV_COMPRESS_GET_CAPS: Query capability of DSP
> * SNDRV_COMPRESS_GET_CODEC_CAPS: Query capability of a codec
> @@ -145,6 +166,10 @@ struct snd_compr_codec_caps {
> struct snd_compr_codec_caps)
> #define SNDRV_COMPRESS_SET_PARAMS _IOW('C', 0x12, struct snd_compr_params)
> #define SNDRV_COMPRESS_GET_PARAMS _IOR('C', 0x13, struct snd_codec)
> +#define SNDRV_COMPRESS_SET_METADATA _IOW('C', 0x14,\
> + struct snd_compr_metadata)
> +#define SNDRV_COMPRESS_GET_METADATA _IOWR('C', 0x15,\
> + struct snd_compr_metadata)
> #define SNDRV_COMPRESS_TSTAMP _IOR('C', 0x20, struct snd_compr_tstamp)
> #define SNDRV_COMPRESS_AVAIL _IOR('C', 0x21, struct snd_compr_avail)
> #define SNDRV_COMPRESS_PAUSE _IO('C', 0x30)
> @@ -152,10 +177,14 @@ struct snd_compr_codec_caps {
> #define SNDRV_COMPRESS_START _IO('C', 0x32)
> #define SNDRV_COMPRESS_STOP _IO('C', 0x33)
> #define SNDRV_COMPRESS_DRAIN _IO('C', 0x34)
> +#define SNDRV_COMPRESS_NEXT_TRACK _IO('C', 0x35)
> +#define SNDRV_COMPRESS_PARTIAL_DRAIN _IO('C', 0x36)
> /*
> * TODO
> * 1. add mmap support
> *
> */
> #define SND_COMPR_TRIGGER_DRAIN 7 /*FIXME move this to pcm.h */
> +#define SND_COMPR_TRIGGER_NEXT_TRACK 8
> +#define SND_COMPR_TRIGGER_PARTIAL_DRAIN 9
> #endif
> diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c
> index ad11dc9..cbb592f 100644
> --- a/sound/core/compress_offload.c
> +++ b/sound/core/compress_offload.c
> @@ -483,6 +483,8 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg)
> if (retval)
> goto out;
> stream->runtime->state = SNDRV_PCM_STATE_SETUP;
> + stream->metadata_set = false;
> + stream->next_track = false;
> } else {
> return -EPERM;
> }
> @@ -514,6 +516,49 @@ out:
> return retval;
> }
>
> +static int
> +snd_compr_get_metadata(struct snd_compr_stream *stream, unsigned long arg)
> +{
> + struct snd_compr_metadata metadata;
> + int retval;
> +
> + if (!stream->ops->get_metadata)
> + return -ENXIO;
> +
> + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata)))
> + return -EFAULT;
> +
> + retval = stream->ops->get_metadata(stream, &metadata);
> + if (retval != 0)
> + return retval;
> +
> + if (copy_to_user((void __user *)arg, &metadata, sizeof(metadata)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int
> +snd_compr_set_metadata(struct snd_compr_stream *stream, unsigned long arg)
> +{
> + struct snd_compr_metadata metadata;
> + int retval;
> +
> + if (!stream->ops->set_metadata)
> + return -ENXIO;
> + /*
> + * we should allow parameter change only when stream has been
> + * opened not in other cases
> + */
> + if (copy_from_user(&metadata, (void __user *)arg, sizeof(metadata)))
> + return -EFAULT;
> +
> + retval = stream->ops->set_metadata(stream, &metadata);
> + stream->metadata_set = true;
> +
> + return retval;
> +}
> +
> static inline int
> snd_compr_tstamp(struct snd_compr_stream *stream, unsigned long arg)
> {
> @@ -594,6 +639,44 @@ static int snd_compr_drain(struct snd_compr_stream *stream)
> return retval;
> }
>
> +static int snd_compr_next_track(struct snd_compr_stream *stream)
> +{
> + int retval;
> +
> + /* only a running stream can transition to next track */
> + if (stream->runtime->state != SNDRV_PCM_STATE_RUNNING)
> + return -EPERM;
> +
> + /* you can signal next track isf this is intended to be a gapless stream
> + * and current track metadata is set
> + */
> + if (stream->metadata_set == false)
> + return -EPERM;
> +
> + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_NEXT_TRACK);
> + if (retval != 0)
> + return retval;
> + stream->metadata_set = false;
> + stream->next_track = true;
> + return 0;
> +}
> +
> +static int snd_compr_partial_drain(struct snd_compr_stream *stream)
> +{
> + int retval;
> + if (stream->runtime->state == SNDRV_PCM_STATE_PREPARED ||
> + stream->runtime->state == SNDRV_PCM_STATE_SETUP)
> + return -EPERM;
> + /* stream can be drained only when next track has been signalled */
> + if (stream->next_track == false)
> + return -EPERM;
> +
> + retval = stream->ops->trigger(stream, SND_COMPR_TRIGGER_PARTIAL_DRAIN);
> +
> + stream->next_track = false;
> + return retval;
> +}
> +
> static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
> {
> struct snd_compr_file *data = f->private_data;
> @@ -623,6 +706,12 @@ static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
> case _IOC_NR(SNDRV_COMPRESS_GET_PARAMS):
> retval = snd_compr_get_params(stream, arg);
> break;
> + case _IOC_NR(SNDRV_COMPRESS_SET_METADATA):
> + retval = snd_compr_set_metadata(stream, arg);
> + break;
> + case _IOC_NR(SNDRV_COMPRESS_GET_METADATA):
> + retval = snd_compr_get_metadata(stream, arg);
> + break;
> case _IOC_NR(SNDRV_COMPRESS_TSTAMP):
> retval = snd_compr_tstamp(stream, arg);
> break;
> @@ -644,6 +733,13 @@ static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
> case _IOC_NR(SNDRV_COMPRESS_DRAIN):
> retval = snd_compr_drain(stream);
> break;
> + case _IOC_NR(SNDRV_COMPRESS_PARTIAL_DRAIN):
> + retval = snd_compr_partial_drain(stream);
> + break;
> + case _IOC_NR(SNDRV_COMPRESS_NEXT_TRACK):
> + retval = snd_compr_next_track(stream);
> + break;
> +
> }
> mutex_unlock(&stream->device->lock);
> return retval;
> --
> 1.7.0.4
>
More information about the Alsa-devel
mailing list