+
+static int vlc_ceil_log2( const unsigned int val )
+{
+ int n = 31 - clz(val);
+ if ((1U << n) != val)
+ n++;
+
+ return n;
+}
+
+static void OpusSetup(demux_t *demux, uint8_t *p, size_t len, es_format_t *p_fmt)
+{
+ OpusHeader h;
+
+ /* default mapping */
+ static const unsigned char map[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
+ memcpy(h.stream_map, map, sizeof(map));
+
+ int csc, mapping;
+ int channels = 0;
+ int stream_count = 0;
+ int ccc = p[1]; // channel_config_code
+ if (ccc <= 8) {
+ channels = ccc;
+ if (channels)
+ mapping = channels > 2;
+ else {
+ mapping = 255;
+ channels = 2; // dual mono
+ }
+ static const uint8_t p_csc[8] = { 0, 1, 1, 2, 2, 2, 3, 3 };
+ csc = p_csc[channels - 1];
+ stream_count = channels - csc;
+
+ static const uint8_t map[6][7] = {
+ { 2,1 },
+ { 1,2,3 },
+ { 4,1,2,3 },
+ { 4,1,2,3,5 },
+ { 4,1,2,3,5,6 },
+ { 6,1,2,3,4,5,7 },
+ };
+ if (channels > 2)
+ memcpy(&h.stream_map[1], map[channels-3], channels - 1);
+ } else if (ccc == 0x81) {
+ if (len < 4)
+ goto explicit_config_too_short;
+
+ channels = p[2];
+ mapping = p[3];
+ csc = 0;
+ if (mapping) {
+ bs_t s;
+ bs_init(&s, &p[4], len - 4);
+ stream_count = 1;
+ if (channels) {
+ int bits = vlc_ceil_log2(channels);
+ if (s.i_left < bits)
+ goto explicit_config_too_short;
+ stream_count = bs_read(&s, bits) + 1;
+ bits = vlc_ceil_log2(stream_count + 1);
+ if (s.i_left < bits)
+ goto explicit_config_too_short;
+ csc = bs_read(&s, bits);
+ }
+ int channel_bits = vlc_ceil_log2(stream_count + csc + 1);
+ if (s.i_left < channels * channel_bits)
+ goto explicit_config_too_short;
+
+ unsigned char silence = (1U << (stream_count + csc + 1)) - 1;
+ for (int i = 0; i < channels; i++) {
+ unsigned char m = bs_read(&s, channel_bits);
+ if (m == silence)
+ m = 0xff;
+ h.stream_map[i] = m;
+ }
+ }
+ } else if (ccc >= 0x80 && ccc <= 0x88) {
+ channels = ccc - 0x80;
+ if (channels)
+ mapping = 1;
+ else {
+ mapping = 255;
+ channels = 2; // dual mono
+ }
+ csc = 0;
+ stream_count = channels;
+ } else {
+ msg_Err(demux, "Opus channel configuration 0x%.2x is reserved", ccc);
+ }
+
+ if (!channels) {
+ msg_Err(demux, "Opus channel configuration 0x%.2x not supported yet", p[1]);
+ return;
+ }
+
+ opus_prepare_header(channels, 0, &h);
+ h.preskip = 0;
+ h.input_sample_rate = 48000;
+ h.nb_coupled = csc;
+ h.nb_streams = channels - csc;
+ h.channel_mapping = mapping;
+
+ if (h.channels) {
+ opus_write_header((uint8_t**)&p_fmt->p_extra, &p_fmt->i_extra, &h, NULL /* FIXME */);
+ if (p_fmt->p_extra) {
+ p_fmt->i_cat = AUDIO_ES;
+ p_fmt->i_codec = VLC_CODEC_OPUS;
+ p_fmt->audio.i_channels = h.channels;
+ p_fmt->audio.i_rate = 48000;
+ }
+ }
+
+ return;
+
+explicit_config_too_short:
+ msg_Err(demux, "Opus descriptor too short");
+}
+