1 /* Icecast
2  *
3  * This program is distributed under the GNU General Public License, version 2.
4  * A copy of this license is included with this source.
5  *
6  * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7  *                      Michael Smith <msmith@xiph.org>,
8  *                      oddsock <oddsock@xiph.org>,
9  *                      Karl Heyes <karl@xiph.org>
10  *                      and others (see AUTHORS for details).
11  */
12 
13 
14 /* Ogg codec handler for vorbis streams */
15 
16 #ifdef HAVE_CONFIG_H
17 #include <config.h>
18 #endif
19 
20 #include <stdlib.h>
21 #include <ogg/ogg.h>
22 #include <vorbis/codec.h>
23 #include <memory.h>
24 #include <string.h>
25 
26 #include "refbuf.h"
27 #include "source.h"
28 #include "client.h"
29 
30 #include "format_ogg.h"
31 #include "stats.h"
32 #include "format.h"
33 
34 #define CATMODULE "format-vorbis"
35 #include "logging.h"
36 
37 
38 typedef struct vorbis_codec_tag
39 {
40     vorbis_info vi;
41     vorbis_comment vc;
42 
43     int rebuild_comment;
44     int stream_notify;
45     int initial_audio_page;
46 
47     ogg_stream_state new_os;
48     int page_samples_trigger;
49     ogg_int64_t prev_granulepos;
50     ogg_packet *prev_packet;
51     ogg_int64_t granulepos;
52     ogg_int64_t initial_page_granulepos;
53     ogg_int64_t samples_in_page;
54     int prev_window;
55     int initial_audio_packet;
56 
57     ogg_page bos_page;
58     ogg_packet *header [3];
59     ogg_int64_t prev_page_samples;
60 
61     int (*process_packet)(ogg_state_t *ogg_info, ogg_codec_t *codec);
62     refbuf_t *(*get_buffer_page)(ogg_state_t *ogg_info, ogg_codec_t *codec);
63 
64 } vorbis_codec_t;
65 
66 static int process_vorbis_headers (ogg_state_t *ogg_info, ogg_codec_t *codec);
67 static refbuf_t *process_vorbis_page (ogg_state_t *ogg_info,
68                 ogg_codec_t *codec, ogg_page *page);
69 static refbuf_t *process_vorbis (ogg_state_t *ogg_info, ogg_codec_t *codec);
70 static void vorbis_set_tag (format_plugin_t *plugin, const char *tag, const char *value, const char *charset);
71 
72 
free_ogg_packet(ogg_packet * packet)73 static void free_ogg_packet (ogg_packet *packet)
74 {
75     if (packet)
76     {
77         free (packet->packet);
78         free (packet);
79     }
80 }
81 
82 
vorbis_codec_free(ogg_state_t * ogg_info,ogg_codec_t * codec)83 static void vorbis_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec)
84 {
85     vorbis_codec_t *vorbis = codec->specific;
86 
87     ICECAST_LOG_DEBUG("freeing vorbis codec");
88     stats_event (ogg_info->mount, "audio_bitrate", NULL);
89     stats_event (ogg_info->mount, "audio_channels", NULL);
90     stats_event (ogg_info->mount, "audio_samplerate", NULL);
91     vorbis_info_clear (&vorbis->vi);
92     vorbis_comment_clear (&vorbis->vc);
93     ogg_stream_clear (&codec->os);
94     ogg_stream_clear (&vorbis->new_os);
95     free_ogg_packet (vorbis->header[0]);
96     free_ogg_packet (vorbis->header[1]);
97     free_ogg_packet (vorbis->header[2]);
98     free_ogg_packet (vorbis->prev_packet);
99     free (vorbis->bos_page.header);
100     free (vorbis);
101     free (codec);
102 }
103 
104 
copy_ogg_packet(ogg_packet * packet)105 static ogg_packet *copy_ogg_packet (ogg_packet *packet)
106 {
107     ogg_packet *next;
108     do
109     {
110         next = malloc (sizeof (ogg_packet));
111         if (next == NULL)
112             break;
113         memcpy (next, packet, sizeof (ogg_packet));
114         next->packet = malloc (next->bytes);
115         if (next->packet == NULL)
116             break;
117         memcpy (next->packet, packet->packet, next->bytes);
118         return next;
119     } while (0);
120 
121     if (next)
122         free (next);
123     return NULL;
124 }
125 
126 
add_audio_packet(vorbis_codec_t * source_vorbis,ogg_packet * packet)127 static void add_audio_packet (vorbis_codec_t *source_vorbis, ogg_packet *packet)
128 {
129     if (source_vorbis->initial_audio_packet)
130     {
131         packet->granulepos = 0;
132         source_vorbis->initial_audio_packet = 0;
133     }
134     else
135     {
136         source_vorbis->samples_in_page +=
137             (packet->granulepos - source_vorbis->prev_granulepos);
138         source_vorbis->prev_granulepos = packet->granulepos;
139         source_vorbis->granulepos += source_vorbis->prev_window;
140     }
141     ogg_stream_packetin (&source_vorbis->new_os, packet);
142 }
143 
144 
get_buffer_audio(ogg_state_t * ogg_info,ogg_codec_t * codec)145 static refbuf_t *get_buffer_audio (ogg_state_t *ogg_info, ogg_codec_t *codec)
146 {
147     refbuf_t *refbuf = NULL;
148     ogg_page page;
149     vorbis_codec_t *source_vorbis = codec->specific;
150     int (*get_ogg_page)(ogg_stream_state*, ogg_page *) = ogg_stream_pageout;
151 
152     if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger)
153         get_ogg_page = ogg_stream_flush;
154 
155     if (get_ogg_page (&source_vorbis->new_os, &page) > 0)
156     {
157         /* squeeze a page copy into a buffer */
158         source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples);
159         source_vorbis->prev_page_samples = ogg_page_granulepos (&page);
160 
161         refbuf = make_refbuf_with_page (&page);
162     }
163     return refbuf;
164 }
165 
166 
get_buffer_header(ogg_state_t * ogg_info,ogg_codec_t * codec)167 static refbuf_t *get_buffer_header (ogg_state_t *ogg_info, ogg_codec_t *codec)
168 {
169     int headers_flushed = 0;
170     ogg_page page;
171     vorbis_codec_t *source_vorbis = codec->specific;
172 
173     while (ogg_stream_flush (&source_vorbis->new_os, &page) > 0)
174     {
175         format_ogg_attach_header (ogg_info, &page);
176         headers_flushed = 1;
177     }
178     if (headers_flushed)
179     {
180         source_vorbis->get_buffer_page = get_buffer_audio;
181     }
182     return NULL;
183 }
184 
185 
get_buffer_finished(ogg_state_t * ogg_info,ogg_codec_t * codec)186 static refbuf_t *get_buffer_finished (ogg_state_t *ogg_info, ogg_codec_t *codec)
187 {
188     vorbis_codec_t *source_vorbis = codec->specific;
189     ogg_page page;
190     refbuf_t *refbuf;
191 
192     if (ogg_stream_flush (&source_vorbis->new_os, &page) > 0)
193     {
194         source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples);
195         source_vorbis->prev_page_samples = ogg_page_granulepos (&page);
196 
197         refbuf = make_refbuf_with_page (&page);
198         ICECAST_LOG_DEBUG("flushing page");
199         return refbuf;
200     }
201     ogg_stream_clear (&source_vorbis->new_os);
202     ogg_stream_init (&source_vorbis->new_os, rand());
203 
204     format_ogg_free_headers (ogg_info);
205     source_vorbis->get_buffer_page = NULL;
206     if (source_vorbis->prev_packet)
207         source_vorbis->process_packet = process_vorbis_headers;
208     else
209         source_vorbis->process_packet = NULL;
210 
211     if (source_vorbis->initial_audio_packet == 0)
212         source_vorbis->prev_window = 0;
213 
214     return NULL;
215 }
216 
217 
218 /* push last packet into stream marked with eos */
initiate_flush(vorbis_codec_t * source_vorbis)219 static void initiate_flush (vorbis_codec_t *source_vorbis)
220 {
221     if (source_vorbis->prev_packet)
222     {
223         /* insert prev_packet with eos */
224         ICECAST_LOG_DEBUG("adding EOS packet");
225         source_vorbis->prev_packet->e_o_s = 1;
226         add_audio_packet (source_vorbis, source_vorbis->prev_packet);
227         source_vorbis->prev_packet->e_o_s = 0;
228     }
229     source_vorbis->get_buffer_page = get_buffer_finished;
230     source_vorbis->initial_audio_packet = 1;
231 }
232 
233 
234 /* process the vorbis audio packets. Here we just take each packet out
235  * and add them into the new stream, flushing after so many samples. We
236  * also check if an new headers are requested after each processed page
237  */
process_vorbis_audio(ogg_state_t * ogg_info,ogg_codec_t * codec)238 static int process_vorbis_audio (ogg_state_t *ogg_info, ogg_codec_t *codec)
239 {
240     vorbis_codec_t *source_vorbis = codec->specific;
241 
242     while (1)
243     {
244         int window;
245         ogg_packet packet;
246 
247         /* now, lets extract what packets we can */
248         if (ogg_stream_packetout (&codec->os, &packet) <= 0)
249             break;
250 
251         /* calculate granulepos for the packet */
252         window = vorbis_packet_blocksize (&source_vorbis->vi, &packet) / 4;
253 
254         source_vorbis->granulepos += window;
255         if (source_vorbis->prev_packet)
256         {
257             ogg_packet *prev_packet = source_vorbis->prev_packet;
258 
259             add_audio_packet (source_vorbis, prev_packet);
260             free_ogg_packet (prev_packet);
261 
262             /* check for short values on first initial page */
263             if (packet . packetno == 4)
264             {
265                 if (source_vorbis->initial_page_granulepos < source_vorbis->granulepos)
266                 {
267                     source_vorbis->granulepos -= source_vorbis->initial_page_granulepos;
268                     source_vorbis->samples_in_page = source_vorbis->page_samples_trigger;
269                 }
270             }
271             /* check for long values on first page */
272             if (packet.granulepos == source_vorbis->initial_page_granulepos)
273             {
274                 if (source_vorbis->initial_page_granulepos > source_vorbis->granulepos)
275                     source_vorbis->granulepos = source_vorbis->initial_page_granulepos;
276             }
277 
278             if (packet.e_o_s == 0)
279                 packet . granulepos = source_vorbis->granulepos;
280         }
281         else
282         {
283             packet . granulepos = 0;
284         }
285 
286         /* store the current packet details */
287         source_vorbis->prev_window = window;
288         source_vorbis->prev_packet = copy_ogg_packet (&packet);
289         if (packet.e_o_s)
290         {
291             initiate_flush (source_vorbis);
292             free_ogg_packet (source_vorbis->prev_packet);
293             source_vorbis->prev_packet = NULL;
294             return 1;
295         }
296 
297         /* allow for pages to be flushed if there's over a certain number of samples */
298         if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger)
299             return 1;
300     }
301     if (source_vorbis->stream_notify)
302     {
303         initiate_flush (source_vorbis);
304         source_vorbis->stream_notify = 0;
305         return 1;
306     }
307     return -1;
308 }
309 
310 
311 /* This handles the headers at the backend, here we insert the header packets
312  * we want for the queue.
313  */
process_vorbis_headers(ogg_state_t * ogg_info,ogg_codec_t * codec)314 static int process_vorbis_headers (ogg_state_t *ogg_info, ogg_codec_t *codec)
315 {
316     vorbis_codec_t *source_vorbis = codec->specific;
317 
318     if (source_vorbis->header [0] == NULL)
319         return 0;
320 
321     ICECAST_LOG_DEBUG("Adding the 3 header packets");
322     ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [0]);
323     /* NOTE: we could build a separate comment packet each time */
324     if (source_vorbis->rebuild_comment)
325     {
326         vorbis_comment vc;
327         ogg_packet header;
328         ice_config_t *config;
329 
330         vorbis_comment_init (&vc);
331         if (ogg_info->artist)
332             vorbis_comment_add_tag (&vc, "artist", ogg_info->artist);
333         if (ogg_info->title)
334             vorbis_comment_add_tag (&vc, "title", ogg_info->title);
335         config = config_get_config();
336         vorbis_comment_add_tag (&vc, "server", config->server_id);
337         config_release_config();
338         vorbis_commentheader_out (&vc, &header);
339 
340         ogg_stream_packetin (&source_vorbis->new_os, &header);
341         vorbis_comment_clear (&vc);
342         ogg_packet_clear (&header);
343     }
344     else
345         ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [1]);
346     ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [2]);
347     source_vorbis->rebuild_comment = 0;
348 
349     ogg_info->log_metadata = 1;
350     source_vorbis->get_buffer_page = get_buffer_header;
351     source_vorbis->process_packet = process_vorbis_audio;
352     source_vorbis->granulepos = source_vorbis->prev_window;
353     source_vorbis->initial_audio_packet = 1;
354     return 1;
355 }
356 
357 
358 /* check if the provided BOS page is the start of a vorbis stream. If so
359  * then setup a structure so it can be used
360  */
initial_vorbis_page(format_plugin_t * plugin,ogg_page * page)361 ogg_codec_t *initial_vorbis_page (format_plugin_t *plugin, ogg_page *page)
362 {
363     ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t));
364     ogg_packet packet;
365 
366     vorbis_codec_t *vorbis = calloc (1, sizeof (vorbis_codec_t));
367 
368     ogg_stream_init (&codec->os, ogg_page_serialno (page));
369     ogg_stream_pagein (&codec->os, page);
370 
371     vorbis_info_init (&vorbis->vi);
372     vorbis_comment_init (&vorbis->vc);
373 
374     ogg_stream_packetout (&codec->os, &packet);
375 
376     ICECAST_LOG_DEBUG("checking for vorbis codec");
377     if (vorbis_synthesis_headerin (&vorbis->vi, &vorbis->vc, &packet) < 0)
378     {
379         ogg_stream_clear (&codec->os);
380         vorbis_info_clear (&vorbis->vi);
381         vorbis_comment_clear (&vorbis->vc);
382         free (vorbis);
383         free (codec);
384         return NULL;
385     }
386     ICECAST_LOG_INFO("seen initial vorbis header");
387     codec->specific = vorbis;
388     codec->codec_free = vorbis_codec_free;
389     codec->headers = 1;
390     codec->name = "Vorbis";
391 
392     free_ogg_packet (vorbis->header[0]);
393     free_ogg_packet (vorbis->header[1]);
394     free_ogg_packet (vorbis->header[2]);
395     memset (vorbis->header, 0, sizeof (vorbis->header));
396     vorbis->header [0] = copy_ogg_packet (&packet);
397     ogg_stream_init (&vorbis->new_os, rand());
398 
399     codec->process_page = process_vorbis_page;
400     codec->process = process_vorbis;
401     plugin->set_tag = vorbis_set_tag;
402 
403     vorbis->bos_page.header = malloc (page->header_len + page->body_len);
404 
405     memcpy (vorbis->bos_page.header, page->header, page->header_len);
406     vorbis->bos_page.header_len = page->header_len;
407 
408     vorbis->bos_page.body = vorbis->bos_page.header + page->header_len;
409     memcpy (vorbis->bos_page.body, page->body, page->body_len);
410     vorbis->bos_page.body_len = page->body_len;
411 
412     return codec;
413 }
414 
415 
416 /* called from the admin interface, here we update the artist/title info
417  * and schedule a new set of header pages
418  */
vorbis_set_tag(format_plugin_t * plugin,const char * tag,const char * in_value,const char * charset)419 static void vorbis_set_tag (format_plugin_t *plugin, const char *tag, const char *in_value, const char *charset)
420 {
421     ogg_state_t *ogg_info = plugin->_state;
422     ogg_codec_t *codec = ogg_info->codecs;
423     vorbis_codec_t *source_vorbis;
424     char *value;
425 
426     /* avoid updating if multiple codecs in use */
427     if (codec && codec->next == NULL)
428         source_vorbis = codec->specific;
429     else
430         return;
431 
432     if (tag == NULL)
433     {
434         source_vorbis->stream_notify = 1;
435         source_vorbis->rebuild_comment = 1;
436         return;
437     }
438 
439     value = util_conv_string (in_value, charset, "UTF-8");
440     if (value == NULL)
441         value = strdup (in_value);
442 
443     if (strcmp (tag, "artist") == 0)
444     {
445         free (ogg_info->artist);
446         ogg_info->artist = value;
447     }
448     else if (strcmp (tag, "title") == 0)
449     {
450         free (ogg_info->title);
451         ogg_info->title = value;
452     }
453     else if (strcmp (tag, "song") == 0)
454     {
455         free (ogg_info->title);
456         ogg_info->title = value;
457     }
458     else
459         free (value);
460 }
461 
462 
463 /* main backend routine when rebuilding streams. Here we loop until we either
464  * have a refbuf to add onto the queue, or we want more data to process.
465  */
process_vorbis(ogg_state_t * ogg_info,ogg_codec_t * codec)466 static refbuf_t *process_vorbis (ogg_state_t *ogg_info, ogg_codec_t *codec)
467 {
468     vorbis_codec_t *source_vorbis = codec->specific;
469     refbuf_t *refbuf;
470 
471     while (1)
472     {
473         if (source_vorbis->get_buffer_page)
474         {
475             refbuf = source_vorbis->get_buffer_page (ogg_info, codec);
476             if (refbuf)
477                 return refbuf;
478         }
479 
480         if (source_vorbis->process_packet &&
481                 source_vorbis->process_packet (ogg_info, codec) > 0)
482             continue;
483         return NULL;
484     }
485 }
486 
487 
488 /* no processing of pages, just wrap them up in a refbuf and pass
489  * back for adding to the queue
490  */
process_vorbis_passthru_page(ogg_state_t * ogg_info,ogg_codec_t * codec,ogg_page * page)491 static refbuf_t *process_vorbis_passthru_page (ogg_state_t *ogg_info,
492         ogg_codec_t *codec, ogg_page *page)
493 {
494     return make_refbuf_with_page (page);
495 }
496 
497 
498 /* handle incoming page. as the stream is being rebuilt, we need to
499  * add all pages from the stream before processing packets
500  */
process_vorbis_page(ogg_state_t * ogg_info,ogg_codec_t * codec,ogg_page * page)501 static refbuf_t *process_vorbis_page (ogg_state_t *ogg_info,
502         ogg_codec_t *codec, ogg_page *page)
503 {
504     ogg_packet header;
505     vorbis_codec_t *source_vorbis = codec->specific;
506     char *comment;
507 
508     if (ogg_stream_pagein (&codec->os, page) < 0)
509     {
510         ogg_info->error = 1;
511         return NULL;
512     }
513     if (codec->headers == 3)
514     {
515         if (source_vorbis->initial_audio_page)
516         {
517             source_vorbis->initial_page_granulepos = ogg_page_granulepos (page);
518             source_vorbis->initial_audio_page = 0;
519         }
520         return NULL;
521     }
522 
523     while (codec->headers < 3)
524     {
525         /* now, lets extract the packets */
526         ICECAST_LOG_DEBUG("processing incoming header packet (%d)", codec->headers);
527 
528         if (ogg_stream_packetout (&codec->os, &header) <= 0)
529         {
530             if (ogg_info->codecs->next)
531                 format_ogg_attach_header (ogg_info, page);
532             return NULL;
533         }
534 
535         /* change comments here if need be */
536         if (vorbis_synthesis_headerin (&source_vorbis->vi, &source_vorbis->vc, &header) < 0)
537         {
538             ogg_info->error = 1;
539             ICECAST_LOG_WARN("Problem parsing ogg vorbis header");
540             return NULL;
541         }
542         header.granulepos = 0;
543         source_vorbis->header [codec->headers] = copy_ogg_packet (&header);
544         codec->headers++;
545     }
546     ICECAST_LOG_DEBUG("we have the header packets now");
547 
548     /* if vorbis is the only codec then allow rebuilding of the streams */
549     if (ogg_info->codecs->next == NULL)
550     {
551         /* set queued vorbis pages to contain about 1/2 of a second worth of samples */
552         source_vorbis->page_samples_trigger = source_vorbis->vi.rate / 2;
553         source_vorbis->process_packet = process_vorbis_headers;
554         source_vorbis->initial_audio_page = 1;
555     }
556     else
557     {
558         format_ogg_attach_header (ogg_info, &source_vorbis->bos_page);
559         format_ogg_attach_header (ogg_info, page);
560         codec->process_page = process_vorbis_passthru_page;
561     }
562 
563     free (ogg_info->title);
564     comment = vorbis_comment_query (&source_vorbis->vc, "TITLE", 0);
565     if (comment)
566         ogg_info->title = strdup (comment);
567     else
568         ogg_info->title = NULL;
569 
570     free (ogg_info->artist);
571     comment = vorbis_comment_query (&source_vorbis->vc, "ARTIST", 0);
572     if (comment)
573         ogg_info->artist = strdup (comment);
574     else
575         ogg_info->artist = NULL;
576     ogg_info->log_metadata = 1;
577 
578     stats_event_args (ogg_info->mount, "audio_samplerate", "%ld", (long)source_vorbis->vi.rate);
579     stats_event_args (ogg_info->mount, "audio_channels", "%ld", (long)source_vorbis->vi.channels);
580     stats_event_args (ogg_info->mount, "audio_bitrate", "%ld", (long)source_vorbis->vi.bitrate_nominal);
581     stats_event_args (ogg_info->mount, "ice-bitrate", "%ld", (long)source_vorbis->vi.bitrate_nominal/1000);
582 
583     return NULL;
584 }
585 
586