mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2025-02-02 15:07:36 -05:00
merge multi ogg codec handling. Handle theora and/or vorbis. Place new
clients before keyframe. For vorbis-only streams, perform rebuild to flush pages more frequently and to provide url updating mechanism for titles svn path=/icecast/trunk/icecast/; revision=8341
This commit is contained in:
parent
8249712940
commit
2bd23d9050
21
configure.in
21
configure.in
@ -73,9 +73,23 @@ XIPH_PATH_XSLT
|
||||
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$XSLT_CFLAGS])
|
||||
XIPH_VAR_PREPEND([XIPH_LIBS],[$XSLT_LIBS])
|
||||
|
||||
XIPH_PATH_VORBIS(, AC_MSG_ERROR([must have Ogg Vorbis v1.0 or above installed!]))
|
||||
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$VORBIS_CFLAGS])
|
||||
XIPH_VAR_PREPEND([XIPH_LIBS],[$VORBIS_LIBS])
|
||||
XIPH_PATH_VORBIS([
|
||||
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$VORBIS_CFLAGS])
|
||||
XIPH_VAR_PREPEND([XIPH_LIBS],[$VORBIS_LIBS])
|
||||
XIPH_VAR_APPEND([XIPH_LDFLAGS],[$VORBIS_LDFLAGS])
|
||||
ICECAST_OPTIONAL="$ICECAST_OPTIONAL format_vorbis.o"
|
||||
],
|
||||
[AC_MSG_ERROR([must have Ogg Vorbis v1.0 or above installed])
|
||||
])
|
||||
|
||||
XIPH_PATH_THEORA([
|
||||
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$THEORA_CFLAGS])
|
||||
XIPH_VAR_APPEND([XIPH_LDFLAGS],[$THEORA_LDFLAGS])
|
||||
XIPH_VAR_PREPEND([XIPH_LIBS],[$THEORA_LIBS])
|
||||
ICECAST_OPTIONAL="$ICECAST_OPTIONAL format_theora.o"
|
||||
],
|
||||
[ AC_MSG_WARN([Theora disabled!])
|
||||
])
|
||||
|
||||
ACX_PTHREAD(, AC_MSG_ERROR([POSIX threads missing]))
|
||||
XIPH_VAR_APPEND([XIPH_CFLAGS],[$PTHREAD_CFLAGS])
|
||||
@ -109,6 +123,7 @@ dnl Make substitutions
|
||||
AC_SUBST(XIPH_CPPFLAGS)
|
||||
AC_SUBST(XIPH_CFLAGS)
|
||||
AC_SUBST(XIPH_LIBS)
|
||||
AC_SUBST(XIPH_LDFLAGS)
|
||||
AC_SUBST(PTHREAD_CPPFLAGS)
|
||||
AC_SUBST(PTHREAD_CFLAGS)
|
||||
AC_SUBST(PTHREAD_LIBS)
|
||||
|
@ -6,13 +6,16 @@ SUBDIRS = avl thread httpp net log timing
|
||||
|
||||
bin_PROGRAMS = icecast
|
||||
|
||||
noinst_HEADERS = admin.h cfgfile.h os.h logging.h sighandler.h connection.h global.h\
|
||||
util.h slave.h source.h stats.h refbuf.h client.h format.h format_vorbis.h\
|
||||
compat.h format_mp3.h fserve.h xslt.h yp.h event.h auth.h md5.h
|
||||
icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c\
|
||||
util.c slave.c source.c stats.c refbuf.c client.c format.c format_vorbis.c\
|
||||
noinst_HEADERS = admin.h cfgfile.h os.h logging.h sighandler.h connection.h \
|
||||
global.h util.h slave.h source.h stats.h refbuf.h client.h format.h \
|
||||
compat.h format_mp3.h fserve.h xslt.h yp.h event.h md5.h \
|
||||
auth.h format_ogg.h \
|
||||
format_vorbis.h format_theora.h
|
||||
icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c \
|
||||
util.c slave.c source.c stats.c refbuf.c client.c format.c format_ogg.c \
|
||||
format_mp3.c xslt.c fserve.c event.c admin.c auth.c md5.c
|
||||
EXTRA_icecast_SOURCES = yp.c
|
||||
EXTRA_icecast_SOURCES = yp.c \
|
||||
format_vorbis.c format_theora.c
|
||||
|
||||
icecast_DEPENDENCIES = @ICECAST_OPTIONAL@ net/libicenet.la thread/libicethread.la \
|
||||
httpp/libicehttpp.la log/libicelog.la avl/libiceavl.la timing/libicetiming.la
|
||||
@ -20,6 +23,7 @@ icecast_LDADD = $(icecast_DEPENDENCIES) @XIPH_LIBS@
|
||||
|
||||
AM_CFLAGS = @XIPH_CFLAGS@
|
||||
AM_CPPFLAGS = @XIPH_CPPFLAGS@
|
||||
AM_LDFLAGS = @XIPH_LDFLAGS@
|
||||
|
||||
|
||||
debug:
|
||||
|
55
src/admin.c
55
src/admin.c
@ -825,18 +825,15 @@ static void command_fallback(client_t *client, source_t *source,
|
||||
static void command_metadata(client_t *client, source_t *source)
|
||||
{
|
||||
char *action;
|
||||
char *value;
|
||||
mp3_state *state;
|
||||
char *song, *title, *artist;
|
||||
format_plugin_t *plugin;
|
||||
|
||||
DEBUG0("Got metadata update request");
|
||||
|
||||
COMMAND_REQUIRE(client, "mode", action);
|
||||
COMMAND_REQUIRE(client, "song", value);
|
||||
|
||||
if (source->format->type == FORMAT_TYPE_VORBIS) {
|
||||
client_send_400 (client, "Cannot update metadata on vorbis streams");
|
||||
return;
|
||||
}
|
||||
COMMAND_OPTIONAL(client, "song", song);
|
||||
COMMAND_OPTIONAL(client, "title", title);
|
||||
COMMAND_OPTIONAL(client, "artist", artist);
|
||||
|
||||
if (strcmp (action, "updinfo") != 0)
|
||||
{
|
||||
@ -844,22 +841,32 @@ static void command_metadata(client_t *client, source_t *source)
|
||||
return;
|
||||
}
|
||||
|
||||
state = source->format->_state;
|
||||
plugin = source->format;
|
||||
|
||||
mp3_set_tag (source->format, "title", value);
|
||||
|
||||
DEBUG2("Metadata on mountpoint %s changed to \"%s\"",
|
||||
source->mount, value);
|
||||
stats_event(source->mount, "title", value);
|
||||
|
||||
/* At this point, we assume that the metadata passed in
|
||||
is encoded in UTF-8 */
|
||||
logging_playlist(source->mount, value, source->listeners);
|
||||
/* If we get an update on the mountpoint, force a
|
||||
yp touch */
|
||||
yp_touch (source->mount);
|
||||
if (plugin && plugin->set_tag)
|
||||
{
|
||||
if (song)
|
||||
{
|
||||
plugin->set_tag (plugin, "song", song);
|
||||
DEBUG2("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (artist && title)
|
||||
{
|
||||
plugin->set_tag (plugin, "title", title);
|
||||
plugin->set_tag (plugin, "artist", artist);
|
||||
INFO3("Metadata on mountpoint %s changed to \"%s - %s\"",
|
||||
source->mount, artist, title);
|
||||
}
|
||||
}
|
||||
|
||||
html_success(client, "Metadata update successful");
|
||||
}
|
||||
else
|
||||
{
|
||||
client_send_400 (client, "mountpoint will not accept URL updates");
|
||||
}
|
||||
}
|
||||
|
||||
static void command_shoutcast_metadata(client_t *client, source_t *source)
|
||||
@ -873,7 +880,7 @@ static void command_shoutcast_metadata(client_t *client, source_t *source)
|
||||
COMMAND_REQUIRE(client, "mode", action);
|
||||
COMMAND_REQUIRE(client, "song", value);
|
||||
|
||||
if (source->format->type == FORMAT_TYPE_VORBIS) {
|
||||
if (source->format->type == FORMAT_TYPE_OGG) {
|
||||
client_send_400 (client, "Cannot update metadata on vorbis streams");
|
||||
return;
|
||||
}
|
||||
@ -890,11 +897,7 @@ static void command_shoutcast_metadata(client_t *client, source_t *source)
|
||||
|
||||
DEBUG2("Metadata on mountpoint %s changed to \"%s\"",
|
||||
source->mount, value);
|
||||
stats_event(source->mount, "title", value);
|
||||
|
||||
/* If we get an update on the mountpoint, force a
|
||||
yp touch */
|
||||
yp_touch (source->mount);
|
||||
|
||||
html_success(client, "Metadata update successful");
|
||||
}
|
||||
|
@ -51,9 +51,9 @@
|
||||
format_type_t format_get_type(char *contenttype)
|
||||
{
|
||||
if(strcmp(contenttype, "application/x-ogg") == 0)
|
||||
return FORMAT_TYPE_VORBIS; /* Backwards compatibility */
|
||||
return FORMAT_TYPE_OGG; /* Backwards compatibility */
|
||||
else if(strcmp(contenttype, "application/ogg") == 0)
|
||||
return FORMAT_TYPE_VORBIS; /* Now blessed by IANA */
|
||||
return FORMAT_TYPE_OGG; /* Now blessed by IANA */
|
||||
else
|
||||
/* We default to the Generic format handler, which
|
||||
can handle many more formats than just mp3 */
|
||||
@ -65,8 +65,8 @@ int format_get_plugin(format_type_t type, source_t *source)
|
||||
int ret = -1;
|
||||
|
||||
switch (type) {
|
||||
case FORMAT_TYPE_VORBIS:
|
||||
ret = format_vorbis_get_plugin (source);
|
||||
case FORMAT_TYPE_OGG:
|
||||
ret = format_ogg_get_plugin (source);
|
||||
break;
|
||||
case FORMAT_TYPE_GENERIC:
|
||||
ret = format_mp3_get_plugin (source);
|
||||
|
@ -26,9 +26,9 @@ struct source_tag;
|
||||
|
||||
typedef enum _format_type_tag
|
||||
{
|
||||
FORMAT_TYPE_VORBIS,
|
||||
FORMAT_TYPE_GENERIC,
|
||||
FORMAT_ERROR /* No format, source not processable */
|
||||
FORMAT_ERROR, /* No format, source not processable */
|
||||
FORMAT_TYPE_OGG,
|
||||
FORMAT_TYPE_GENERIC
|
||||
} format_type_t;
|
||||
|
||||
typedef struct _format_plugin_tag
|
||||
@ -46,6 +46,7 @@ typedef struct _format_plugin_tag
|
||||
int (*create_client_data)(struct source_tag *source, client_t *client);
|
||||
void (*client_send_headers)(struct _format_plugin_tag *format,
|
||||
struct source_tag *source, client_t *client);
|
||||
void (*set_tag)(struct _format_plugin_tag *plugin, char *tag, char *value);
|
||||
void (*free_plugin)(struct _format_plugin_tag *self);
|
||||
|
||||
/* for internal state management */
|
||||
|
@ -80,7 +80,7 @@ int format_mp3_get_plugin (source_t *source)
|
||||
mp3_state *state = calloc(1, sizeof(mp3_state));
|
||||
refbuf_t *meta;
|
||||
|
||||
plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t));
|
||||
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
|
||||
|
||||
plugin->type = FORMAT_TYPE_GENERIC;
|
||||
plugin->get_buffer = mp3_get_no_meta;
|
||||
@ -89,6 +89,7 @@ int format_mp3_get_plugin (source_t *source)
|
||||
plugin->create_client_data = format_mp3_create_client_data;
|
||||
plugin->client_send_headers = format_mp3_send_headers;
|
||||
plugin->free_plugin = format_mp3_free_plugin;
|
||||
plugin->set_tag = mp3_set_tag;
|
||||
|
||||
plugin->contenttype = httpp_getvar (source->parser, "content-type");
|
||||
if (plugin->contenttype == NULL) {
|
||||
@ -179,6 +180,7 @@ static void filter_shoutcast_metadata (source_t *source, char *metadata, unsigne
|
||||
if (p)
|
||||
{
|
||||
memcpy (p, metadata+13, len);
|
||||
logging_playlist (source->mount, p, source->listeners);
|
||||
stats_event (source->mount, "title", p);
|
||||
yp_touch (source->mount);
|
||||
free (p);
|
||||
@ -421,6 +423,7 @@ static refbuf_t *mp3_get_no_meta (source_t *source)
|
||||
refbuf->len = bytes;
|
||||
refbuf->associated = source_mp3->metadata;
|
||||
refbuf_addref (source_mp3->metadata);
|
||||
refbuf->sync_point = 1;
|
||||
return refbuf;
|
||||
}
|
||||
refbuf_release (refbuf);
|
||||
@ -561,6 +564,7 @@ static refbuf_t *mp3_get_filter_meta (source_t *source)
|
||||
}
|
||||
refbuf->associated = source_mp3->metadata;
|
||||
refbuf_addref (source_mp3->metadata);
|
||||
refbuf->sync_point = 1;
|
||||
|
||||
return refbuf;
|
||||
}
|
||||
|
565
src/format_ogg.c
Normal file
565
src/format_ogg.c
Normal file
@ -0,0 +1,565 @@
|
||||
/* 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
|
||||
|
||||
#define CATMODULE "format-ogg"
|
||||
#include "logging.h"
|
||||
|
||||
struct _ogg_state_tag;
|
||||
|
||||
static void format_ogg_free_plugin (format_plugin_t *plugin);
|
||||
static int create_ogg_client_data(source_t *source, client_t *client);
|
||||
static void format_ogg_send_headers(format_plugin_t *self,
|
||||
source_t *source, client_t *client);
|
||||
static void free_ogg_client_data (client_t *client);
|
||||
|
||||
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 (format_plugin_t *self, 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_page *page)
|
||||
{
|
||||
refbuf_t *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_state_t *ogg_info, ogg_page *page)
|
||||
{
|
||||
refbuf_t *refbuf = make_refbuf_with_page (page);
|
||||
|
||||
if (ogg_page_bos (page))
|
||||
{
|
||||
DEBUG0 ("attaching BOS page");
|
||||
if (*ogg_info->bos_end == NULL)
|
||||
ogg_info->header_pages_tail = refbuf;
|
||||
refbuf->next = *ogg_info->bos_end;
|
||||
*ogg_info->bos_end = refbuf;
|
||||
ogg_info->bos_end = &refbuf->next;
|
||||
return;
|
||||
}
|
||||
DEBUG0 ("attaching header page");
|
||||
if (ogg_info->header_pages_tail)
|
||||
ogg_info->header_pages_tail->next = 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)
|
||||
{
|
||||
refbuf_t *header;
|
||||
|
||||
/* release the header pages first */
|
||||
DEBUG0 ("releasing header pages");
|
||||
header = ogg_info->header_pages;
|
||||
while (header)
|
||||
{
|
||||
refbuf_t *to_release = header;
|
||||
header = header->next;
|
||||
refbuf_release (to_release);
|
||||
}
|
||||
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;
|
||||
codec->codec_free (ogg_info, codec);
|
||||
codec = next;
|
||||
}
|
||||
ogg_info->codecs = NULL;
|
||||
ogg_info->current = NULL;
|
||||
ogg_info->bos_completed = 0;
|
||||
}
|
||||
|
||||
|
||||
int format_ogg_get_plugin (source_t *source)
|
||||
{
|
||||
format_plugin_t *plugin;
|
||||
ogg_state_t *state = calloc (1, sizeof (ogg_state_t));
|
||||
|
||||
plugin = (format_plugin_t *)calloc(1, sizeof(format_plugin_t));
|
||||
|
||||
plugin->type = FORMAT_TYPE_OGG;
|
||||
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->client_send_headers = format_ogg_send_headers;
|
||||
plugin->free_plugin = format_ogg_free_plugin;
|
||||
plugin->set_tag = NULL;
|
||||
plugin->contenttype = "application/ogg";
|
||||
|
||||
ogg_sync_init (&state->oy);
|
||||
|
||||
plugin->_state = state;
|
||||
source->format = plugin;
|
||||
state->mount = source->mount;
|
||||
state->bos_end = &state->header_pages;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void format_ogg_free_plugin (format_plugin_t *plugin)
|
||||
{
|
||||
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);
|
||||
|
||||
free (plugin);
|
||||
}
|
||||
|
||||
|
||||
/* 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
|
||||
{
|
||||
codec = initial_vorbis_page (plugin, page);
|
||||
if (codec)
|
||||
break;
|
||||
#ifdef HAVE_THEORA
|
||||
codec = initial_theora_page (plugin, page);
|
||||
if (codec)
|
||||
break;
|
||||
#endif
|
||||
/* any others */
|
||||
INFO0 ("Seen BOS page with unknown type");
|
||||
return -1;
|
||||
} while (0);
|
||||
|
||||
if (codec)
|
||||
{
|
||||
/* add codec to list */
|
||||
codec->next = ogg_info->codecs;
|
||||
ogg_info->codecs = codec;
|
||||
}
|
||||
|
||||
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 = 0;
|
||||
|
||||
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);
|
||||
yp_touch (source->mount);
|
||||
}
|
||||
|
||||
|
||||
/* 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_t *header = ogg_info->header_pages;
|
||||
|
||||
while (header)
|
||||
{
|
||||
refbuf_addref (header);
|
||||
header = header->next;
|
||||
}
|
||||
refbuf->associated = ogg_info->header_pages;
|
||||
|
||||
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->sync_point = 1;
|
||||
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;
|
||||
char *data = NULL;
|
||||
int bytes;
|
||||
|
||||
while (1)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
ogg_page page;
|
||||
refbuf_t *refbuf;
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
ogg_info->bos_completed = 1;
|
||||
refbuf = process_ogg_page (ogg_info, &page);
|
||||
if (ogg_info->error)
|
||||
{
|
||||
ERROR0 ("Problem processing stream");
|
||||
source->running = 0;
|
||||
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 = sock_read_bytes (source->con->sock, data, 4096);
|
||||
if (bytes < 0)
|
||||
{
|
||||
if (sock_recoverable (sock_error()))
|
||||
return NULL;
|
||||
WARN0 ("source connection has died");
|
||||
ogg_sync_wrote (&ogg_info->oy, 0);
|
||||
source->running = 0;
|
||||
return NULL;
|
||||
}
|
||||
if (bytes == 0)
|
||||
{
|
||||
INFO1 ("End of Stream %s", source->mount);
|
||||
ogg_sync_wrote (&ogg_info->oy, 0);
|
||||
source->running = 0;
|
||||
return NULL;
|
||||
}
|
||||
ogg_sync_wrote (&ogg_info->oy, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int create_ogg_client_data (source_t *source, 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;
|
||||
ret = 0;
|
||||
}
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
ret = client_send_bytes (client, data, len);
|
||||
if (ret > 0)
|
||||
written += ret;
|
||||
if (ret < (int)len)
|
||||
return written ? written : -1;
|
||||
client_data->pos += ret;
|
||||
if (client_data->pos == refbuf->len)
|
||||
{
|
||||
refbuf = refbuf->next;
|
||||
client_data->header_page = refbuf;
|
||||
client_data->pos = 0;
|
||||
}
|
||||
}
|
||||
client_data->headers_sent = 1;
|
||||
client_data->headers = headers;
|
||||
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 (format_plugin_t *self, client_t *client)
|
||||
{
|
||||
refbuf_t *refbuf = client->refbuf;
|
||||
char *buf;
|
||||
unsigned len;
|
||||
struct ogg_client *client_data = client->format_data;
|
||||
int ret, written = 0;
|
||||
|
||||
if (refbuf->next == NULL && client->pos == refbuf->len)
|
||||
return 0;
|
||||
|
||||
if (refbuf->next && client->pos == refbuf->len)
|
||||
{
|
||||
client_set_queue (client, refbuf->next);
|
||||
refbuf = client->refbuf;
|
||||
}
|
||||
buf = refbuf->data + client->pos;
|
||||
len = refbuf->len - client->pos;
|
||||
do
|
||||
{
|
||||
if (client_data->headers != refbuf->associated)
|
||||
{
|
||||
ret = send_ogg_headers (client, refbuf->associated);
|
||||
if (client_data->headers_sent == 0)
|
||||
break;
|
||||
written += ret;
|
||||
}
|
||||
ret = client_send_bytes (client, buf, len);
|
||||
|
||||
if (ret > 0)
|
||||
client->pos += ret;
|
||||
|
||||
if (ret < (int)len)
|
||||
break;
|
||||
written += ret;
|
||||
/* we have now written the page(s) */
|
||||
ret = 0;
|
||||
} while (0);
|
||||
|
||||
if (ret > 0)
|
||||
written += ret;
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
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->next;
|
||||
}
|
||||
ogg_info->file_headers = refbuf->associated;
|
||||
}
|
||||
write_ogg_data (source, refbuf);
|
||||
}
|
||||
|
||||
|
||||
static void format_ogg_send_headers(format_plugin_t *self,
|
||||
source_t *source, client_t *client)
|
||||
{
|
||||
int bytes;
|
||||
|
||||
client->respcode = 200;
|
||||
bytes = sock_write(client->con->sock,
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Content-Type: %s\r\n",
|
||||
source->format->contenttype);
|
||||
|
||||
if(bytes > 0) client->con->sent_bytes += bytes;
|
||||
|
||||
format_send_general_headers(self, source, client);
|
||||
}
|
||||
|
68
src/format_ogg.h
Normal file
68
src/format_ogg.h
Normal file
@ -0,0 +1,68 @@
|
||||
/* 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.h
|
||||
**
|
||||
** vorbis format plugin header
|
||||
**
|
||||
*/
|
||||
#ifndef __FORMAT_OGG_H__
|
||||
#define __FORMAT_OGG_H__
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
#include "refbuf.h"
|
||||
#include "format.h"
|
||||
|
||||
typedef struct ogg_state_tag
|
||||
{
|
||||
char *mount;
|
||||
ogg_sync_state oy;
|
||||
int error;
|
||||
|
||||
struct ogg_codec_tag *codecs;
|
||||
char *artist;
|
||||
char *title;
|
||||
int log_metadata;
|
||||
refbuf_t *file_headers;
|
||||
refbuf_t *header_pages;
|
||||
refbuf_t *header_pages_tail;
|
||||
refbuf_t **bos_end;
|
||||
int bos_completed;
|
||||
long bitrate;
|
||||
struct ogg_codec_tag *current;
|
||||
struct ogg_codec_tag *codec_sync;
|
||||
} ogg_state_t;
|
||||
|
||||
|
||||
/* per codec/logical structure */
|
||||
typedef struct ogg_codec_tag
|
||||
{
|
||||
struct ogg_codec_tag *next;
|
||||
ogg_stream_state os;
|
||||
unsigned headers;
|
||||
void *specific;
|
||||
refbuf_t *possible_start;
|
||||
refbuf_t *page;
|
||||
|
||||
refbuf_t *(*process)(ogg_state_t *ogg_info, struct ogg_codec_tag *codec);
|
||||
refbuf_t *(*process_page)(ogg_state_t *ogg_info,
|
||||
struct ogg_codec_tag *codec, ogg_page *page);
|
||||
void (*codec_free)(ogg_state_t *ogg_info, struct ogg_codec_tag *codec);
|
||||
} ogg_codec_t;
|
||||
|
||||
|
||||
refbuf_t *make_refbuf_with_page (ogg_page *page);
|
||||
void format_ogg_attach_header (ogg_state_t *ogg_info, ogg_page *page);
|
||||
void format_ogg_free_headers (ogg_state_t *ogg_info);
|
||||
int format_ogg_get_plugin (source_t *source);
|
||||
|
||||
#endif /* __FORMAT_OGG_H__ */
|
170
src/format_theora.c
Normal file
170
src/format_theora.c
Normal file
@ -0,0 +1,170 @@
|
||||
/* 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).
|
||||
*/
|
||||
|
||||
|
||||
/* Ogg codec handler for theora logical streams */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ogg/ogg.h>
|
||||
#include <theora/theora.h>
|
||||
|
||||
typedef struct source_tag source_t;
|
||||
|
||||
#include "refbuf.h"
|
||||
#include "format_ogg.h"
|
||||
#include "format_theora.h"
|
||||
#include "client.h"
|
||||
#include "stats.h"
|
||||
|
||||
#define CATMODULE "format-theora"
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
typedef struct _theora_codec_tag
|
||||
{
|
||||
theora_info ti;
|
||||
theora_comment tc;
|
||||
int granule_shift;
|
||||
ogg_int64_t last_iframe;
|
||||
ogg_int64_t prev_granulepos;
|
||||
} theora_codec_t;
|
||||
|
||||
|
||||
static void theora_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec)
|
||||
{
|
||||
theora_codec_t *theora = codec->specific;
|
||||
|
||||
DEBUG0 ("freeing theora codec");
|
||||
stats_event (ogg_info->mount, "video_bitrate", NULL);
|
||||
stats_event (ogg_info->mount, "framerate", NULL);
|
||||
stats_event (ogg_info->mount, "frame_size", NULL);
|
||||
theora_info_clear (&theora->ti);
|
||||
theora_comment_clear (&theora->tc);
|
||||
ogg_stream_clear (&codec->os);
|
||||
free (theora);
|
||||
free (codec);
|
||||
}
|
||||
|
||||
|
||||
/* theora pages are not rebuilt, so here we just for headers and then
|
||||
* pass them straight through to the the queue
|
||||
*/
|
||||
static refbuf_t *process_theora_page (ogg_state_t *ogg_info, ogg_codec_t *codec, ogg_page *page)
|
||||
{
|
||||
theora_codec_t *theora = codec->specific;
|
||||
ogg_packet packet;
|
||||
int header_page = 0;
|
||||
int has_keyframe = 0;
|
||||
refbuf_t *refbuf = NULL;
|
||||
ogg_int64_t granulepos;
|
||||
|
||||
if (ogg_stream_pagein (&codec->os, page) < 0)
|
||||
{
|
||||
ogg_info->error = 1;
|
||||
return NULL;
|
||||
}
|
||||
granulepos = ogg_page_granulepos (page);
|
||||
|
||||
while (ogg_stream_packetout (&codec->os, &packet) > 0)
|
||||
{
|
||||
if (theora_packet_isheader (&packet))
|
||||
{
|
||||
if (theora_decode_header (&theora->ti, &theora->tc, &packet) < 0)
|
||||
{
|
||||
ogg_info->error = 1;
|
||||
WARN0 ("problem with theora header");
|
||||
return NULL;
|
||||
}
|
||||
header_page = 1;
|
||||
codec->headers++;
|
||||
continue;
|
||||
}
|
||||
if (codec->headers < 3)
|
||||
{
|
||||
ogg_info->error = 1;
|
||||
ERROR0 ("Not enough header packets");
|
||||
return NULL;
|
||||
}
|
||||
if (theora_packet_iskeyframe (&packet))
|
||||
has_keyframe = 1;
|
||||
}
|
||||
if (header_page)
|
||||
{
|
||||
format_ogg_attach_header (ogg_info, page);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
refbuf = make_refbuf_with_page (page);
|
||||
/* DEBUG3 ("refbuf %p has pageno %ld, %llu", refbuf, ogg_page_pageno (page), (uint64_t)granulepos); */
|
||||
|
||||
if (has_keyframe && codec->possible_start)
|
||||
{
|
||||
codec->possible_start->sync_point = 1;
|
||||
refbuf_release (codec->possible_start);
|
||||
codec->possible_start = NULL;
|
||||
}
|
||||
if (granulepos != theora->prev_granulepos || granulepos == 0)
|
||||
{
|
||||
if (codec->possible_start)
|
||||
refbuf_release (codec->possible_start);
|
||||
refbuf_addref (refbuf);
|
||||
codec->possible_start = refbuf;
|
||||
}
|
||||
theora->prev_granulepos = granulepos;
|
||||
|
||||
return refbuf;
|
||||
}
|
||||
|
||||
|
||||
/* Check if specified BOS page is the start of a theora stream and
|
||||
* if so, create a codec structure for handling it
|
||||
*/
|
||||
ogg_codec_t *initial_theora_page (format_plugin_t *plugin, ogg_page *page)
|
||||
{
|
||||
ogg_state_t *ogg_info = plugin->_state;
|
||||
ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t));
|
||||
ogg_packet packet;
|
||||
|
||||
theora_codec_t *theora_codec = calloc (1, sizeof (theora_codec_t));
|
||||
|
||||
ogg_stream_init (&codec->os, ogg_page_serialno (page));
|
||||
ogg_stream_pagein (&codec->os, page);
|
||||
|
||||
theora_info_init (&theora_codec->ti);
|
||||
theora_comment_init (&theora_codec->tc);
|
||||
|
||||
ogg_stream_packetout (&codec->os, &packet);
|
||||
|
||||
DEBUG0("checking for theora codec");
|
||||
if (theora_decode_header (&theora_codec->ti, &theora_codec->tc, &packet) < 0)
|
||||
{
|
||||
theora_info_clear (&theora_codec->ti);
|
||||
theora_comment_clear (&theora_codec->tc);
|
||||
ogg_stream_clear (&codec->os);
|
||||
free (theora_codec);
|
||||
free (codec);
|
||||
return NULL;
|
||||
}
|
||||
INFO0 ("seen initial theora header");
|
||||
codec->specific = theora_codec;
|
||||
codec->process_page = process_theora_page;
|
||||
codec->codec_free = theora_codec_free;
|
||||
codec->headers = 1;
|
||||
format_ogg_attach_header (ogg_info, page);
|
||||
ogg_info->codec_sync = codec;
|
||||
return codec;
|
||||
}
|
||||
|
21
src/format_theora.h
Normal file
21
src/format_theora.h
Normal file
@ -0,0 +1,21 @@
|
||||
/* 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).
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __FORMAT_THEORA_H
|
||||
#define __FORMAT_THEORA_H
|
||||
|
||||
#include "format_ogg.h"
|
||||
|
||||
ogg_codec_t *initial_theora_page (format_plugin_t *plugin, ogg_page *page);
|
||||
|
||||
#endif /* __FORMAT_THEORA_H */
|
@ -10,439 +10,546 @@
|
||||
* and others (see AUTHORS for details).
|
||||
*/
|
||||
|
||||
/* format_vorbis.c
|
||||
**
|
||||
** format plugin for vorbis
|
||||
**
|
||||
*/
|
||||
|
||||
/* Ogg codec handler for vorbis streams */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
#include <vorbis/codec.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include "refbuf.h"
|
||||
#include "source.h"
|
||||
#include "client.h"
|
||||
|
||||
#include "format_ogg.h"
|
||||
#include "stats.h"
|
||||
#include "format.h"
|
||||
|
||||
#define CATMODULE "format-vorbis"
|
||||
#include "logging.h"
|
||||
|
||||
#define MAX_HEADER_PAGES 10
|
||||
|
||||
typedef struct _vstate_tag
|
||||
typedef struct vorbis_codec_tag
|
||||
{
|
||||
ogg_sync_state oy;
|
||||
ogg_stream_state os;
|
||||
vorbis_info vi;
|
||||
vorbis_comment vc;
|
||||
|
||||
ogg_page og;
|
||||
unsigned long serialno;
|
||||
int header;
|
||||
refbuf_t *file_headers;
|
||||
refbuf_t *header_pages;
|
||||
refbuf_t *header_pages_tail;
|
||||
int packets;
|
||||
} vstate_t;
|
||||
int rebuild_comment;
|
||||
int stream_notify;
|
||||
|
||||
struct client_vorbis
|
||||
ogg_stream_state new_os;
|
||||
int page_samples_trigger;
|
||||
ogg_int64_t prev_granulepos;
|
||||
ogg_packet *prev_packet;
|
||||
ogg_int64_t granulepos;
|
||||
ogg_int64_t samples_in_page;
|
||||
int prev_window;
|
||||
int initial_audio_packet;
|
||||
|
||||
ogg_page bos_page;
|
||||
ogg_packet *header [3];
|
||||
ogg_int64_t prev_page_samples;
|
||||
|
||||
int (*process_packet)(ogg_state_t *ogg_info, ogg_codec_t *codec);
|
||||
refbuf_t *(*get_buffer_page)(ogg_state_t *ogg_info, ogg_codec_t *codec);
|
||||
|
||||
} vorbis_codec_t;
|
||||
|
||||
static int process_vorbis_headers (ogg_state_t *ogg_info, ogg_codec_t *codec);
|
||||
static refbuf_t *process_vorbis_page (ogg_state_t *ogg_info,
|
||||
ogg_codec_t *codec, ogg_page *page);
|
||||
static refbuf_t *process_vorbis (ogg_state_t *ogg_info, ogg_codec_t *codec);
|
||||
static void vorbis_set_tag (format_plugin_t *plugin, char *tag, char *value);
|
||||
|
||||
|
||||
static void free_ogg_packet (ogg_packet *packet)
|
||||
{
|
||||
refbuf_t *headers;
|
||||
refbuf_t *header_page;
|
||||
unsigned int pos;
|
||||
int processing_headers;
|
||||
};
|
||||
|
||||
|
||||
static void format_vorbis_free_plugin(format_plugin_t *self);
|
||||
static refbuf_t *format_vorbis_get_buffer (source_t *source);
|
||||
static int format_vorbis_create_client_data (source_t *source, client_t *client);
|
||||
static void format_vorbis_send_headers(format_plugin_t *self,
|
||||
source_t *source, client_t *client);
|
||||
static int write_buf_to_client (format_plugin_t *self, client_t *client);
|
||||
static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf);
|
||||
|
||||
|
||||
int format_vorbis_get_plugin(source_t *source)
|
||||
{
|
||||
format_plugin_t *plugin;
|
||||
vstate_t *state;
|
||||
|
||||
plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t));
|
||||
|
||||
plugin->type = FORMAT_TYPE_VORBIS;
|
||||
plugin->write_buf_to_file = write_ogg_to_file;
|
||||
plugin->get_buffer = format_vorbis_get_buffer;
|
||||
plugin->write_buf_to_client = write_buf_to_client;
|
||||
plugin->create_client_data = format_vorbis_create_client_data;
|
||||
plugin->client_send_headers = format_vorbis_send_headers;
|
||||
plugin->free_plugin = format_vorbis_free_plugin;
|
||||
plugin->contenttype = "application/ogg";
|
||||
|
||||
state = (vstate_t *)calloc(1, sizeof(vstate_t));
|
||||
ogg_sync_init(&state->oy);
|
||||
|
||||
plugin->_state = (void *)state;
|
||||
source->format = plugin;
|
||||
|
||||
return 0;
|
||||
if (packet)
|
||||
{
|
||||
free (packet->packet);
|
||||
free (packet);
|
||||
}
|
||||
}
|
||||
|
||||
void format_vorbis_free_plugin(format_plugin_t *self)
|
||||
|
||||
static void vorbis_codec_free (ogg_state_t *ogg_info, ogg_codec_t *codec)
|
||||
{
|
||||
vstate_t *state = (vstate_t *)self->_state;
|
||||
refbuf_t *header = state->header_pages;
|
||||
vorbis_codec_t *vorbis = codec->specific;
|
||||
|
||||
/* free memory associated with this plugin instance */
|
||||
|
||||
/* free state memory */
|
||||
while (header)
|
||||
{
|
||||
refbuf_t *to_release = header;
|
||||
header = header->next;
|
||||
refbuf_release (to_release);
|
||||
}
|
||||
ogg_sync_clear(&state->oy);
|
||||
ogg_stream_clear(&state->os);
|
||||
vorbis_comment_clear(&state->vc);
|
||||
vorbis_info_clear(&state->vi);
|
||||
|
||||
free(state);
|
||||
|
||||
/* free the plugin instance */
|
||||
free(self);
|
||||
DEBUG0 ("freeing vorbis codec");
|
||||
stats_event (ogg_info->mount, "audio-bitrate", NULL);
|
||||
stats_event (ogg_info->mount, "audio-channels", NULL);
|
||||
stats_event (ogg_info->mount, "audio-samplerate", NULL);
|
||||
vorbis_info_clear (&vorbis->vi);
|
||||
vorbis_comment_clear (&vorbis->vc);
|
||||
ogg_stream_clear (&codec->os);
|
||||
ogg_stream_clear (&vorbis->new_os);
|
||||
free_ogg_packet (vorbis->header[0]);
|
||||
free_ogg_packet (vorbis->header[1]);
|
||||
free_ogg_packet (vorbis->header[2]);
|
||||
free_ogg_packet (vorbis->prev_packet);
|
||||
free (vorbis->bos_page.header);
|
||||
free (vorbis);
|
||||
free (codec);
|
||||
}
|
||||
|
||||
static refbuf_t *format_vorbis_get_buffer (source_t *source)
|
||||
|
||||
static ogg_packet *copy_ogg_packet (ogg_packet *packet)
|
||||
{
|
||||
int result;
|
||||
ogg_packet op;
|
||||
char *title_tag;
|
||||
char *artist_tag;
|
||||
char *metadata = NULL;
|
||||
int metadata_len = 0;
|
||||
refbuf_t *refbuf, *header;
|
||||
char *data;
|
||||
format_plugin_t *self = source->format;
|
||||
int bytes;
|
||||
vstate_t *state = (vstate_t *)self->_state;
|
||||
|
||||
data = ogg_sync_buffer (&state->oy, 4096);
|
||||
|
||||
bytes = sock_read_bytes (source->con->sock, data, 4096);
|
||||
if (bytes < 0)
|
||||
ogg_packet *next;
|
||||
do
|
||||
{
|
||||
if (sock_recoverable (sock_error()))
|
||||
return NULL;
|
||||
WARN0 ("source connection has died");
|
||||
ogg_sync_wrote (&state->oy, 0);
|
||||
source->running = 0;
|
||||
return NULL;
|
||||
}
|
||||
if (bytes == 0)
|
||||
{
|
||||
INFO1 ("End of Stream %s", source->mount);
|
||||
ogg_sync_wrote (&state->oy, 0);
|
||||
source->running = 0;
|
||||
return NULL;
|
||||
}
|
||||
ogg_sync_wrote (&state->oy, bytes);
|
||||
|
||||
refbuf = NULL;
|
||||
if (ogg_sync_pageout(&state->oy, &state->og) == 1) {
|
||||
refbuf = refbuf_new(state->og.header_len + state->og.body_len);
|
||||
memcpy(refbuf->data, state->og.header, state->og.header_len);
|
||||
memcpy(&refbuf->data[state->og.header_len], state->og.body, state->og.body_len);
|
||||
|
||||
if (state->serialno != ogg_page_serialno(&state->og)) {
|
||||
DEBUG0("new stream");
|
||||
/* this is a new logical bitstream */
|
||||
state->header = 0;
|
||||
state->packets = 0;
|
||||
|
||||
/* Clear old stuff. Rarely but occasionally needed. */
|
||||
header = state->header_pages;
|
||||
while (header)
|
||||
{
|
||||
refbuf_t *to_release = header;
|
||||
DEBUG0 ("clearing out header page");
|
||||
header = header->next;
|
||||
refbuf_release (to_release);
|
||||
}
|
||||
ogg_stream_clear(&state->os);
|
||||
vorbis_comment_clear(&state->vc);
|
||||
vorbis_info_clear(&state->vi);
|
||||
state->header_pages = NULL;
|
||||
state->header_pages_tail = NULL;
|
||||
|
||||
state->serialno = ogg_page_serialno(&state->og);
|
||||
ogg_stream_init(&state->os, state->serialno);
|
||||
vorbis_info_init(&state->vi);
|
||||
vorbis_comment_init(&state->vc);
|
||||
}
|
||||
|
||||
if (state->header >= 0) {
|
||||
/* FIXME: In some streams (non-vorbis ogg streams), this could get
|
||||
* extras pages beyond the header. We need to collect the pages
|
||||
* here anyway, but they may have to be discarded later.
|
||||
*/
|
||||
DEBUG1 ("header %d", state->header);
|
||||
if (ogg_page_granulepos(&state->og) <= 0) {
|
||||
state->header++;
|
||||
} else {
|
||||
/* we're done caching headers */
|
||||
state->header = -1;
|
||||
|
||||
DEBUG0 ("doing stats");
|
||||
/* put known comments in the stats */
|
||||
title_tag = vorbis_comment_query(&state->vc, "TITLE", 0);
|
||||
if (title_tag) stats_event(source->mount, "title", title_tag);
|
||||
else stats_event(source->mount, "title", "unknown");
|
||||
artist_tag = vorbis_comment_query(&state->vc, "ARTIST", 0);
|
||||
if (artist_tag) stats_event(source->mount, "artist", artist_tag);
|
||||
else stats_event(source->mount, "artist", "unknown");
|
||||
|
||||
metadata = NULL;
|
||||
if (artist_tag) {
|
||||
if (title_tag) {
|
||||
metadata_len = strlen(artist_tag) + strlen(title_tag) +
|
||||
strlen(" - ") + 1;
|
||||
metadata = (char *)calloc(1, metadata_len);
|
||||
sprintf(metadata, "%s - %s", artist_tag, title_tag);
|
||||
}
|
||||
else {
|
||||
metadata_len = strlen(artist_tag) + 1;
|
||||
metadata = (char *)calloc(1, metadata_len);
|
||||
sprintf(metadata, "%s", artist_tag);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (title_tag) {
|
||||
metadata_len = strlen(title_tag) + 1;
|
||||
metadata = (char *)calloc(1, metadata_len);
|
||||
sprintf(metadata, "%s", title_tag);
|
||||
}
|
||||
}
|
||||
if (metadata) {
|
||||
logging_playlist(source->mount, metadata, source->listeners);
|
||||
free(metadata);
|
||||
metadata = NULL;
|
||||
}
|
||||
/* don't need these now */
|
||||
ogg_stream_clear(&state->os);
|
||||
vorbis_comment_clear(&state->vc);
|
||||
vorbis_info_clear(&state->vi);
|
||||
|
||||
yp_touch (source->mount);
|
||||
}
|
||||
}
|
||||
|
||||
/* cache header pages */
|
||||
if (state->header > 0 && state->packets < 3) {
|
||||
/* build a list of headers pages for attaching */
|
||||
if (state->header_pages_tail)
|
||||
state->header_pages_tail->next = refbuf;
|
||||
state->header_pages_tail = refbuf;
|
||||
|
||||
if (state->header_pages == NULL)
|
||||
state->header_pages = refbuf;
|
||||
|
||||
if (state->packets >= 0 && state->packets < 3) {
|
||||
ogg_stream_pagein(&state->os, &state->og);
|
||||
while (state->packets < 3) {
|
||||
result = ogg_stream_packetout(&state->os, &op);
|
||||
if (result == 0) break; /* need more data */
|
||||
if (result < 0) {
|
||||
state->packets = -1;
|
||||
next = malloc (sizeof (ogg_packet));
|
||||
if (next == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
state->packets++;
|
||||
|
||||
if (vorbis_synthesis_headerin(&state->vi, &state->vc, &op) < 0) {
|
||||
state->packets = -1;
|
||||
memcpy (next, packet, sizeof (ogg_packet));
|
||||
next->packet = malloc (next->bytes);
|
||||
if (next->packet == NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* we do not place ogg headers on the main queue */
|
||||
return NULL;
|
||||
}
|
||||
/* increase ref counts on each header page going out */
|
||||
header = state->header_pages;
|
||||
while (header)
|
||||
{
|
||||
refbuf_addref (header);
|
||||
header = header->next;
|
||||
}
|
||||
refbuf->associated = state->header_pages;
|
||||
}
|
||||
memcpy (next->packet, packet->packet, next->bytes);
|
||||
return next;
|
||||
} while (0);
|
||||
|
||||
if (next)
|
||||
free (next);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void add_audio_packet (vorbis_codec_t *source_vorbis, ogg_packet *packet)
|
||||
{
|
||||
if (source_vorbis->initial_audio_packet)
|
||||
{
|
||||
packet->granulepos = 0;
|
||||
source_vorbis->initial_audio_packet = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
source_vorbis->samples_in_page +=
|
||||
(packet->granulepos - source_vorbis->prev_granulepos);
|
||||
source_vorbis->prev_granulepos = packet->granulepos;
|
||||
source_vorbis->granulepos += source_vorbis->prev_window;
|
||||
}
|
||||
ogg_stream_packetin (&source_vorbis->new_os, packet);
|
||||
}
|
||||
|
||||
|
||||
static refbuf_t *get_buffer_audio (ogg_state_t *ogg_info, ogg_codec_t *codec)
|
||||
{
|
||||
refbuf_t *refbuf = NULL;
|
||||
ogg_page page;
|
||||
vorbis_codec_t *source_vorbis = codec->specific;
|
||||
int (*get_ogg_page)(ogg_stream_state*, ogg_page *) = ogg_stream_pageout;
|
||||
|
||||
if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger)
|
||||
get_ogg_page = ogg_stream_flush;
|
||||
|
||||
if (get_ogg_page (&source_vorbis->new_os, &page) > 0)
|
||||
{
|
||||
/* squeeze a page copy into a buffer */
|
||||
source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples);
|
||||
source_vorbis->prev_page_samples = ogg_page_granulepos (&page);
|
||||
|
||||
refbuf = make_refbuf_with_page (&page);
|
||||
}
|
||||
return refbuf;
|
||||
}
|
||||
|
||||
static void free_ogg_client_data (client_t *client)
|
||||
{
|
||||
free (client->format_data);
|
||||
client->format_data = NULL;
|
||||
}
|
||||
|
||||
static int format_vorbis_create_client_data (source_t *source, client_t *client)
|
||||
static refbuf_t *get_buffer_header (ogg_state_t *ogg_info, ogg_codec_t *codec)
|
||||
{
|
||||
struct client_vorbis *client_data = calloc (1, sizeof (struct client_vorbis));
|
||||
int ret = -1;
|
||||
int headers_flushed = 0;
|
||||
ogg_page page;
|
||||
vorbis_codec_t *source_vorbis = codec->specific;
|
||||
|
||||
if (client_data)
|
||||
while (ogg_stream_flush (&source_vorbis->new_os, &page) > 0)
|
||||
{
|
||||
client->format_data = client_data;
|
||||
client->free_client_data = free_ogg_client_data;
|
||||
ret = 0;
|
||||
format_ogg_attach_header (ogg_info, &page);
|
||||
headers_flushed = 1;
|
||||
}
|
||||
return ret;
|
||||
if (headers_flushed)
|
||||
{
|
||||
source_vorbis->get_buffer_page = get_buffer_audio;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void format_vorbis_send_headers(format_plugin_t *self,
|
||||
source_t *source, client_t *client)
|
||||
|
||||
static refbuf_t *get_buffer_finished (ogg_state_t *ogg_info, ogg_codec_t *codec)
|
||||
{
|
||||
int bytes;
|
||||
|
||||
client->respcode = 200;
|
||||
bytes = sock_write(client->con->sock,
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Content-Type: %s\r\n",
|
||||
source->format->contenttype);
|
||||
|
||||
if(bytes > 0) client->con->sent_bytes += bytes;
|
||||
|
||||
format_send_general_headers(self, source, client);
|
||||
}
|
||||
|
||||
static int send_ogg_headers (client_t *client, refbuf_t *headers)
|
||||
{
|
||||
struct client_vorbis *client_data = client->format_data;
|
||||
vorbis_codec_t *source_vorbis = codec->specific;
|
||||
ogg_page page;
|
||||
refbuf_t *refbuf;
|
||||
int written = 0;
|
||||
|
||||
if (client_data->processing_headers == 0)
|
||||
if (ogg_stream_flush (&source_vorbis->new_os, &page) > 0)
|
||||
{
|
||||
client_data->header_page = headers;
|
||||
client_data->pos = 0;
|
||||
client_data->processing_headers = 1;
|
||||
}
|
||||
refbuf = client_data->header_page;
|
||||
while (refbuf)
|
||||
{
|
||||
char *data = refbuf->data + client_data->pos;
|
||||
unsigned int len = refbuf->len - client_data->pos;
|
||||
int ret;
|
||||
source_vorbis->samples_in_page -= (ogg_page_granulepos (&page) - source_vorbis->prev_page_samples);
|
||||
source_vorbis->prev_page_samples = ogg_page_granulepos (&page);
|
||||
|
||||
ret = client_send_bytes (client, data, len);
|
||||
if (ret > 0)
|
||||
{
|
||||
written += ret;
|
||||
client_data->pos += ret;
|
||||
refbuf = make_refbuf_with_page (&page);
|
||||
DEBUG0 ("flushing page");
|
||||
return refbuf;
|
||||
}
|
||||
if (ret < (int)len)
|
||||
return written;
|
||||
if (client_data->pos == refbuf->len)
|
||||
{
|
||||
refbuf = refbuf->next;
|
||||
client_data->header_page = refbuf;
|
||||
client_data->pos = 0;
|
||||
}
|
||||
}
|
||||
/* update client info on headers sent */
|
||||
client_data->processing_headers = 0;
|
||||
client_data->headers = headers;
|
||||
return written;
|
||||
ogg_stream_clear (&source_vorbis->new_os);
|
||||
ogg_stream_init (&source_vorbis->new_os, rand());
|
||||
|
||||
format_ogg_free_headers (ogg_info);
|
||||
source_vorbis->get_buffer_page = NULL;
|
||||
source_vorbis->process_packet = process_vorbis_headers;
|
||||
if (source_vorbis->initial_audio_packet == 0)
|
||||
source_vorbis->prev_window = 0;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int write_buf_to_client (format_plugin_t *self, client_t *client)
|
||||
{
|
||||
refbuf_t *refbuf = client->refbuf;
|
||||
char *buf;
|
||||
unsigned int len;
|
||||
struct client_vorbis *client_data = client->format_data;
|
||||
int ret, written = 0;
|
||||
|
||||
if (refbuf->next == NULL && client->pos == refbuf->len)
|
||||
/* push last packet into stream marked with eos */
|
||||
static void initiate_flush (vorbis_codec_t *source_vorbis)
|
||||
{
|
||||
DEBUG0 ("adding EOS packet");
|
||||
if (source_vorbis->prev_packet)
|
||||
{
|
||||
/* insert prev_packet with eos */
|
||||
source_vorbis->prev_packet->e_o_s = 1;
|
||||
add_audio_packet (source_vorbis, source_vorbis->prev_packet);
|
||||
source_vorbis->prev_packet->e_o_s = 0;
|
||||
}
|
||||
source_vorbis->get_buffer_page = get_buffer_finished;
|
||||
source_vorbis->initial_audio_packet = 1;
|
||||
}
|
||||
|
||||
|
||||
/* process the vorbis audio packets. Here we just take each packet out
|
||||
* and add them into the new stream, flushing after so many samples. We
|
||||
* also check if an new headers are requested after each processed page
|
||||
*/
|
||||
static int process_vorbis_audio (ogg_state_t *ogg_info, ogg_codec_t *codec)
|
||||
{
|
||||
vorbis_codec_t *source_vorbis = codec->specific;
|
||||
|
||||
while (1)
|
||||
{
|
||||
int window;
|
||||
ogg_packet packet;
|
||||
|
||||
/* now, lets extract what packets we can */
|
||||
if (ogg_stream_packetout (&codec->os, &packet) <= 0)
|
||||
break;
|
||||
|
||||
/* calculate granulepos for the packet */
|
||||
window = vorbis_packet_blocksize (&source_vorbis->vi, &packet) / 4;
|
||||
|
||||
source_vorbis->granulepos += window;
|
||||
if (source_vorbis->prev_packet)
|
||||
{
|
||||
ogg_packet *prev_packet = source_vorbis->prev_packet;
|
||||
|
||||
add_audio_packet (source_vorbis, prev_packet);
|
||||
free_ogg_packet (prev_packet);
|
||||
packet . granulepos = source_vorbis->granulepos;
|
||||
}
|
||||
else
|
||||
{
|
||||
packet . granulepos = 0;
|
||||
}
|
||||
|
||||
/* store the current packet details */
|
||||
source_vorbis->prev_window = window;
|
||||
source_vorbis->prev_packet = copy_ogg_packet (&packet);
|
||||
if (packet.e_o_s)
|
||||
{
|
||||
initiate_flush (source_vorbis);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* allow for pages to be flushed if there's over a certain number of samples */
|
||||
if (source_vorbis->samples_in_page > source_vorbis->page_samples_trigger)
|
||||
return 1;
|
||||
}
|
||||
if (source_vorbis->stream_notify)
|
||||
{
|
||||
initiate_flush (source_vorbis);
|
||||
source_vorbis->stream_notify = 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* This handles the headers at the backend, here we insert the header packets
|
||||
* we want for the queue.
|
||||
*/
|
||||
static int process_vorbis_headers (ogg_state_t *ogg_info, ogg_codec_t *codec)
|
||||
{
|
||||
vorbis_codec_t *source_vorbis = codec->specific;
|
||||
|
||||
if (source_vorbis->header [0] == NULL)
|
||||
return 0;
|
||||
|
||||
if (refbuf->next && client->pos == refbuf->len)
|
||||
DEBUG0 ("Adding the 3 header packets");
|
||||
ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [0]);
|
||||
/* NOTE: we could build a separate comment packet each time */
|
||||
if (source_vorbis->rebuild_comment)
|
||||
{
|
||||
client_set_queue (client, refbuf->next);
|
||||
refbuf = client->refbuf;
|
||||
vorbis_comment vc;
|
||||
ogg_packet header;
|
||||
|
||||
vorbis_comment_init (&vc);
|
||||
if (ogg_info->artist)
|
||||
vorbis_comment_add_tag (&vc, "artist", ogg_info->artist);
|
||||
if (ogg_info->title)
|
||||
vorbis_comment_add_tag (&vc, "title", ogg_info->title);
|
||||
vorbis_comment_add (&vc, "server=" ICECAST_VERSION_STRING);
|
||||
vorbis_commentheader_out (&vc, &header);
|
||||
|
||||
ogg_stream_packetin (&source_vorbis->new_os, &header);
|
||||
vorbis_comment_clear (&vc);
|
||||
ogg_packet_clear (&header);
|
||||
}
|
||||
do
|
||||
{
|
||||
if (client_data->headers != refbuf->associated)
|
||||
{
|
||||
/* different headers seen so send the new ones */
|
||||
ret = send_ogg_headers (client, refbuf->associated);
|
||||
if (client_data->processing_headers)
|
||||
break;
|
||||
written += ret;
|
||||
}
|
||||
buf = refbuf->data + client->pos;
|
||||
len = refbuf->len - client->pos;
|
||||
ret = client_send_bytes (client, buf, len);
|
||||
else
|
||||
ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [1]);
|
||||
ogg_stream_packetin (&source_vorbis->new_os, source_vorbis->header [2]);
|
||||
source_vorbis->rebuild_comment = 0;
|
||||
|
||||
if (ret > 0)
|
||||
client->pos += ret;
|
||||
|
||||
if (ret < (int)len)
|
||||
break;
|
||||
written += ret;
|
||||
/* we have now written the page(s) */
|
||||
ret = 0;
|
||||
} while (0);
|
||||
|
||||
if (ret > 0)
|
||||
written += ret;
|
||||
return written;
|
||||
}
|
||||
|
||||
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;
|
||||
ogg_info->log_metadata = 1;
|
||||
source_vorbis->get_buffer_page = get_buffer_header;
|
||||
source_vorbis->process_packet = process_vorbis_audio;
|
||||
source_vorbis->granulepos = source_vorbis->prev_window;
|
||||
source_vorbis->initial_audio_packet = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void write_ogg_to_file (struct source_tag *source, refbuf_t *refbuf)
|
||||
/* check if the provided BOS page is the start of a vorbis stream. If so
|
||||
* then setup a structure so it can be used
|
||||
*/
|
||||
ogg_codec_t *initial_vorbis_page (format_plugin_t *plugin, ogg_page *page)
|
||||
{
|
||||
vstate_t *state = (vstate_t *)source->format->_state;
|
||||
// ogg_state_t *ogg_info = plugin->_state;
|
||||
ogg_codec_t *codec = calloc (1, sizeof (ogg_codec_t));
|
||||
ogg_packet packet;
|
||||
|
||||
vorbis_codec_t *vorbis = calloc (1, sizeof (vorbis_codec_t));
|
||||
|
||||
ogg_stream_init (&codec->os, ogg_page_serialno (page));
|
||||
ogg_stream_pagein (&codec->os, page);
|
||||
|
||||
vorbis_info_init (&vorbis->vi);
|
||||
vorbis_comment_init (&vorbis->vc);
|
||||
|
||||
ogg_stream_packetout (&codec->os, &packet);
|
||||
|
||||
DEBUG0("checking for vorbis codec");
|
||||
if (vorbis_synthesis_headerin (&vorbis->vi, &vorbis->vc, &packet) < 0)
|
||||
{
|
||||
ogg_stream_clear (&codec->os);
|
||||
vorbis_info_clear (&vorbis->vi);
|
||||
vorbis_comment_clear (&vorbis->vc);
|
||||
free (vorbis);
|
||||
free (codec);
|
||||
return NULL;
|
||||
}
|
||||
INFO0 ("seen initial vorbis header");
|
||||
codec->specific = vorbis;
|
||||
codec->codec_free = vorbis_codec_free;
|
||||
codec->headers = 1;
|
||||
|
||||
free_ogg_packet (vorbis->header[0]);
|
||||
free_ogg_packet (vorbis->header[1]);
|
||||
free_ogg_packet (vorbis->header[2]);
|
||||
memset (vorbis->header, 0, sizeof (vorbis->header));
|
||||
vorbis->header [0] = copy_ogg_packet (&packet);
|
||||
ogg_stream_init (&vorbis->new_os, rand());
|
||||
|
||||
codec->process_page = process_vorbis_page;
|
||||
codec->process = process_vorbis;
|
||||
plugin->set_tag = vorbis_set_tag;
|
||||
|
||||
vorbis->bos_page.header = malloc (page->header_len + page->body_len);
|
||||
|
||||
memcpy (vorbis->bos_page.header, page->header, page->header_len);
|
||||
vorbis->bos_page.header_len = page->header_len;
|
||||
|
||||
vorbis->bos_page.body = vorbis->bos_page.header + page->header_len;
|
||||
memcpy (vorbis->bos_page.body, page->body, page->body_len);
|
||||
vorbis->bos_page.body_len = page->body_len;
|
||||
|
||||
return codec;
|
||||
}
|
||||
|
||||
|
||||
if (state->file_headers != refbuf->associated)
|
||||
{
|
||||
refbuf_t *header = refbuf->associated;
|
||||
while (header)
|
||||
{
|
||||
if (write_ogg_data (source, header) == 0)
|
||||
/* called from the admin interface, here we update the artist/title info
|
||||
* and schedule a new set of header pages
|
||||
*/
|
||||
static void vorbis_set_tag (format_plugin_t *plugin, char *tag, char *value)
|
||||
{
|
||||
ogg_state_t *ogg_info = plugin->_state;
|
||||
ogg_codec_t *codec = ogg_info->codecs;
|
||||
vorbis_codec_t *source_vorbis;
|
||||
int change = 0;
|
||||
|
||||
/* avoid updating if multiple codecs in use */
|
||||
if (codec && codec->next == NULL)
|
||||
source_vorbis = codec->specific;
|
||||
else
|
||||
return;
|
||||
header = header->next;
|
||||
|
||||
if (strcmp (tag, "artist") == 0)
|
||||
{
|
||||
char *p = strdup (value);
|
||||
if (p)
|
||||
{
|
||||
free (ogg_info->artist);
|
||||
ogg_info->artist = p;
|
||||
change = 1;
|
||||
}
|
||||
state->file_headers = refbuf->associated;
|
||||
}
|
||||
write_ogg_data (source, refbuf);
|
||||
if (strcmp (tag, "title") == 0)
|
||||
{
|
||||
char *p = strdup (value);
|
||||
if (p)
|
||||
{
|
||||
free (ogg_info->title);
|
||||
ogg_info->title = p;
|
||||
change = 1;
|
||||
}
|
||||
}
|
||||
if (strcmp (tag, "song") == 0)
|
||||
{
|
||||
char *p = strdup (value);
|
||||
if (p)
|
||||
{
|
||||
free (ogg_info->artist);
|
||||
free (ogg_info->title);
|
||||
ogg_info->artist = NULL;
|
||||
ogg_info->title = p;
|
||||
change = 1;
|
||||
}
|
||||
}
|
||||
if (change)
|
||||
{
|
||||
source_vorbis->stream_notify = 1;
|
||||
source_vorbis->rebuild_comment = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* main backend routine when rebuilding streams. Here we loop until we either
|
||||
* have a refbuf to add onto the queue, or we want more data to process.
|
||||
*/
|
||||
static refbuf_t *process_vorbis (ogg_state_t *ogg_info, ogg_codec_t *codec)
|
||||
{
|
||||
vorbis_codec_t *source_vorbis = codec->specific;
|
||||
refbuf_t *refbuf;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (source_vorbis->get_buffer_page)
|
||||
{
|
||||
refbuf = source_vorbis->get_buffer_page (ogg_info, codec);
|
||||
if (refbuf)
|
||||
return refbuf;
|
||||
}
|
||||
|
||||
if (source_vorbis->process_packet &&
|
||||
source_vorbis->process_packet (ogg_info, codec) > 0)
|
||||
continue;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* no processing of pages, just wrap them up in a refbuf and pass
|
||||
* back for adding to the queue
|
||||
*/
|
||||
static refbuf_t *process_vorbis_passthru_page (ogg_state_t *ogg_info,
|
||||
ogg_codec_t *codec, ogg_page *page)
|
||||
{
|
||||
return make_refbuf_with_page (page);
|
||||
}
|
||||
|
||||
|
||||
/* handle incoming page. as the stream is being rebuilt, we need to
|
||||
* add all pages from the stream before processing packets
|
||||
*/
|
||||
static refbuf_t *process_vorbis_page (ogg_state_t *ogg_info,
|
||||
ogg_codec_t *codec, ogg_page *page)
|
||||
{
|
||||
ogg_packet header;
|
||||
vorbis_codec_t *source_vorbis = codec->specific;
|
||||
char *comment;
|
||||
|
||||
if (ogg_stream_pagein (&codec->os, page) < 0)
|
||||
{
|
||||
ogg_info->error = 1;
|
||||
return NULL;
|
||||
}
|
||||
if (codec->headers == 3)
|
||||
return NULL;
|
||||
|
||||
while (codec->headers < 3)
|
||||
{
|
||||
/* now, lets extract the packets */
|
||||
DEBUG1 ("processing incoming header packet (%d)", codec->headers);
|
||||
|
||||
if (ogg_stream_packetout (&codec->os, &header) <= 0)
|
||||
{
|
||||
if (ogg_info->codecs->next)
|
||||
format_ogg_attach_header (ogg_info, page);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* change comments here if need be */
|
||||
if (vorbis_synthesis_headerin (&source_vorbis->vi, &source_vorbis->vc, &header) < 0)
|
||||
{
|
||||
ogg_info->error = 1;
|
||||
WARN0 ("Problem parsing ogg vorbis header");
|
||||
return NULL;
|
||||
}
|
||||
header.granulepos = 0;
|
||||
source_vorbis->header [codec->headers] = copy_ogg_packet (&header);
|
||||
codec->headers++;
|
||||
}
|
||||
DEBUG0 ("we have the header packets now");
|
||||
|
||||
/* if vorbis is the only codec then allow rebuilding of the streams */
|
||||
if (ogg_info->codecs->next == NULL)
|
||||
{
|
||||
/* set queued vorbis pages to contain about 1/2 of a second worth of samples */
|
||||
source_vorbis->page_samples_trigger = source_vorbis->vi.rate / 2;
|
||||
source_vorbis->process_packet = process_vorbis_headers;
|
||||
}
|
||||
else
|
||||
{
|
||||
format_ogg_attach_header (ogg_info, &source_vorbis->bos_page);
|
||||
format_ogg_attach_header (ogg_info, page);
|
||||
codec->process_page = process_vorbis_passthru_page;
|
||||
}
|
||||
|
||||
free (ogg_info->title);
|
||||
comment = vorbis_comment_query (&source_vorbis->vc, "TITLE", 0);
|
||||
if (comment)
|
||||
ogg_info->title = strdup (comment);
|
||||
else
|
||||
ogg_info->title = NULL;
|
||||
|
||||
free (ogg_info->artist);
|
||||
comment = vorbis_comment_query (&source_vorbis->vc, "ARTIST", 0);
|
||||
if (comment)
|
||||
ogg_info->artist = strdup (comment);
|
||||
else
|
||||
ogg_info->artist = NULL;
|
||||
ogg_info->log_metadata = 1;
|
||||
|
||||
stats_event_args (ogg_info->mount, "audio-samplerate", "%ld", (long)source_vorbis->vi.rate);
|
||||
stats_event_args (ogg_info->mount, "audio-channels", "%ld", (long)source_vorbis->vi.channels);
|
||||
stats_event_args (ogg_info->mount, "audio-bitrate", "%ld", (long)source_vorbis->vi.bitrate_nominal);
|
||||
stats_event_args (ogg_info->mount, "ice-bitrate", "%ld", (long)source_vorbis->vi.bitrate_nominal/1000);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -10,14 +10,12 @@
|
||||
* and others (see AUTHORS for details).
|
||||
*/
|
||||
|
||||
/* format_vorbis.h
|
||||
**
|
||||
** vorbis format plugin header
|
||||
**
|
||||
*/
|
||||
#ifndef __FORMAT_VORBIS_H__
|
||||
#define __FORMAT_VORBIS_H__
|
||||
|
||||
int format_vorbis_get_plugin(source_t *source);
|
||||
#ifndef __FORMAT_VORBIS_H
|
||||
#define __FORMAT_VORBIS_H
|
||||
|
||||
#endif /* __FORMAT_VORBIS_H__ */
|
||||
#include "format_ogg.h"
|
||||
|
||||
ogg_codec_t *initial_vorbis_page (format_plugin_t *plugin, ogg_page *page);
|
||||
|
||||
#endif /* __FORMAT_VORBIS_H */
|
||||
|
@ -51,6 +51,7 @@ refbuf_t *refbuf_new(unsigned long size)
|
||||
}
|
||||
}
|
||||
refbuf->len = size;
|
||||
refbuf->sync_point = 0;
|
||||
refbuf->_count = 1;
|
||||
refbuf->next = NULL;
|
||||
refbuf->associated = NULL;
|
||||
|
@ -22,6 +22,7 @@ typedef struct _refbuf_tag
|
||||
{
|
||||
char *data;
|
||||
long len;
|
||||
int sync_point;
|
||||
struct _refbuf_tag *associated;
|
||||
struct _refbuf_tag *next;
|
||||
|
||||
|
23
src/source.c
23
src/source.c
@ -381,6 +381,26 @@ void source_move_clients (source_t *source, source_t *dest)
|
||||
thread_mutex_unlock (&move_clients_mutex);
|
||||
}
|
||||
|
||||
|
||||
/* clients need to be start from somewhere in the queue
|
||||
* * so we will look for a refbuf which has been previous
|
||||
* * marked as a sync point */
|
||||
static void find_client_start (source_t *source, client_t *client)
|
||||
{
|
||||
refbuf_t *refbuf = source->burst_point;
|
||||
|
||||
while (refbuf)
|
||||
{
|
||||
if (refbuf->sync_point)
|
||||
{
|
||||
client_set_queue (client, refbuf);
|
||||
break;
|
||||
}
|
||||
refbuf = refbuf->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* get some data from the source. The stream data is placed in a refbuf
|
||||
* and sent back, however NULL is also valid as in the case of a short
|
||||
* timeout and there's no data pending.
|
||||
@ -442,8 +462,7 @@ static void send_to_listener (source_t *source, client_t *client, int deletion_e
|
||||
/* new users need somewhere to start from */
|
||||
if (client->refbuf == NULL)
|
||||
{
|
||||
/* make clients start at the per source burst point on the queue */
|
||||
client_set_queue (client, source->burst_point);
|
||||
find_client_start (source, client);
|
||||
if (client->refbuf == NULL)
|
||||
return;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user