format_opus.c 6.59 KB
Newer Older
giles's avatar
giles committed
1
2
/* Icecast
 *
3
 *  This program is distributed under the GNU General Public License,
4
5
6
 *  version 2. A copy of this license is included with this source.
 *  At your option, this specific source file can also be distributed
 *  under the GNU GPL version 3.
giles's avatar
giles committed
7
 *
8
 * Copyright 2012,      David Richards, Mozilla Foundation,
giles's avatar
giles committed
9
 *                      and others (see AUTHORS for details).
10
 * Copyright 2014-2018, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
giles's avatar
giles committed
11
12
13
14
15
16
17
18
19
20
 */


/* Ogg codec handler for opus streams */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
21
#include <string.h>
giles's avatar
giles committed
22
23
24
#include <ogg/ogg.h>

#include "format_opus.h"
25
#include "stats.h"
giles's avatar
giles committed
26
27
28
29
30
31
32
33
#include "refbuf.h"
#include "client.h"

#define CATMODULE "format-opus"
#include "logging.h"

static void opus_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec)
{
34
35
    stats_event(ogg_info->mount, "audio_channels", NULL);
    stats_event(ogg_info->mount, "audio_samplerate", NULL);
Philipp Schafft's avatar
Philipp Schafft committed
36
37
    ogg_stream_clear(&codec->os);
    free(codec);
giles's avatar
giles committed
38
39
}

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
static uint32_t __read_header_u32be_unaligned(const unsigned char *in)
{
    uint32_t ret = 0;
    ret += in[3];
    ret <<= 8;
    ret += in[2];
    ret <<= 8;
    ret += in[1];
    ret <<= 8;
    ret += in[0];
    return ret;
}

static void __handle_header_opushead(ogg_state_t *ogg_info, ogg_packet *packet)
{
    if (packet->bytes < 19) {
        ICECAST_LOG_WARN("Bad Opus header: header too small, expected at least 19 byte, got %li", (long int)packet->bytes);
        return; /* Invalid OpusHead */
    }

    if ((packet->packet[8] & 0xF0) != 0) {
        ICECAST_LOG_WARN("Bad Opus header: bad header version, expected major 0, got %i", (int)packet->packet[8]);
        return; /* Invalid OpusHead Version */
    }

    stats_event_args(ogg_info->mount, "audio_channels", "%ld", (long int)packet->packet[9]);
    stats_event_args(ogg_info->mount, "audio_samplerate", "%ld", (long int)__read_header_u32be_unaligned(packet->packet+12));
}

static void __handle_header_opustags(ogg_state_t *ogg_info, ogg_packet *packet, format_plugin_t *plugin) 
{
    size_t comments;
    size_t next;
    size_t left = packet->bytes;
    size_t buflen = 0;
    char *buf = NULL;
    char *buf_new;
    const void *p = packet->packet;

    if (packet->bytes < 16) {
        ICECAST_LOG_WARN("Bad Opus header: header too small, expected at least 16 byte, got %li", (long int)packet->bytes);
        return; /* Invalid OpusHead */
    }

    /* Skip header "OpusTags" */
    p += 8;
    left -= 8;

    /* Now the vendor string follows. We just skip it. */
    next = __read_header_u32be_unaligned(p);
    p += 4;
    left -= 4;

    if (left < (next + 4)) {
        ICECAST_LOG_WARN("Bad Opus header: corrupted OpusTags header.");
        return;
    }
    p += next;
    left -= next;

    /* Next is the comment counter. */
    comments = __read_header_u32be_unaligned(p);
    p += 4;
    left -= 4;

    /* Ok, next (comments) blocks follows, each composed of 4 byte length followed by the data */
    if (left < (comments * 4)) {
        ICECAST_LOG_WARN("Bad Opus header: corrupted OpusTags header.");
        return;
    }

    vorbis_comment_clear(&plugin->vc);
    vorbis_comment_init(&plugin->vc);

    while (comments) {
        next = __read_header_u32be_unaligned(p);
        p += 4;
        left -= 4;

        if (left < next) {
            if (buf)
                free(buf);
            vorbis_comment_clear(&plugin->vc);
            vorbis_comment_init(&plugin->vc);
            ICECAST_LOG_WARN("Bad Opus header: corrupted OpusTags header.");
            return;
        }

        if ((next + 1) > buflen) {
            buf_new = realloc(buf, next + 1);
            if (buf_new) {
                buf = buf_new;
                buflen = next + 1;
            }
        }

        if (buflen >= (next + 1)) {
            memcpy(buf, p, next);
            buf[next] = 0;
            vorbis_comment_add(&plugin->vc, buf);
        }

        p += next;
        left -= next;

        comments--;
        if (comments && left < 4) {
            if (buf)
                free(buf);
            vorbis_comment_clear(&plugin->vc);
            vorbis_comment_init(&plugin->vc);
            ICECAST_LOG_WARN("Bad Opus header: corrupted OpusTags header.");
            return;
        }
    }

    if (buf)
        free(buf);

    ogg_info->log_metadata = 1;
}

static void __handle_header(ogg_state_t *ogg_info,
        ogg_codec_t *codec, ogg_packet *packet, format_plugin_t *plugin)
{
    ICECAST_LOG_DEBUG("Got Opus header");
    if (packet->bytes < 8) {
        ICECAST_LOG_DEBUG("Not a real header, less than 8 bytes in size.");
        return; /* packet is not a header */
    }

    if (strncmp((const char*)packet->packet, "OpusHead", 8) == 0) {
172
        ICECAST_LOG_DEBUG("Got Opus header: OpusHead");
173
174
        __handle_header_opushead(ogg_info, packet);
    } else if (strncmp((const char*)packet->packet, "OpusTags", 8) == 0) {
175
        ICECAST_LOG_DEBUG("Got Opus header: OpusTags");
176
177
178
179
180
181
        __handle_header_opustags(ogg_info, packet, plugin);
    } else {
        ICECAST_LOG_DEBUG("Unknown header or data.");
        return; /* Unknown header or data */
    }
}
giles's avatar
giles committed
182
183

static refbuf_t *process_opus_page (ogg_state_t *ogg_info,
184
        ogg_codec_t *codec, ogg_page *page, format_plugin_t *plugin)
giles's avatar
giles committed
185
186
187
188
189
190
191
192
193
194
195
196
{
    refbuf_t *refbuf;

    if (codec->headers < 2)
    {
        ogg_packet packet;

        ogg_stream_pagein (&codec->os, page);
        while (ogg_stream_packetout (&codec->os, &packet) > 0)
        {
           /* first time around (normal case) yields comments */
           codec->headers++;
197
            __handle_header(ogg_info, codec, &packet, plugin);
giles's avatar
giles committed
198
199
200
201
202
203
204
205
206
207
208
209
210
        }
        /* add header page to associated list */
        format_ogg_attach_header (ogg_info, page);
        return NULL;
    }
    refbuf = make_refbuf_with_page (page);
    return refbuf;
}


ogg_codec_t *initial_opus_page (format_plugin_t *plugin, ogg_page *page)
{
    ogg_state_t *ogg_info = plugin->_state;
Philipp Schafft's avatar
Philipp Schafft committed
211
    ogg_codec_t *codec = calloc(1, sizeof (ogg_codec_t));
giles's avatar
giles committed
212
213
    ogg_packet packet;

Philipp Schafft's avatar
Philipp Schafft committed
214
215
    ogg_stream_init(&codec->os, ogg_page_serialno (page));
    ogg_stream_pagein(&codec->os, page);
giles's avatar
giles committed
216

Philipp Schafft's avatar
Philipp Schafft committed
217
    ogg_stream_packetout(&codec->os, &packet);
giles's avatar
giles committed
218

219
    ICECAST_LOG_DEBUG("checking for opus codec");
220
    if (packet.bytes < 8 || strncmp((char *)packet.packet, "OpusHead", 8) != 0)
giles's avatar
giles committed
221
    {
Philipp Schafft's avatar
Philipp Schafft committed
222
223
        ogg_stream_clear(&codec->os);
        free(codec);
giles's avatar
giles committed
224
225
        return NULL;
    }
226
    __handle_header(ogg_info, codec, &packet, plugin);
227
    ICECAST_LOG_INFO("seen initial opus header");
giles's avatar
giles committed
228
229
    codec->process_page = process_opus_page;
    codec->codec_free = opus_codec_free;
230
    codec->name = "Opus";
giles's avatar
giles committed
231
232
233
234
235
    codec->headers = 1;
    format_ogg_attach_header (ogg_info, page);
    return codec;
}