format_vorbis.c 8.39 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/* 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).
 */

Jack Moffitt's avatar
Jack Moffitt committed
13 14 15 16 17 18
/* format_vorbis.c
**
** format plugin for vorbis
**
*/

19 20 21 22
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Jack Moffitt's avatar
Jack Moffitt committed
23 24 25 26 27 28 29 30
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <ogg/ogg.h>
#include <vorbis/codec.h>

#include "refbuf.h"
31 32
#include "source.h"
#include "client.h"
Jack Moffitt's avatar
Jack Moffitt committed
33

Michael Smith's avatar
Michael Smith committed
34
#include "stats.h"
Jack Moffitt's avatar
Jack Moffitt committed
35 36
#include "format.h"

37 38 39
#define CATMODULE "format-vorbis"
#include "logging.h"

Michael Smith's avatar
Michael Smith committed
40 41
#define MAX_HEADER_PAGES 10

Jack Moffitt's avatar
Jack Moffitt committed
42 43
typedef struct _vstate_tag
{
44 45 46 47 48 49 50 51 52 53
    ogg_sync_state oy;
    ogg_stream_state os;
    vorbis_info vi;
    vorbis_comment vc;

    ogg_page og;
    unsigned long serialno;
    int header;
    refbuf_t *headbuf[MAX_HEADER_PAGES];
    int packets;
Jack Moffitt's avatar
Jack Moffitt committed
54 55
} vstate_t;

56 57 58 59
static void format_vorbis_free_plugin(format_plugin_t *self);
static int format_vorbis_get_buffer(format_plugin_t *self, char *data, 
        unsigned long len, refbuf_t **buffer);
static refbuf_queue_t *format_vorbis_get_predata(format_plugin_t *self);
60 61 62 63
static void *format_vorbis_create_client_data(format_plugin_t *self,
        source_t *source, client_t *client);
static void format_vorbis_send_headers(format_plugin_t *self,
        source_t *source, client_t *client);
Jack Moffitt's avatar
Jack Moffitt committed
64 65 66

format_plugin_t *format_vorbis_get_plugin(void)
{
67 68
    format_plugin_t *plugin;
    vstate_t *state;
Jack Moffitt's avatar
Jack Moffitt committed
69

70
    plugin = (format_plugin_t *)malloc(sizeof(format_plugin_t));
71

72 73 74 75
    plugin->type = FORMAT_TYPE_VORBIS;
    plugin->has_predata = 1;
    plugin->get_buffer = format_vorbis_get_buffer;
    plugin->get_predata = format_vorbis_get_predata;
76 77
    plugin->write_buf_to_client = format_generic_write_buf_to_client;
    plugin->create_client_data = format_vorbis_create_client_data;
78
    plugin->client_send_headers = format_vorbis_send_headers;
79
    plugin->free_plugin = format_vorbis_free_plugin;
80
    plugin->format_description = "Ogg Vorbis";
Jack Moffitt's avatar
Jack Moffitt committed
81

82 83
    state = (vstate_t *)calloc(1, sizeof(vstate_t));
    ogg_sync_init(&state->oy);
Jack Moffitt's avatar
Jack Moffitt committed
84

85
    plugin->_state = (void *)state;
Jack Moffitt's avatar
Jack Moffitt committed
86

87
    return plugin;
Jack Moffitt's avatar
Jack Moffitt committed
88 89
}

90 91
void format_vorbis_free_plugin(format_plugin_t *self)
{
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
    int i;
    vstate_t *state = (vstate_t *)self->_state;

    /* free memory associated with this plugin instance */

    /* free state memory */
    ogg_sync_clear(&state->oy);
    ogg_stream_clear(&state->os);
    vorbis_comment_clear(&state->vc);
    vorbis_info_clear(&state->vi);
    
    for (i = 0; i < MAX_HEADER_PAGES; i++) {
        if (state->headbuf[i]) {
            refbuf_release(state->headbuf[i]);
            state->headbuf[i] = NULL;
        }
    }

    free(state);

    /* free the plugin instance */
    free(self);
114 115
}

Michael Smith's avatar
Michael Smith committed
116
int format_vorbis_get_buffer(format_plugin_t *self, char *data, unsigned long len, refbuf_t **buffer)
Jack Moffitt's avatar
Jack Moffitt committed
117
{
118 119 120 121
    char *buf;
    int i, result;
    ogg_packet op;
    char *tag;
122
    refbuf_t *refbuf, *source_refbuf;
123
    vstate_t *state = (vstate_t *)self->_state;
124
    source_t *source;
Jack Moffitt's avatar
Jack Moffitt committed
125

126 127 128
    if (data) {
        /* write the data to the buffer */
        buf = ogg_sync_buffer(&state->oy, len);
Michael Smith's avatar
Michael Smith committed
129
        memcpy(buf, data, len);
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
        ogg_sync_wrote(&state->oy, len);
    }

    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)) {
            /* this is a new logical bitstream */
            state->header = 0;
            state->packets = 0;

            /* release old headers, stream state, vorbis data */
            for (i = 0; i < MAX_HEADER_PAGES; i++) {
                if (state->headbuf[i]) {
                    refbuf_release(state->headbuf[i]);
                    state->headbuf[i] = NULL;
                }
            }
151
            /* Clear old stuff. Rarely but occasionally needed. */
152 153 154
            ogg_stream_clear(&state->os);
            vorbis_comment_clear(&state->vc);
            vorbis_info_clear(&state->vi);
Jack Moffitt's avatar
Jack Moffitt committed
155

156 157 158 159 160
            state->serialno = ogg_page_serialno(&state->og);
            ogg_stream_init(&state->os, state->serialno);
            vorbis_info_init(&state->vi);
            vorbis_comment_init(&state->vc);
        }
Jack Moffitt's avatar
Jack Moffitt committed
161

162
        if (state->header >= 0) {
Michael Smith's avatar
Michael Smith committed
163 164 165 166
            /* 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.
             */
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
            if (ogg_page_granulepos(&state->og) <= 0) {
                state->header++;
            } else {
                /* we're done caching headers */
                state->header = -1;

                /* put known comments in the stats */
                tag = vorbis_comment_query(&state->vc, "TITLE", 0);
                if (tag) stats_event(self->mount, "title", tag);
                else stats_event(self->mount, "title", "unknown");
                tag = vorbis_comment_query(&state->vc, "ARTIST", 0);
                if (tag) stats_event(self->mount, "artist", tag);
                else stats_event(self->mount, "artist", "unknown");

                /* don't need these now */
                ogg_stream_clear(&state->os);
                vorbis_comment_clear(&state->vc);
                vorbis_info_clear(&state->vi);
185 186 187 188

                /* Drain the source queue on metadata update otherwise you
                   could have a mismatch between what is on the source queue
                   and what is in the state->headbuf */
189
                avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
190
                source = source_find_mount_raw(self->mount);
191 192
                avl_tree_unlock(global.source_tree);

193 194 195 196 197 198
                thread_mutex_lock(&source->queue_mutex);
                while ((source_refbuf = refbuf_queue_remove(&source->queue))) {
                    refbuf_release(source_refbuf);
                }
                thread_mutex_unlock(&source->queue_mutex);

199
                yp_touch (self->mount);
200 201 202 203 204
            }
        }

        /* cache header pages */
        if (state->header > 0 && state->packets < 3) {
Michael Smith's avatar
Michael Smith committed
205 206
            if(state->header > MAX_HEADER_PAGES) {
                refbuf_release(refbuf);
207 208
                ERROR1("Bad vorbis input: header is more than %d pages long", MAX_HEADER_PAGES);

Michael Smith's avatar
Michael Smith committed
209 210
                return -1;
            }
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
            refbuf_addref(refbuf);
            state->headbuf[state->header - 1] = 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;
                        break;
                    }

                    state->packets++;

                    if (vorbis_synthesis_headerin(&state->vi, &state->vc, &op) < 0) {
                        state->packets = -1;
                        break;
                    }
                }
            }
        }
    }
Jack Moffitt's avatar
Jack Moffitt committed
234

Michael Smith's avatar
Michael Smith committed
235
    *buffer = refbuf;
236
    return 0;
Jack Moffitt's avatar
Jack Moffitt committed
237 238 239 240
}

refbuf_queue_t *format_vorbis_get_predata(format_plugin_t *self)
{
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
    refbuf_queue_t *queue;
    int i;
    vstate_t *state = (vstate_t *)self->_state;

    queue = NULL;
    for (i = 0; i < MAX_HEADER_PAGES; i++) {
        if (state->headbuf[i]) {
            refbuf_addref(state->headbuf[i]);
            refbuf_queue_add(&queue, state->headbuf[i]);
        } else {
            break;
        }
    }

    return queue;
Jack Moffitt's avatar
Jack Moffitt committed
256 257
}

258 259 260
static void *format_vorbis_create_client_data(format_plugin_t *self,
        source_t *source, client_t *client) 
{
261 262
    return NULL;
}
Jack Moffitt's avatar
Jack Moffitt committed
263

264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
static void format_vorbis_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", 
            format_get_mimetype(source->format->type));

    if(bytes > 0) client->con->sent_bytes += bytes;

    format_send_general_headers(self, source, client);
}

Jack Moffitt's avatar
Jack Moffitt committed
280