Skip to content

ASoC: hdmi-codec: Fall back to a valid channel allocation - #7604

Open
popcornmix wants to merge 2 commits into
raspberrypi:rpi-6.18.yfrom
popcornmix:speaker_alloc
Open

ASoC: hdmi-codec: Fall back to a valid channel allocation#7604
popcornmix wants to merge 2 commits into
raspberrypi:rpi-6.18.yfrom
popcornmix:speaker_alloc

Conversation

@popcornmix

Copy link
Copy Markdown
Collaborator

hdmi_codec_get_ch_alloc_table_idx() only succeeds when it finds a CEA channel allocation whose speaker mask is a subset of the one advertised in the sink's ELD. Every allocation in the table contains the front pair, and the only escape hatch requires the ELD speaker allocation byte to be entirely zero (the unplugged case), so a sink that advertises speakers without setting bit 0 (FL/FR) matches nothing at all and the function returns -EINVAL.

A Philips 77OLED809 does exactly that. Its Speaker Allocation Data Block is 0x7e, declaring LFE1, FC, BL/BR, BC, FLc/FRc and RLC/RRC but no front pair. CTA-861 defines bit 0 as FL/FR and attaches no further requirement to it, so edid-decode -c passes this EDID, but with the bit clear there is no allocation left to select. Playback then fails for every channel count, including the 2 channel LPCM that is the only uncompressed format the set accepts:

hdmi-audio-codec hdmi-audio-codec.1.auto: Not able to map channels to speakers (-22)
hdmi-audio-codec hdmi-audio-codec.1.auto: ASoC: error at snd_soc_pcm_dai_prepare on i2s-hifi: -22

This repeats for as long as userspace retries the open, and leaves no usable HDMI audio output.

Handle it the way hdmi_channel_allocation_spk_alloc_blk() in the HDA driver always has: return CA 0x00 for two channels or fewer without consulting the ELD at all, and where the speaker mask matches nothing, substitute the first allocation with the requested channel count rather than failing.

The fallback is restricted to sinks advertising more than the front pair. hdmi_codec_chmap_ctl_get() indexes the installed channel map array by ca_id, and hdmi_codec_eld_chmap() only installs the ca_id-indexed 8 channel maps under that same condition. hdmi_codec_stereo_chmaps has no entry beyond CA 0x00, so returning a higher ca_id with that array installed would read out of bounds.

Well-formed ELDs are unaffected: every speaker allocation that previously selected an allocation still selects the same one.

See: #7603

@popcornmix

Copy link
Copy Markdown
Collaborator Author

Tested successfully in #7603

@popcornmix
popcornmix marked this pull request as ready for review September 4, 2026 14:18
@popcornmix

Copy link
Copy Markdown
Collaborator Author

@6by9 ?

Note this follows the logic in hda driver.

@6by9

6by9 commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

I'd had a quick look at the HDA driver and could see the channels <= 2 check, but the rest was less obviously the same.

It looks like you're just setting up fallback within the one loop through channel_allocations, whilst HDA loops through it a second time if the mask wasn't found.
Your new code also returns 0 instead of -EINVAL in the event that no match at all was found. Is that actually correct/useful?

If returning 0 on error instead of -EINVAL is correct, then it can be simplified by:

  • initialising fallback to 0 (which is the default stereo map)
  • if (!fallback) in the loop,
  • return fallback after the loop.

(The if (fallback >= 0 && (spk_mask & ~(FL | FR))) looks abnormally special)

Comment thread sound/soc/codecs/hdmi-codec.c Outdated

/* hdmi_codec_eld_chmap() leaves only CA 0x00 indexable for these */
if (!(spk_mask & ~(FL | FR)))
return 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we end up returning 0 as the default value for fallback if we go through the loop when spk_mask doesn't include FL or FR?

@popcornmix

Copy link
Copy Markdown
Collaborator Author

Updated to match the shape I think you wanted, but I don't believe there is any change in behaviour.

I also believe we are functionally equivalent to HDA driver.
Only entries that already passed cap->n_ch != channels reach the fallback assignment, so the first channel-count match either returns or becomes fallback - precisely what HDA's second loop goes back and finds.

hdmi_codec_get_ch_alloc_table_idx() searches for a CEA channel allocation
whose speaker mask is a subset of the one advertised in the sink's ELD.
Every allocation in the table contains the front pair, and the only escape
hatch requires the ELD speaker allocation byte to be entirely zero (the
unplugged case), so a sink that advertises speakers without setting bit 0
(FL/FR) matches nothing and the function returns -EINVAL.

A Philips 77OLED809 does exactly that. Its Speaker Allocation Data Block
is 0x7e, declaring LFE1, FC, BL/BR, BC, FLc/FRc and RLC/RRC but no front
pair. CTA-861 defines bit 0 as FL/FR and attaches no further requirement
to it, so edid-decode -c passes this EDID, but with the bit clear there
is no allocation left to select. The set's only LPCM format is 2 channel,
so every playback attempt fails:

  hdmi-audio-codec hdmi-audio-codec.1.auto: Not able to map channels to speakers (-22)
  hdmi-audio-codec hdmi-audio-codec.1.auto: ASoC: error at snd_soc_pcm_dai_prepare on i2s-hifi: -22

This repeats for as long as userspace retries the open, and leaves no
usable HDMI audio output.

CA 0x00 describes plain stereo and is valid whatever the sink advertises,
so return it for two channels or fewer without consulting the ELD.
Compressed formats are unaffected, as they do not reach this function.

See: raspberrypi#7603

Signed-off-by: Dom Cobley <popcornmix@gmail.com>
@popcornmix

Copy link
Copy Markdown
Collaborator Author

Latest version just keeps the "accept 2 channels" part of patch, which is what resolved #7603 and is directly comparable with HDA.

Let's go with that for now. We can reconsider the other part of patch when find a user with an edid that works with HDA and not with us.

As an aside, vc4 declares channels_min = 1, channels_max = 8 but odd channel counts are impossible to use due to hdmi_codec_channel_alloc only listing even speaker counts.

HDA also can't do odd numbered counts due to speaker allocation, but at least declares that up front.

I believe odd channel counts should be usable (FL/FR/C or FL/FR/LFE seem like reasonable sound bar configs) - and I'm pretty sure I had that working with the firmware side driver.

@6by9 6by9 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was happy with the fallback, but just saw optimisations.

No quibble at all with this though - 2 channels is going to be CA 0x00.

@6by9

6by9 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

As an aside, vc4 declares channels_min = 1, channels_max = 8 but odd channel counts are impossible to use due to hdmi_codec_channel_alloc only listing even speaker counts.

HDA also can't do odd numbered counts due to speaker allocation, but at least declares that up front.

I believe odd channel counts should be usable (FL/FR/C or FL/FR/LFE seem like reasonable sound bar configs) - and I'm pretty sure I had that working with the firmware side driver.

I haven't looked in detail at the HDMI specs, but as the audio data gets IEC60958 encapsulated and that is inherently carrying a multiple of 2 channels, I suspect that is why they have rounded the channel count up to an even value.
eg CA 0x01 is defined as FL/FR/LFE in CTA-861-F Table 31, so one would expect 3 channels, but https://elixir.bootlin.com/linux/v7.2.2/source/sound/soc/codecs/hdmi-codec.c#L69 defines it as 4, with the 4th as SNDRV_CHMAP_NA.
However Table 27 for the Audio InfoFrame Data Byte 1 also lists all values of number of channels from 2 to 8 to be valid, not just even ones.

This may be more applicable on vc4 as we're relying on ALSA to do the IEC60958 encapsulation, so that bit of parsing may not be in the most obvious location.

I have no issues with adding a snd_pcm_hw_constraint_step call to the driver in an appropriate location though.

@popcornmix

Copy link
Copy Markdown
Collaborator Author

The two subframes per frame just means the channels are packed like:

IEC 60958 frame 0:   L0  R0
IEC 60958 frame 1:   C0  L1
IEC 60958 frame 2:   R1  C1
IEC 60958 frame 3:   L2  R2
...

so, there is no padding needed for odd numbers of channels.

The CPU DAI advertises 1 to 8 channels and nothing narrows that further,
so userspace can open a 3, 5 or 7 channel stream and have hw_params
succeed. hdmi_codec_channel_alloc[] only holds allocations for 2, 4, 6 and
8 channels, so hdmi_codec_get_ch_alloc_table_idx() then fails the stream
at prepare:

  hdmi-audio-codec hdmi-audio-codec.1.auto: Not able to map channels to speakers (-22)

Constrain the channel count to a multiple of two at startup so the odd
counts are never offered, rather than being accepted and rejected later.
This matches the HDA HDMI codec, which applies the same constraint.

Signed-off-by: Dom Cobley <popcornmix@gmail.com>
@popcornmix

Copy link
Copy Markdown
Collaborator Author

Added commit to set snd_pcm_hw_constraint_step to even channels.

Before:

$ speaker-test -D sysdefault:CARD=vc4hdmi0 -c3
speaker-test 1.2.8

Playback device is sysdefault:CARD=vc4hdmi0
Stream parameters are 48000Hz, S16_LE, 3 channels
Using 16 octaves of pink noise
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 4 to 43690
Period size range from 2 to 21845
Using max buffer size 43688
Periods = 4
Unable to set hw params for playback: Invalid argument
Setting of hwparams failed: Invalid argument

speaker-test thinks 3 channels is supported, but fails in set hw params.

After the commit:

$ speaker-test -D sysdefault:CARD=vc4hdmi0 -c3
speaker-test 1.2.8

Playback device is sysdefault:CARD=vc4hdmi0
Stream parameters are 48000Hz, S16_LE, 3 channels
Using 16 octaves of pink noise
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 2 to 32768
Period size range from 1 to 16384
Using max buffer size 32768
Periods = 4
was set period_size = 8192
was set buffer_size = 32768
 0 - Front Left
 1 - Front Right
 2 - LFE
Time per period = 8.029867
 0 - Front Left
 1 - Front Right
 2 - LFE

(analyser shows 4 channels, with one silent).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants