[alsa-devel] [PATCH] compress: add support for gapless playback
From: Jeeja KP jeeja.kp@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@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com ---- 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;
At Thu, 14 Feb 2013 16:52:51 +0530, Vinod Koul wrote:
From: Jeeja KP jeeja.kp@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@intel.com Signed-off-by: Vinod Koul vinod.koul@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,
int (*trigger)(struct snd_compr_stream *stream, int cmd); int (*pointer)(struct snd_compr_stream *stream, struct snd_compr_tstamp *tstamp);struct snd_compr_metadata *metadata);
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
- 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;
} else { return -EPERM; }stream->next_track = false;
@@ -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);
case _IOC_NR(SNDRV_COMPRESS_TSTAMP): retval = snd_compr_tstamp(stream, arg); break;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
On Thu, Feb 14, 2013 at 12:31:33PM +0100, Takashi Iwai wrote:
At Thu, 14 Feb 2013 16:52:51 +0530, Vinod Koul wrote:
From: Jeeja KP jeeja.kp@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@intel.com Signed-off-by: Vinod Koul vinod.koul@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?
Thanks, I will send the ASoC and driver update soon. Hopefully they will be before merge window.
-- ~Vinod
participants (2)
-
Takashi Iwai
-
Vinod Koul