pcm: iec958: implement HDMI HBR audio formatting

High bitrate compressed audio data like DTS HD or MAT is usually
packed into 8-channel data. The HDMI specs state this has to be
formatted as a single IEC958 stream, compared to normal multi-
channel PCM data which has to be formatted as parallel IEC958 streams.

As this single-stream formatting mode may break existing setups that
expect non-PCM multichannel data to be formatted as parallel IEC958
streams it needs to be explicitly selected by setting the hdmi_mode
option to true.

The single-stream formatting implementation is prepared to cope with
arbitrary channel counts but only limited testing was done for channel
counts other than 8.

Signed-off-by: Matthias Reichl <hias@horus.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Matthias Reichl 2020-07-13 23:17:03 +02:00 committed by Takashi Iwai
parent d824b461ae
commit 8defc5c2a6

View file

@ -63,6 +63,7 @@ struct snd_pcm_iec958 {
unsigned int byteswap; unsigned int byteswap;
unsigned char preamble[3]; /* B/M/W or Z/X/Y */ unsigned char preamble[3]; /* B/M/W or Z/X/Y */
snd_pcm_fast_ops_t fops; snd_pcm_fast_ops_t fops;
int hdmi_mode;
}; };
enum { PREAMBLE_Z, PREAMBLE_X, PREAMBLE_Y }; enum { PREAMBLE_Z, PREAMBLE_X, PREAMBLE_Y };
@ -193,6 +194,10 @@ static void snd_pcm_iec958_encode(snd_pcm_iec958_t *iec,
unsigned int channel; unsigned int channel;
int32_t sample = 0; int32_t sample = 0;
int counter = iec->counter; int counter = iec->counter;
int single_stream = iec->hdmi_mode &&
(iec->status[0] & IEC958_AES0_NONAUDIO) &&
(channels == 8);
int counter_step = single_stream ? ((channels + 1) >> 1) : 1;
for (channel = 0; channel < channels; ++channel) { for (channel = 0; channel < channels; ++channel) {
const char *src; const char *src;
uint32_t *dst; uint32_t *dst;
@ -205,7 +210,12 @@ static void snd_pcm_iec958_encode(snd_pcm_iec958_t *iec,
src_step = snd_pcm_channel_area_step(src_area); src_step = snd_pcm_channel_area_step(src_area);
dst_step = snd_pcm_channel_area_step(dst_area) / sizeof(uint32_t); dst_step = snd_pcm_channel_area_step(dst_area) / sizeof(uint32_t);
frames1 = frames; frames1 = frames;
if (single_stream)
iec->counter = (counter + (channel >> 1)) % 192;
else
iec->counter = counter; iec->counter = counter;
while (frames1-- > 0) { while (frames1-- > 0) {
goto *get; goto *get;
#define GET32_END after #define GET32_END after
@ -217,9 +227,11 @@ static void snd_pcm_iec958_encode(snd_pcm_iec958_t *iec,
*dst = sample; *dst = sample;
src += src_step; src += src_step;
dst += dst_step; dst += dst_step;
iec->counter++; iec->counter += counter_step;
iec->counter %= 192; iec->counter %= 192;
} }
if (single_stream) /* set counter to ch0 value for next iteration */
iec->counter = (counter + frames * counter_step) % 192;
} }
} }
#endif /* DOC_HIDDEN */ #endif /* DOC_HIDDEN */
@ -473,6 +485,7 @@ static const snd_pcm_ops_t snd_pcm_iec958_ops = {
* \param close_slave When set, the slave PCM handle is closed with copy PCM * \param close_slave When set, the slave PCM handle is closed with copy PCM
* \param status_bits The IEC958 status bits * \param status_bits The IEC958 status bits
* \param preamble_vals The preamble byte values * \param preamble_vals The preamble byte values
* \param hdmi_mode When set, enable HDMI compliant formatting
* \retval zero on success otherwise a negative error code * \retval zero on success otherwise a negative error code
* \warning Using of this function might be dangerous in the sense * \warning Using of this function might be dangerous in the sense
* of compatibility reasons. The prototype might be freely * of compatibility reasons. The prototype might be freely
@ -481,7 +494,8 @@ static const snd_pcm_ops_t snd_pcm_iec958_ops = {
int snd_pcm_iec958_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, int snd_pcm_iec958_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat,
snd_pcm_t *slave, int close_slave, snd_pcm_t *slave, int close_slave,
const unsigned char *status_bits, const unsigned char *status_bits,
const unsigned char *preamble_vals) const unsigned char *preamble_vals,
int hdmi_mode)
{ {
snd_pcm_t *pcm; snd_pcm_t *pcm;
snd_pcm_iec958_t *iec; snd_pcm_iec958_t *iec;
@ -519,6 +533,8 @@ int snd_pcm_iec958_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sfo
memcpy(iec->preamble, preamble_vals, 3); memcpy(iec->preamble, preamble_vals, 3);
iec->hdmi_mode = hdmi_mode;
err = snd_pcm_new(&pcm, SND_PCM_TYPE_IEC958, name, slave->stream, slave->mode); err = snd_pcm_new(&pcm, SND_PCM_TYPE_IEC958, name, slave->stream, slave->mode);
if (err < 0) { if (err < 0) {
free(iec); free(iec);
@ -566,9 +582,14 @@ pcm.name {
[preamble.z or preamble.b val] [preamble.z or preamble.b val]
[preamble.x or preamble.m val] [preamble.x or preamble.m val]
[preamble.y or preamble.w val] [preamble.y or preamble.w val]
[hdmi_mode true]
} }
\endcode \endcode
When <code>hdmi_mode</code> is true, 8-channel compressed data is
formatted as 4 contiguous frames of a single IEC958 stream as required
by the HDMI HBR specification.
\subsection pcm_plugins_iec958_funcref Function reference \subsection pcm_plugins_iec958_funcref Function reference
<UL> <UL>
@ -605,6 +626,7 @@ int _snd_pcm_iec958_open(snd_pcm_t **pcmp, const char *name,
unsigned char preamble_vals[3] = { unsigned char preamble_vals[3] = {
0x08, 0x02, 0x04 /* Z, X, Y */ 0x08, 0x02, 0x04 /* Z, X, Y */
}; };
int hdmi_mode = 0;
snd_config_for_each(i, next, conf) { snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i); snd_config_t *n = snd_config_iterator_entry(i);
@ -633,6 +655,13 @@ int _snd_pcm_iec958_open(snd_pcm_t **pcmp, const char *name,
preamble = n; preamble = n;
continue; continue;
} }
if (strcmp(id, "hdmi_mode") == 0) {
err = snd_config_get_bool(n);
if (err < 0)
continue;
hdmi_mode = err;
continue;
}
SNDERR("Unknown field %s", id); SNDERR("Unknown field %s", id);
return -EINVAL; return -EINVAL;
} }
@ -707,7 +736,7 @@ int _snd_pcm_iec958_open(snd_pcm_t **pcmp, const char *name,
return err; return err;
err = snd_pcm_iec958_open(pcmp, name, sformat, spcm, 1, err = snd_pcm_iec958_open(pcmp, name, sformat, spcm, 1,
status ? status_bits : NULL, status ? status_bits : NULL,
preamble_vals); preamble_vals, hdmi_mode);
if (err < 0) if (err < 0)
snd_pcm_close(spcm); snd_pcm_close(spcm);
return err; return err;