Reported by the camera session while verifying an unrelated I2C change, and passed to me to judge whether it is a bug or intended arbitration. It is a real limitation, and it is not the audio session contract refusing.
Symptom
On the Waveshare P4 panel board: open the microphone, then call play(), and it raises. Play first and then open the microphone and it sometimes succeeds and sometimes does not.
Not the session contract
AudioSession arbitrates owners of one shared codec, and this board correctly gives each direction its own session, since it has two codecs:
_SESSION = AudioSession(codec_factory=lambda: _codec(), duplex=False) # ES8311, playback
_INPUT_SESSION = AudioSession(codec_factory=lambda: _input_codec(), duplex=False) # ES7210, capture
Two separate sessions, so acquire() never sees a second owner and never raises. The contract permits simultaneous play and record.
What actually blocks it
Both directions construct the same I2S peripheral, board_peripherals.py around lines 91 and 108:
return I2S(0, sck=Pin(_SCLK), ws=Pin(_LRCK), sd=Pin(_DSDIN), mode=I2S.TX, ...) # audio_out
return I2S(0, sck=Pin(_SCLK), ws=Pin(_LRCK), sd=Pin(_ASDOUT), mode=I2S.RX, ...) # audio_in
machine.I2S takes a direction at construction and one id cannot be both, so the second open fails. The order-dependence and the intermittency are about whether the first instance has been released, not about arbitration.
The wiring is genuinely full duplex: the two codecs share the bit and word clocks and have separate data lines (_DSDIN into the codec, _ASDOUT out of it). So this is one peripheral that should be running duplex, not two peripherals.
Why it matters
Under design principle 1, a pairing the hardware permits that the design does not deliver is a defect in the design rather than a feature request. The board has a speaker and a microphone on one bus, the API exposes both, and the two cannot be used together. Anything wanting monitoring, a loopback, an effects path, or a voice interface hits this.
It also cost real time this week: an attempt to prove speaker output by acoustic loopback was inconclusive, and could not be run at all in the obvious way for this reason.
Options, none free
- Duplex
machine.I2S. Correct, and a port-level change: the ESP32 port would need to expose one id in both directions. Upstream question.
- A native duplex path in audiodev's I2S backend, bypassing
machine.I2S for boards whose codecs share a bus.
- Document the exclusion on the board and in
audiodev, so it fails with a clear message naming the cause instead of an OSError from the second open. This is the cheap half and should happen regardless of which of the above is chosen.
Third option first, in any case: today the failure names neither the peripheral nor the reason.
Reported by the camera session while verifying an unrelated I2C change, and passed to me to judge whether it is a bug or intended arbitration. It is a real limitation, and it is not the audio session contract refusing.
Symptom
On the Waveshare P4 panel board: open the microphone, then call
play(), and it raises. Play first and then open the microphone and it sometimes succeeds and sometimes does not.Not the session contract
AudioSessionarbitrates owners of one shared codec, and this board correctly gives each direction its own session, since it has two codecs:Two separate sessions, so
acquire()never sees a second owner and never raises. The contract permits simultaneous play and record.What actually blocks it
Both directions construct the same I2S peripheral,
board_peripherals.pyaround lines 91 and 108:machine.I2Stakes a direction at construction and one id cannot be both, so the second open fails. The order-dependence and the intermittency are about whether the first instance has been released, not about arbitration.The wiring is genuinely full duplex: the two codecs share the bit and word clocks and have separate data lines (
_DSDINinto the codec,_ASDOUTout of it). So this is one peripheral that should be running duplex, not two peripherals.Why it matters
Under design principle 1, a pairing the hardware permits that the design does not deliver is a defect in the design rather than a feature request. The board has a speaker and a microphone on one bus, the API exposes both, and the two cannot be used together. Anything wanting monitoring, a loopback, an effects path, or a voice interface hits this.
It also cost real time this week: an attempt to prove speaker output by acoustic loopback was inconclusive, and could not be run at all in the obvious way for this reason.
Options, none free
machine.I2S. Correct, and a port-level change: the ESP32 port would need to expose one id in both directions. Upstream question.machine.I2Sfor boards whose codecs share a bus.audiodev, so it fails with a clear message naming the cause instead of anOSErrorfrom the second open. This is the cheap half and should happen regardless of which of the above is chosen.Third option first, in any case: today the failure names neither the peripheral nor the reason.