Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
678 lines (581 sloc) 18.3 KB
/* Icecast
*
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
* Michael Smith <msmith@xiph.org>,
* oddsock <oddsock@xiph.org>,
* Karl Heyes <karl@xiph.org>
* and others (see AUTHORS for details).
*/
/* format_ogg.c
*
* format plugin for Ogg
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ogg/ogg.h>
#include "refbuf.h"
#include "source.h"
#include "client.h"
#include "stats.h"
#include "format.h"
#include "format_ogg.h"
#include "format_vorbis.h"
#ifdef HAVE_THEORA
#include "format_theora.h"
#endif
#ifdef HAVE_SPEEX
#include "format_speex.h"
#endif
#include "format_opus.h"
#include "format_midi.h"
#include "format_flac.h"
#include "format_kate.h"
#include "format_skeleton.h"
#include "global.h"
#define CATMODULE "format-ogg"
#include "logging.h"
struct _ogg_state_tag;
static void format_ogg_free_plugin (format_plugin_t *plugin, client_t *client);
static int create_ogg_client_data(format_plugin_t *plugin, client_t *client);
static void free_ogg_client_data (client_t *client);
static int get_image (client_t *client, struct _format_plugin_tag *format);
static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf);
static refbuf_t *ogg_get_buffer (source_t *source);
static int write_buf_to_client (client_t *client);
static void apply_ogg_settings (format_plugin_t *format, mount_proxy *mount);
static void ogg_apply_client (format_plugin_t *plugin, client_t *client);
struct ogg_client
{
refbuf_t *headers;
refbuf_t *header_page;
unsigned pos;
int headers_sent;
};
refbuf_t *make_refbuf_with_page (ogg_codec_t *codec, ogg_page *page)
{
refbuf_t *refbuf;
if (codec && codec->filtered)
return NULL;
refbuf = refbuf_new (page->header_len + page->body_len);
memcpy (refbuf->data, page->header, page->header_len);
memcpy (refbuf->data+page->header_len, page->body, page->body_len);
return refbuf;
}
/* routine for taking the provided page (should be a header page) and
* placing it on the collection of header pages
*/
void format_ogg_attach_header (ogg_codec_t *codec, ogg_page *page)
{
ogg_state_t *ogg_info = codec->parent;
refbuf_t *refbuf;
if (codec->filtered)
return;
refbuf = make_refbuf_with_page (codec, page);
if (ogg_page_bos (page))
{
DEBUG0 ("attaching BOS page");
if (*ogg_info->bos_end == NULL)
ogg_info->header_pages_tail = refbuf;
refbuf->associated = *ogg_info->bos_end;
*ogg_info->bos_end = refbuf;
ogg_info->bos_end = (refbuf_t**)&refbuf->associated;
return;
}
DEBUG0 ("attaching header page");
if (ogg_info->header_pages_tail)
ogg_info->header_pages_tail->associated = refbuf;
ogg_info->header_pages_tail = refbuf;
if (ogg_info->header_pages == NULL)
ogg_info->header_pages = refbuf;
}
void format_ogg_free_headers (ogg_state_t *ogg_info)
{
/* release the header pages first */
DEBUG0 ("releasing header pages");
refbuf_release (ogg_info->header_pages);
ogg_info->header_pages = NULL;
ogg_info->header_pages_tail = NULL;
ogg_info->bos_end = &ogg_info->header_pages;
}
/* release the memory used for the codec and header pages from the module */
static void free_ogg_codecs (ogg_state_t *ogg_info)
{
ogg_codec_t *codec;
if (ogg_info == NULL)
return;
format_ogg_free_headers (ogg_info);
/* now free the codecs */
codec = ogg_info->codecs;
DEBUG0 ("freeing codecs");
while (codec)
{
ogg_codec_t *next = codec->next;
refbuf_release (codec->possible_start);
codec->codec_free (ogg_info, codec);
codec = next;
}
ogg_info->codecs = NULL;
ogg_info->current = NULL;
ogg_info->bos_completed = 0;
ogg_info->codec_count = 0;
}
int format_ogg_get_plugin (format_plugin_t *plugin)
{
ogg_state_t *state = calloc (1, sizeof (ogg_state_t));
state->use_url_metadata = 1;
plugin->get_buffer = ogg_get_buffer;
plugin->write_buf_to_client = write_buf_to_client;
plugin->write_buf_to_file = write_ogg_to_file;
plugin->create_client_data = create_ogg_client_data;
plugin->free_plugin = format_ogg_free_plugin;
plugin->get_image = get_image;
plugin->set_tag = NULL;
plugin->apply_settings = apply_ogg_settings;
plugin->apply_client = ogg_apply_client;
plugin->_state = state;
return 0;
}
static void ogg_apply_client (format_plugin_t *plugin, client_t *client)
{
ogg_state_t *state = plugin->_state;
state->mount = NULL;
ogg_sync_clear (&state->oy);
if (client == NULL)
return;
plugin->parser = client->parser;
if (plugin->parser)
{
const char *s = httpp_getvar (plugin->parser, "content-type");
if (s==NULL || strcmp (s, "application/x-ogg") == 0)
httpp_setvar (plugin->parser, "content-type", "application/ogg");
s = httpp_getvar (plugin->parser, "content-type");
if (s)
plugin->contenttype = strdup (s);
}
ogg_sync_init (&state->oy);
state->mount = plugin->mount;
state->bos_end = &state->header_pages;
}
static void format_ogg_free_plugin (format_plugin_t *plugin, client_t *client)
{
ogg_state_t *state = plugin->_state;
/* free memory associated with this plugin instance */
free_ogg_codecs (state);
free (state->artist);
free (state->title);
ogg_sync_clear (&state->oy);
free (state);
}
static void apply_ogg_settings (format_plugin_t *format, mount_proxy *mount)
{
ogg_state_t *ogg_info = format->_state;
if (mount == NULL || format == NULL || ogg_info == NULL)
return;
if (mount->filter_theora)
ogg_info->filter_theora = 1;
DEBUG1 ("filter for theora is %d", ogg_info->filter_theora);
ogg_info->use_url_metadata = mount->url_ogg_meta;
DEBUG1 ("metadata via url is %d", ogg_info->use_url_metadata);
ogg_info->passthrough = mount->ogg_passthrough;
DEBUG1 ("oggpassthrough is %d", ogg_info->passthrough);
ogg_info->admin_comments_only = mount->admin_comments_only;
DEBUG1 ("admin_comments_only is %d", ogg_info->admin_comments_only);
}
/* a new BOS page has been seen so check which codec it is */
static int process_initial_page (format_plugin_t *plugin, ogg_page *page)
{
ogg_state_t *ogg_info = plugin->_state;
ogg_codec_t *codec;
if (ogg_info->bos_completed)
{
ogg_info->bitrate = 0;
ogg_info->codec_sync = NULL;
/* need to zap old list of codecs when next group of BOS pages appear */
free_ogg_codecs (ogg_info);
}
do
{
if (ogg_info->codec_count > 10)
{
ERROR0 ("many codecs in stream, playing safe, dropping source");
ogg_info->error = 1;
return -1;
}
codec = initial_vorbis_page (plugin, page);
if (codec)
break;
#ifdef HAVE_THEORA
codec = initial_theora_page (plugin, page);
if (codec)
break;
#endif
codec = initial_skeleton_page (plugin, page);
if (codec)
break;
codec = initial_opus_page (plugin, page);
if (codec)
break;
codec = initial_midi_page (plugin, page);
if (codec)
break;
codec = initial_flac_page (plugin, page);
if (codec)
break;
codec = initial_kate_page (plugin, page);
if (codec)
break;
#ifdef HAVE_SPEEX
codec = initial_speex_page (plugin, page);
if (codec)
break;
#endif
/* any others */
ERROR0 ("Seen BOS page with unknown type");
ogg_info->error = 1;
return -1;
} while (0);
if (codec)
{
/* add codec to list */
codec->next = ogg_info->codecs;
ogg_info->codecs = codec;
ogg_info->codec_count++;
DEBUG2 ("%s codec has filter mark %d", codec->name, codec->filtered);
}
return 0;
}
/* This is called when there has been a change in the metadata. Usually
* artist and title are provided separately so here we update the stats
* and write log entry if required.
*/
static void update_comments (source_t *source)
{
ogg_state_t *ogg_info = source->format->_state;
char *title = ogg_info->title;
char *artist = ogg_info->artist;
char *metadata = NULL;
unsigned int len = 1; /* space for the nul byte at least */
ogg_codec_t *codec;
char codec_names [100] = "";
if (ogg_info->artist)
{
if (title)
{
len += strlen(artist) + strlen(title) + 3;
metadata = calloc (1, len);
snprintf (metadata, len, "%s - %s", artist, title);
}
else
{
len += strlen(artist);
metadata = calloc (1, len);
snprintf (metadata, len, "%s", artist);
}
}
else
{
if (title)
{
len += strlen (title);
metadata = calloc (1, len);
snprintf (metadata, len, "%s", title);
}
}
if (metadata)
{
logging_playlist (source->mount, metadata, source->listeners);
free (metadata);
}
stats_event (source->mount, "artist", artist);
stats_event (source->mount, "title", title);
stats_event_time (source->mount, "metadata_updated", STATS_GENERAL);
codec = ogg_info->codecs;
while (codec)
{
if (codec->name)
{
size_t len = strlen (codec_names);
size_t remaining = sizeof (codec_names) - len;
char *where = codec_names + len;
char *separator = "/";
if (len == 0)
separator = "";
snprintf (where, remaining, "%s%s", separator, codec->name);
}
codec = codec->next;
}
stats_event (source->mount, "subtype", codec_names);
yp_touch (source->mount, source->stats);
}
/* called when preparing a refbuf with audio data to be passed
* back for queueing
*/
static refbuf_t *complete_buffer (source_t *source, refbuf_t *refbuf)
{
ogg_state_t *ogg_info = source->format->_state;
refbuf->associated = ogg_info->header_pages;
refbuf_addref (refbuf->associated);
if (ogg_info->log_metadata)
{
update_comments (source);
ogg_info->log_metadata = 0;
}
/* listeners can start anywhere unless the codecs themselves are
* marking starting points */
if (ogg_info->codec_sync == NULL)
refbuf->flags |= SOURCE_BLOCK_SYNC;
source->client->queue_pos += refbuf->len;
return refbuf;
}
/* process the incoming page. this requires searching through the
* currently known codecs that have been seen in the stream
*/
static refbuf_t *process_ogg_page (ogg_state_t *ogg_info, ogg_page *page)
{
ogg_codec_t *codec = ogg_info->codecs;
refbuf_t *refbuf = NULL;
while (codec)
{
if (ogg_page_serialno (page) == codec->os.serialno)
{
if (codec->process_page)
refbuf = codec->process_page (ogg_info, codec, page);
break;
}
codec = codec->next;
}
ogg_info->current = codec;
return refbuf;
}
/* main plugin handler for getting a buffer for the queue. In here we
* just add an incoming page to the codecs and process it until either
* more data is needed or we prodice a buffer for the queue.
*/
static refbuf_t *ogg_get_buffer (source_t *source)
{
ogg_state_t *ogg_info = source->format->_state;
format_plugin_t *format = source->format;
char *data = NULL;
int bytes = 0, total = 0;
while (total < 15000)
{
while (1)
{
ogg_page page;
refbuf_t *refbuf = NULL;
ogg_codec_t *codec = ogg_info->current;
/* if a codec has just been given a page then process it */
if (codec && codec->process)
{
refbuf = codec->process (ogg_info, codec);
if (refbuf)
return complete_buffer (source, refbuf);
ogg_info->current = NULL;
}
if (ogg_sync_pageout (&ogg_info->oy, &page) > 0)
{
if (ogg_page_bos (&page))
{
process_initial_page (source->format, &page);
}
else
{
ogg_info->bos_completed = 1;
refbuf = process_ogg_page (ogg_info, &page);
}
if (ogg_info->error)
{
ERROR0 ("Problem processing stream");
source->flags &= ~SOURCE_RUNNING;
return NULL;
}
if (refbuf)
return complete_buffer (source, refbuf);
continue;
}
/* need more stream data */
break;
}
/* we need more data to continue getting pages */
data = ogg_sync_buffer (&ogg_info->oy, 4096);
bytes = client_read_bytes (source->client, data, 4096);
if (bytes <= 0)
{
ogg_sync_wrote (&ogg_info->oy, 0);
source->client->schedule_ms += 50;
break;
}
total += bytes;
format->read_bytes += bytes;
rate_add (source->in_bitrate, bytes, source->client->worker->current_time.tv_sec);
ogg_sync_wrote (&ogg_info->oy, bytes);
}
return NULL;
}
static int create_ogg_client_data (format_plugin_t *plugin, client_t *client)
{
struct ogg_client *client_data = calloc (1, sizeof (struct ogg_client));
int ret = -1;
if (client_data)
{
client_data->headers_sent = 1;
client->format_data = client_data;
client->free_client_data = free_ogg_client_data;
if (client->refbuf == NULL)
client->refbuf = refbuf_new (4096);
client->refbuf->len = 0;
httpp_setvar (client->parser, HTTPP_VAR_VERSION, "1.0"); // hack to avoid chunk
ret = format_general_headers (plugin, client);
}
return ret;
}
static void free_ogg_client_data (client_t *client)
{
free (client->format_data);
client->format_data = NULL;
}
/* send out the header pages. These are for all codecs but are
* in the order for the stream, ie BOS pages first
*/
static int send_ogg_headers (client_t *client, refbuf_t *headers)
{
struct ogg_client *client_data = client->format_data;
refbuf_t *refbuf;
int written = 0, loop = 10;
if (client->flags & CLIENT_HAS_MOVED)
{
/* if client has been moved then we need to send all header pages */
client_data->headers_sent = 1;
client->flags &= ~CLIENT_HAS_MOVED;
}
if (client_data->headers_sent)
{
client_data->header_page = headers;
client_data->pos = 0;
client_data->headers_sent = 0;
}
refbuf = client_data->header_page;
while (refbuf)
{
char *data = refbuf->data + client_data->pos;
unsigned len = refbuf->len - client_data->pos;
int ret = -1;
if (len > 8192)
len = 8192;
if (client->connection.error == 0)
ret = client_send_bytes (client, data, len);
if (ret > 0)
{
written += ret;
client_data->pos += ret;
}
if (ret < (int)len)
{
client->schedule_ms += 50;
return written ? written : -1;
}
if (client_data->pos == refbuf->len)
{
refbuf = refbuf->associated;
client_data->header_page = refbuf;
client_data->pos = 0;
}
if (written > 10000 || loop == 0)
return written;
loop--;
}
client_data->headers_sent = 1;
client_data->headers = headers;
client_data->header_page = NULL;
return written;
}
/* main client write routine for sending ogg data. Each refbuf has a
* single page so we only need to determine if there are new headers
*/
static int write_buf_to_client (client_t *client)
{
refbuf_t *refbuf = client->refbuf;
char *buf = refbuf->data + client->pos;
unsigned len = refbuf->len - client->pos;
struct ogg_client *client_data = client->format_data;
int ret, written = 0;
do
{
if (client_data->headers != refbuf->associated)
{
ret = send_ogg_headers (client, refbuf->associated);
if (ret > 0)
written += ret;
if (client_data->headers_sent == 0)
break;
}
ret = client_send_bytes (client, buf, len);
if (ret > 0)
{
client->pos += ret;
client->queue_pos += ret;
written += ret;
client->counter += ret;
}
if (ret < (int)len)
client->schedule_ms += 50;
} while (0);
return written ? written : -1;
}
static int write_ogg_data (struct source_tag *source, refbuf_t *refbuf)
{
int ret = 1;
if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len)
{
WARN0 ("Write to dump file failed, disabling");
fclose (source->dumpfile);
source->dumpfile = NULL;
ret = 0;
}
return ret;
}
static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf)
{
ogg_state_t *ogg_info = source->format->_state;
if (ogg_info->file_headers != refbuf->associated)
{
refbuf_t *header = refbuf->associated;
while (header)
{
if (write_ogg_data (source, header) == 0)
return;
header = header->associated;
}
ogg_info->file_headers = refbuf->associated;
}
write_ogg_data (source, refbuf);
}
static int get_image (client_t *client, struct _format_plugin_tag *format)
{
const char *serialp = httpp_get_query_param (client->parser, "serial");
ogg_state_t *ogg_info = format->_state;
ogg_codec_t *codec = ogg_info->codecs;
long serial;
if (serialp)
serial = atoll (serialp);
while (codec)
{
if (serialp == NULL || serial == codec->os.serialno)
{
int ret = 0;
if (codec->get_image)
ret = codec->get_image (client, codec);
return ret;
}
codec = codec->next;
}
return 0;
}
You can’t perform that action at this time.