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