// Copyright (C) 2026 half144 and PS5 React contributors // SPDX-License-Identifier: GPL-3.0-or-later // Additional attribution term: see LICENSE-ATTRIBUTION. #include "audio_sink.hpp" #include namespace audio_sink { namespace { constexpr std::size_t kBlock = 128; constexpr int kRates[7] = {32000, 44100, 48000, 88200, 96000, 176400, 192000}; // CTA-861 audio format codes, by the names FFmpeg and media servers use; '' for reserved. const char* codec_name(int code, int extension) { constexpr const char* kCodes[16] = {"", "pcm", "ac3", "mp2", "mp3", "mp2", "aac", "dts", "atrac", "dsd", "eac3", "dtshd", "truehd", "dst", "wmapro", ""}; if (code != 15) return kCodes[code]; switch (extension) { case 4: case 5: case 6: case 8: case 10: return "aac"; // HE-AAC, HE-AACv2, MPEG-4 AAC LC, with MPEG Surround case 7: return "dra"; case 11: return "mpegh"; case 12: return "ac4"; case 13: return "pcm"; // L-PCM 3D audio default: return ""; } } void add(Capabilities& caps, Format format) { for (Format& known : caps.formats) { if (known.codec != format.codec) continue; // A sink may list a codec twice (one entry per channel count): one entry with the most of each. known.channels = std::max(known.channels, format.channels); known.max_bitrate = std::max(known.max_bitrate, format.max_bitrate); for (int rate : format.rates) if (std::find(known.rates.begin(), known.rates.end(), rate) == known.rates.end()) known.rates.push_back(rate); for (int depth : format.bit_depths) if (std::find(known.bit_depths.begin(), known.bit_depths.end(), depth) == known.bit_depths.end()) known.bit_depths.push_back(depth); std::sort(known.rates.begin(), known.rates.end()); std::sort(known.bit_depths.begin(), known.bit_depths.end()); return; } caps.formats.push_back(std::move(format)); } // One Short Audio Descriptor: three bytes. void read_descriptor(Capabilities& caps, const std::uint8_t* sad) { const int code = (sad[0] >> 3) & 15, extension = sad[2] >> 3; const char* name = codec_name(code, extension); if (!*name) return; Format format; format.codec = name; format.channels = (sad[0] & 7) + 1; if (code == 15 && extension == 13) format.channels = ((sad[0] & 7) | ((sad[1] & 0x80) >> 4) | ((sad[2] & 0x80) >> 3)) + 1; for (int bit = 0; bit < 7; ++bit) if (sad[1] & (1 << bit)) format.rates.push_back(kRates[bit]); if (code == 1) { constexpr int kDepths[3] = {16, 20, 24}; for (int bit = 0; bit < 3; ++bit) if (sad[2] & (1 << bit)) format.bit_depths.push_back(kDepths[bit]); } else if (code >= 2 && code <= 8) { format.max_bitrate = sad[2] * 8000; } add(caps, std::move(format)); } void read_cta(Capabilities& caps, const std::uint8_t* block) { // Revision 1 has no data blocks; byte 2 is where the detailed timings start, and the data blocks end. const std::size_t end = std::min(block[2], kBlock - 1); bool basic_audio = block[1] >= 2 && (block[3] & 0x40); for (std::size_t at = 4; block[1] >= 3 && at < end;) { const int tag = block[at] >> 5, length = block[at] & 31; if (at + 1 + length > end) break; if (tag == 1) for (int i = 0; i + 3 <= length; i += 3) read_descriptor(caps, block + at + 1 + i); at += 1 + length; } // Basic audio: two-channel PCM at 32, 44.1 and 48 kHz, 16-bit, even with no descriptor saying so. if (basic_audio) add(caps, {"pcm", 2, {32000, 44100, 48000}, {16}, 0}); } bool takes(const Capabilities& caps, const char* codec) { for (const Format& format : caps.formats) if (format.codec == codec) return format.rates.empty() || std::find(format.rates.begin(), format.rates.end(), 48000) != format.rates.end(); return false; } } // namespace Capabilities parse_edid(const std::uint8_t* edid, std::size_t size) { Capabilities caps; if (!edid || size < kBlock) return caps; caps.known = true; for (int i = 0; i < 4; ++i) { const std::uint8_t* descriptor = edid + 54 + 18 * i; if (descriptor[0] || descriptor[1] || descriptor[2] || descriptor[3] != 0xfc) continue; for (int c = 5; c < 18 && descriptor[c] != 0x0a; ++c) caps.name += static_cast(descriptor[c]); while (!caps.name.empty() && caps.name.back() == ' ') caps.name.pop_back(); } const std::size_t blocks = std::min(size / kBlock, 1u + edid[126]); for (std::size_t b = 1; b < blocks; ++b) if (edid[b * kBlock] == 0x02) read_cta(caps, edid + b * kBlock); // An E-AC-3 decoder decodes AC-3 too, and a DTS-HD one the DTS core. caps.ac3 = takes(caps, "ac3") || takes(caps, "eac3"); caps.eac3 = takes(caps, "eac3"); caps.dts = takes(caps, "dts") || takes(caps, "dtshd"); for (const Format& format : caps.formats) if (format.codec == "pcm") caps.pcm_channels = std::max(caps.pcm_channels, format.channels); return caps; } Capabilities current() { std::vector edid; return host::sink_edid(edid) ? parse_edid(edid.data(), edid.size()) : Capabilities{}; } } // namespace audio_sink