Coverage Report

Created: 2026-04-12 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/codec/opus_header.c
Line
Count
Source
1
/* Copyright (C)2012 Xiph.Org Foundation
2
   File: opus_header.c
3
4
   Redistribution and use in source and binary forms, with or without
5
   modification, are permitted provided that the following conditions
6
   are met:
7
8
   - Redistributions of source code must retain the above copyright
9
   notice, this list of conditions and the following disclaimer.
10
11
   - Redistributions in binary form must reproduce the above copyright
12
   notice, this list of conditions and the following disclaimer in the
13
   documentation and/or other materials provided with the distribution.
14
15
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18
   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
19
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
*/
27
28
#ifdef HAVE_CONFIG_H
29
# include "config.h"
30
#endif
31
32
#include "opus_header.h"
33
#include <string.h>
34
#include <stdio.h>
35
#include <stdlib.h>
36
37
#include <vlc_common.h>
38
#include <vlc_codec.h>
39
#include "../demux/xiph.h"
40
41
/* Header contents:
42
  - "OpusHead" (64 bits)
43
  - version number (8 bits)
44
  - Channels C (8 bits)
45
  - Pre-skip (16 bits)
46
  - Sampling rate (32 bits)
47
  - Gain in dB (16 bits, S7.8)
48
  - Mapping (8 bits, 0=single stream (mono/stereo) 1=Vorbis mapping,
49
             2..254: reserved, 255: multistream with no mapping)
50
51
  - if (mapping != 0)
52
     - N = totel number of streams (8 bits)
53
     - M = number of paired streams (8 bits)
54
     - C times channel origin
55
          - if (C<2*M)
56
             - stream = byte/2
57
             - if (byte&0x1 == 0)
58
                 - left
59
               else
60
                 - right
61
          - else
62
             - stream = byte-M
63
*/
64
65
typedef struct {
66
    unsigned char *data;
67
    int maxlen;
68
    int pos;
69
} Packet;
70
71
typedef struct {
72
    const unsigned char *data;
73
    int maxlen;
74
    int pos;
75
} ROPacket;
76
77
static int write_uint32(Packet *p, uint32_t val)
78
20.2k
{
79
20.2k
    if (p->pos>p->maxlen-4)
80
0
        return 0;
81
20.2k
    SetDWLE(&p->data[p->pos], val);
82
20.2k
    p->pos += 4;
83
20.2k
    return 1;
84
20.2k
}
85
86
static int write_uint16(Packet *p, uint16_t val)
87
40.4k
{
88
40.4k
    if (p->pos>p->maxlen-2)
89
0
        return 0;
90
40.4k
    SetWLE(&p->data[p->pos], val);
91
40.4k
    p->pos += 2;
92
40.4k
    return 1;
93
40.4k
}
94
95
static int write_chars(Packet *p, const unsigned char *str, int nb_chars)
96
81.1k
{
97
81.1k
    if (p->pos>p->maxlen-nb_chars)
98
0
        return 0;
99
81.1k
    memcpy(&p->data[p->pos], str, nb_chars);
100
81.1k
    p->pos += nb_chars;
101
81.1k
    return 1;
102
81.1k
}
103
104
static int read_uint32(ROPacket *p, uint32_t *val)
105
24.7k
{
106
24.7k
    if (p->pos>p->maxlen-4)
107
0
        return 0;
108
24.7k
    *val = GetDWLE(&p->data[p->pos]);
109
24.7k
    p->pos += 4;
110
24.7k
    return 1;
111
24.7k
}
112
113
static int read_uint16(ROPacket *p, uint16_t *val)
114
49.4k
{
115
49.4k
    if (p->pos>p->maxlen-2)
116
0
        return 0;
117
49.4k
    *val = GetWLE(&p->data[p->pos]);
118
49.4k
    p->pos += 2;
119
49.4k
    return 1;
120
49.4k
}
121
122
static int read_chars(ROPacket *p, unsigned char *str, int nb_chars)
123
102k
{
124
102k
    if (p->pos>p->maxlen-nb_chars)
125
3.12k
        return 0;
126
99.3k
    memcpy(str, &p->data[p->pos], nb_chars);
127
99.3k
    p->pos += nb_chars;
128
99.3k
    return 1;
129
102k
}
130
131
int opus_header_parse(const unsigned char *packet, int len, OpusHeader *h)
132
24.8k
{
133
24.8k
    char str[9];
134
24.8k
    ROPacket p;
135
24.8k
    unsigned char ch;
136
24.8k
    uint16_t shortval;
137
138
24.8k
    p.data = packet;
139
24.8k
    p.maxlen = len;
140
24.8k
    p.pos = 0;
141
24.8k
    str[8] = 0;
142
24.8k
    if (len<19)return 0;
143
24.8k
    if (!read_chars(&p, (unsigned char*)str, 8))
144
0
        return 0;
145
24.8k
    if (memcmp(str, "OpusHead", 8)!=0)
146
0
        return 0;
147
148
24.8k
    if (!read_chars(&p, &ch, 1))
149
0
        return 0;
150
24.8k
    h->version = ch;
151
24.8k
    if((h->version&240) != 0) /* Only major version 0 supported. */
152
82
        return 0;
153
154
24.7k
    if (!read_chars(&p, &ch, 1))
155
0
        return 0;
156
24.7k
    h->channels = ch;
157
24.7k
    if (h->channels == 0)
158
27
        return 0;
159
160
24.7k
    if (!read_uint16(&p, &shortval))
161
0
        return 0;
162
24.7k
    h->preskip = shortval;
163
164
24.7k
    if (!read_uint32(&p, &h->input_sample_rate))
165
0
        return 0;
166
167
24.7k
    if (!read_uint16(&p, &shortval))
168
0
        return 0;
169
24.7k
    h->gain = (short)shortval;
170
171
24.7k
    if (!read_chars(&p, &ch, 1))
172
0
        return 0;
173
24.7k
    h->channel_mapping = ch;
174
175
24.7k
    if(h->channel_mapping == 0)
176
237
    {
177
237
        if(h->channels>2)
178
38
            return 0;
179
199
        h->nb_streams = 1;
180
199
        h->nb_coupled = h->channels>1;
181
199
        h->stream_map[0]=0;
182
199
        h->stream_map[1]=1;
183
199
    }
184
24.4k
    else if(h->channel_mapping < 4)
185
3.16k
    {
186
3.16k
        if (!read_chars(&p, &ch, 1))
187
3.12k
            return 0;
188
189
41
        if (ch<1)
190
0
            return 0;
191
41
        h->nb_streams = ch;
192
193
41
        if (!read_chars(&p, &ch, 1))
194
0
            return 0;
195
196
41
        if (ch > h->nb_streams)
197
3
            return 0;
198
38
        h->nb_coupled = ch;
199
200
        /* Multi-stream support */
201
38
        if(h->channel_mapping <= 2)
202
38
        {
203
38
            if (h->nb_coupled + h->nb_streams > 255)
204
2
                return 0;
205
230
            for (int i=0;i<h->channels;i++)
206
197
            {
207
197
                if (!read_chars(&p, &h->stream_map[i], 1))
208
3
                    return 0;
209
194
                if (h->stream_map[i]>(h->nb_streams+h->nb_coupled) && h->stream_map[i]!=255)
210
0
                    return 0;
211
194
            }
212
36
        }
213
0
        else /* Decoding Matrix */
214
0
        {
215
0
            if (h->nb_coupled + h->nb_streams > 255)
216
0
                return 0;
217
0
            int matrix_entries = h->channels * (h->nb_streams + h->nb_coupled);
218
0
            int matrix_size = len - p.pos;
219
0
            if(matrix_size < matrix_entries * 2)
220
0
                return 0;
221
0
            h->dmatrix = malloc(matrix_size);
222
0
            if(h->dmatrix == NULL)
223
0
                return 0;
224
0
            if(!read_chars(&p, h->dmatrix, matrix_size))
225
0
            {
226
0
                free(h->dmatrix);
227
0
                return 0;
228
0
            }
229
0
            h->dmatrix_size = matrix_size;
230
0
        }
231
38
    }
232
233
    /*For version 0/1 we know there won't be any more data
234
      so reject any that have data past the end.*/
235
21.5k
    if ((h->version==0 || h->version==1) && p.pos != len)
236
3
        return 0;
237
21.5k
    return 1;
238
21.5k
}
239
240
/*
241
 Comments will be stored in the Vorbis style.
242
 It is described in the "Structure" section of
243
    http://www.xiph.org/ogg/vorbis/doc/v-comment.html
244
245
 However, Opus and other non-vorbis formats omit the "framing_bit".
246
247
The comment header is decoded as follows:
248
  1) [vendor_length] = unsigned little endian 32 bits integer
249
  2) [vendor_string] = UTF-8 vector as [vendor_length] octets
250
  3) [user_comment_list_length] = unsigned little endian 32 bits integer
251
  4) iterate [user_comment_list_length] times {
252
     5) [length] = unsigned little endian 32 bits integer
253
     6) this iteration's user comment = UTF-8 vector as [length] octets
254
  }
255
  7) done.
256
*/
257
258
static char *comment_init(size_t *length, const char *vendor)
259
20.2k
{
260
    /*The 'vendor' field should be the actual encoding library used.*/
261
20.2k
    if (!vendor)
262
0
        vendor = "unknown";
263
20.2k
    size_t vendor_length = strlen(vendor);
264
265
20.2k
    size_t user_comment_list_length = 0;
266
20.2k
    size_t len = 8 + 4 + vendor_length + 4;
267
20.2k
    char *p = malloc(len);
268
20.2k
    if (p == NULL)
269
0
        return NULL;
270
271
20.2k
    memcpy(p, "OpusTags", 8);
272
20.2k
    SetDWLE(p + 8, vendor_length);
273
20.2k
    memcpy(p + 12, vendor, vendor_length);
274
20.2k
    SetDWLE(p + 12 + vendor_length, user_comment_list_length);
275
276
20.2k
    *length = len;
277
20.2k
    return p;
278
20.2k
}
279
280
static int comment_add(char **comments, size_t *length, const char *tag,
281
                       const char *val)
282
20.2k
{
283
20.2k
    char *p = *comments;
284
20.2k
    uint32_t vendor_length = GetDWLE(p + 8);
285
20.2k
    size_t user_comment_list_length = GetDWLE(p + 8 + 4 + vendor_length);
286
20.2k
    size_t tag_len = (tag ? strlen(tag) : 0);
287
20.2k
    size_t val_len = strlen(val);
288
20.2k
    size_t len = (*length) + 4 + tag_len + val_len;
289
290
20.2k
    char *reaced = realloc(p, len);
291
20.2k
    if (reaced == NULL)
292
0
        return 1;
293
20.2k
    p = reaced;
294
295
20.2k
    SetDWLE(p + *length, tag_len + val_len);          /* length of comment */
296
20.2k
    if (tag) memcpy(p + *length + 4, tag, tag_len);         /* comment */
297
20.2k
    memcpy(p + *length + 4 + tag_len, val, val_len);        /* comment */
298
20.2k
    SetDWLE(p + 8 + 4 + vendor_length, user_comment_list_length + 1);
299
20.2k
    *comments = p;
300
20.2k
    *length = len;
301
20.2k
    return 0;
302
20.2k
}
303
304
/* adds padding so that metadata can be updated without rewriting the whole file */
305
static int comment_pad(char **comments, size_t *length)
306
20.2k
{
307
20.2k
    const unsigned padding = 512; /* default from opus-tools */
308
309
20.2k
    if(SIZE_MAX - *length < padding + 255)
310
0
        return 1;
311
312
20.2k
    char *p = *comments;
313
    /* Make sure there is at least "padding" worth of padding free, and
314
       round up to the maximum that fits in the current ogg segments. */
315
20.2k
    size_t newlen = ((*length + padding) / 255 + 1) * 255 - 1;
316
20.2k
    char *reaced = realloc(p, newlen);
317
20.2k
    if (reaced == NULL)
318
0
        return 1;
319
20.2k
    p = reaced;
320
321
20.2k
    memset(p + *length, 0, newlen - *length);
322
20.2k
    *comments = p;
323
20.2k
    *length = newlen;
324
20.2k
    return 0;
325
20.2k
}
326
327
void opus_prepare_header(unsigned channels, unsigned rate, OpusHeader *header)
328
20.2k
{
329
20.2k
    header->version = 1;
330
20.2k
    header->channels = channels;
331
20.2k
    header->nb_streams = header->channels;
332
20.2k
    header->nb_coupled = 0;
333
20.2k
    header->input_sample_rate = rate;
334
20.2k
    header->gain = 0; // 0dB
335
20.2k
    header->channel_mapping = header->channels > 8 ? 255 :
336
20.2k
                              header->channels > 2;
337
20.2k
}
338
339
static int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len)
340
20.2k
{
341
20.2k
    Packet p;
342
20.2k
    unsigned char ch;
343
344
20.2k
    p.data = packet;
345
20.2k
    p.maxlen = len;
346
20.2k
    p.pos = 0;
347
20.2k
    if (len<19)return 0;
348
20.2k
    if (!write_chars(&p, (const unsigned char*)"OpusHead", 8))
349
0
        return 0;
350
    /* Version is 1 */
351
20.2k
    ch = 1;
352
20.2k
    if (!write_chars(&p, &ch, 1))
353
0
        return 0;
354
355
20.2k
    ch = h->channels;
356
20.2k
    if (!write_chars(&p, &ch, 1))
357
0
        return 0;
358
359
20.2k
    if (!write_uint16(&p, h->preskip))
360
0
        return 0;
361
362
20.2k
    if (!write_uint32(&p, h->input_sample_rate))
363
0
        return 0;
364
365
20.2k
    if (!write_uint16(&p, h->gain))
366
0
        return 0;
367
368
20.2k
    ch = h->channel_mapping;
369
20.2k
    if (!write_chars(&p, &ch, 1))
370
0
        return 0;
371
372
20.2k
    if (h->channel_mapping == 1)
373
33
    {
374
33
        ch = h->nb_streams;
375
33
        if (!write_chars(&p, &ch, 1))
376
0
            return 0;
377
378
33
        ch = h->nb_coupled;
379
33
        if (!write_chars(&p, &ch, 1))
380
0
            return 0;
381
382
        /* Multi-stream support */
383
227
        for (int i=0;i<h->channels;i++)
384
194
        {
385
194
            if (!write_chars(&p, &h->stream_map[i], 1))
386
0
                return 0;
387
194
        }
388
33
    }
389
390
20.2k
    return p.pos;
391
20.2k
}
392
393
int opus_write_header(uint8_t **p_extra, size_t *i_extra, OpusHeader *header, const char *vendor)
394
20.2k
{
395
20.2k
    unsigned char header_data[100];
396
20.2k
    const int packet_size = opus_header_to_packet(header, header_data,
397
20.2k
                                                  sizeof(header_data));
398
399
20.2k
    const unsigned char *data[2];
400
20.2k
    size_t size[2];
401
402
20.2k
    data[0] = header_data;
403
20.2k
    size[0] = packet_size;
404
405
20.2k
    size_t comments_length;
406
20.2k
    char *comments = comment_init(&comments_length, vendor);
407
20.2k
    if (!comments)
408
0
        return 1;
409
20.2k
    if (comment_add(&comments, &comments_length, "ENCODER=",
410
20.2k
                    "VLC media player"))
411
0
    {
412
0
        free(comments);
413
0
        return 1;
414
0
    }
415
416
20.2k
    if (comment_pad(&comments, &comments_length))
417
0
    {
418
0
        free(comments);
419
0
        return 1;
420
0
    }
421
422
20.2k
    data[1] = (unsigned char *) comments;
423
20.2k
    size[1] = comments_length;
424
425
20.2k
    *i_extra = 0;
426
20.2k
    *p_extra = NULL;
427
428
60.6k
    for (unsigned i = 0; i < ARRAY_SIZE(data); ++i)
429
40.4k
    {
430
40.4k
        if (xiph_AppendHeaders(i_extra, (void **) p_extra, size[i], data[i]))
431
0
        {
432
0
            *i_extra = 0;
433
0
            free(*p_extra);
434
0
            *p_extra = NULL;
435
0
        }
436
40.4k
    }
437
438
20.2k
    free(comments);
439
440
20.2k
    return 0;
441
20.2k
}
442
443
void opus_header_init(OpusHeader *h)
444
45.2k
{
445
45.2k
    h->version = 0;
446
45.2k
    h->channels = 0;
447
45.2k
    h->preskip = 3840; /* default is 80 ms */
448
45.2k
    h->input_sample_rate = 0; /* unknown */
449
45.2k
    h->gain = 0;
450
45.2k
    h->channel_mapping = 255; /* unknown */
451
45.2k
    h->nb_streams = 0;
452
45.2k
    h->nb_coupled = 0;
453
45.2k
    h->dmatrix_size = 0;
454
45.2k
    h->dmatrix = NULL;
455
45.2k
}
456
457
void opus_header_clean(OpusHeader *h)
458
45.2k
{
459
45.2k
    free(h->dmatrix);
460
45.2k
}