format_mp3.c 6.8 KB
Newer Older
Michael Smith's avatar
Michael Smith committed
1
2
/* format_mp3.c
**
Michael Smith's avatar
Michael Smith committed
3
** format plugin for mp3
Michael Smith's avatar
Michael Smith committed
4
5
6
7
8
9
10
11
**
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "refbuf.h"
Michael Smith's avatar
Michael Smith committed
12
13
#include "source.h"
#include "client.h"
Michael Smith's avatar
Michael Smith committed
14
15
16

#include "stats.h"
#include "format.h"
Michael Smith's avatar
Michael Smith committed
17
18
19
20
21
22
23
24
25
#include "httpp/httpp.h"

#include "log.h"
#include "logging.h"

#include "format_mp3.h"

#define CATMODULE "format-mp3"

26
27
28
/* Note that this seems to be 8192 in shoutcast - perhaps we want to be the
 * same for compability with crappy clients?
 */
Michael Smith's avatar
Michael Smith committed
29
#define ICY_METADATA_INTERVAL 16000
Michael Smith's avatar
Michael Smith committed
30

31
32
33
34
static void format_mp3_free_plugin(format_plugin_t *self);
static int format_mp3_get_buffer(format_plugin_t *self, char *data, 
        unsigned long len, refbuf_t **buffer);
static refbuf_queue_t *format_mp3_get_predata(format_plugin_t *self);
Michael Smith's avatar
Michael Smith committed
35
36
37
38
39
40
41
42
static void *format_mp3_create_client_data(format_plugin_t *self,
        source_t *source, client_t *client);
static int format_mp3_write_buf_to_client(format_plugin_t *self,
        client_t *client, unsigned char *buf, int len);
static void format_mp3_send_headers(format_plugin_t *self, 
        source_t *source, client_t *client);

typedef struct {
Michael Smith's avatar
Michael Smith committed
43
   int use_metadata;
Michael Smith's avatar
Michael Smith committed
44
45
   int interval;
   int offset;
Michael Smith's avatar
Michael Smith committed
46
47
   int metadata_age;
   int metadata_offset;
Michael Smith's avatar
Michael Smith committed
48
} mp3_client_data;
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
49
50
51
52

#ifdef WIN32
#define alloca _alloca
#endif
Michael Smith's avatar
Michael Smith committed
53

54
format_plugin_t *format_mp3_get_plugin(http_parser_t *parser)
Michael Smith's avatar
Michael Smith committed
55
{
56
    char *metadata;
Michael Smith's avatar
Michael Smith committed
57
	format_plugin_t *plugin;
Michael Smith's avatar
Michael Smith committed
58
    mp3_state *state = calloc(1, sizeof(mp3_state));
Michael Smith's avatar
Michael Smith committed
59
60
61
62
63
64
65

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

	plugin->type = FORMAT_TYPE_MP3;
	plugin->has_predata = 0;
	plugin->get_buffer = format_mp3_get_buffer;
	plugin->get_predata = format_mp3_get_predata;
Michael Smith's avatar
Michael Smith committed
66
    plugin->write_buf_to_client = format_mp3_write_buf_to_client;
67
    plugin->create_client_data = format_mp3_create_client_data;
Michael Smith's avatar
Michael Smith committed
68
    plugin->client_send_headers = format_mp3_send_headers;
Michael Smith's avatar
Michael Smith committed
69
	plugin->free_plugin = format_mp3_free_plugin;
70
    plugin->format_description = "MP3 audio";
Michael Smith's avatar
Michael Smith committed
71

Michael Smith's avatar
Michael Smith committed
72
73
	plugin->_state = state;

Michael Smith's avatar
Michael Smith committed
74
75
    state->metadata_age = 0;
    state->metadata = strdup("");
Michael Smith's avatar
Michael Smith committed
76
    thread_mutex_create(&(state->lock));
Michael Smith's avatar
Michael Smith committed
77

78
79
80
81
    metadata = httpp_getvar(parser, "icy-metaint");
    if(metadata)
        state->inline_metadata_interval = atoi(metadata);

Michael Smith's avatar
Michael Smith committed
82
83
84
	return plugin;
}

Michael Smith's avatar
Michael Smith committed
85
86
87
88
89
90
91
92
93
static int send_metadata(client_t *client, mp3_client_data *client_state,
        mp3_state *source_state)
{
    int send_metadata;
    int len_byte;
    int len;
    unsigned char *buf;
    int ret;
    int source_age;
94
95
    char	*fullmetadata = NULL;
    int	fullmetadata_size = 0;
Michael Smith's avatar
Michael Smith committed
96
97
98
99
100
101
102
103

    thread_mutex_lock(&(source_state->lock));
    if(source_state->metadata == NULL) {
        /* Shouldn't be possible */
        thread_mutex_unlock(&(source_state->lock));
        return 0;
    }

104
105
    fullmetadata_size = strlen(source_state->metadata) + 
        strlen("StreamTitle='';StreamUrl=''") + 1;
106
107
108
109
110

    fullmetadata = alloca(fullmetadata_size);

    memset(fullmetadata, 0, fullmetadata_size);

111
112
    sprintf(fullmetadata, "StreamTitle='%s';StreamUrl=''", 
            source_state->metadata);
113

Michael Smith's avatar
Michael Smith committed
114
    source_age = source_state->metadata_age;
Michael Smith's avatar
Michael Smith committed
115
116
    send_metadata = source_age != client_state->metadata_age;

117
118
    if(send_metadata && strlen(fullmetadata) > 0)
        len_byte = strlen(fullmetadata)/16 + 1 - 
Michael Smith's avatar
Michael Smith committed
119
120
121
            client_state->metadata_offset;
    else
        len_byte = 0;
Michael Smith's avatar
Michael Smith committed
122
123
124
125
126
127
128
    len = 1 + len_byte*16;
    buf = alloca(len);

    memset(buf, 0, len);

    buf[0] = len_byte;

129
    if (len > 1) {
130
131
        strncpy(buf+1, fullmetadata + client_state->metadata_offset, len-2);
        DEBUG1("Sending metadata (%s)", buf+1);
132
    }
Michael Smith's avatar
Michael Smith committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

    thread_mutex_unlock(&(source_state->lock));

    ret = sock_write_bytes(client->con->sock, buf, len);

    if(ret > 0 && ret < len) {
        client_state->metadata_offset += ret;
    }
    else if(ret == len) {
        client_state->metadata_age = source_age;
        client_state->offset = 0;
        client_state->metadata_offset = 0;
    }

    return ret;
}

Michael Smith's avatar
Michael Smith committed
150
151
152
153
static int format_mp3_write_buf_to_client(format_plugin_t *self, 
    client_t *client, unsigned char *buf, int len) 
{
    int ret;
Michael Smith's avatar
Michael Smith committed
154
    
155
156
    if(((mp3_state *)self->_state)->metadata && 
            ((mp3_client_data *)(client->format_data))->use_metadata)
Michael Smith's avatar
Michael Smith committed
157
158
159
    {
        mp3_client_data *state = client->format_data;
        int max = state->interval - state->offset;
Michael Smith's avatar
Michael Smith committed
160

Michael Smith's avatar
Michael Smith committed
161
162
163
164
165
166
167
168
169
170
171
        if(len == 0) /* Shouldn't happen */
            return 0;

        if(max > len)
            max = len;

        if(max > 0) {
            ret = sock_write_bytes(client->con->sock, buf, max);
            if(ret > 0)
                state->offset += ret;
        }
172
        else {
Michael Smith's avatar
Michael Smith committed
173
            ret = send_metadata(client, state, self->_state);
174
175
176
177
            if(ret > 0)
                client->con->sent_bytes += ret;
            ret = 0;
        }
178

Michael Smith's avatar
Michael Smith committed
179
180
181
182
    }
    else {
        ret = sock_write_bytes(client->con->sock, buf, len);
    }
Michael Smith's avatar
Michael Smith committed
183
184

    if(ret < 0) {
185
        if(sock_recoverable(sock_error())) {
Michael Smith's avatar
Michael Smith committed
186
187
188
189
190
191
192
193
194
195
196
            DEBUG1("Client had recoverable error %ld", ret);
            ret = 0;
        }
    }
    else
        client->con->sent_bytes += ret;

    return ret;
}

static void format_mp3_free_plugin(format_plugin_t *self)
Michael Smith's avatar
Michael Smith committed
197
198
{
	/* free the plugin instance */
Michael Smith's avatar
Michael Smith committed
199
200
    mp3_state *state = self->_state;
    thread_mutex_destroy(&(state->lock));
Michael Smith's avatar
Michael Smith committed
201
202

    free(state->metadata);
Michael Smith's avatar
Michael Smith committed
203
    free(state);
Michael Smith's avatar
Michael Smith committed
204
205
206
	free(self);
}

Michael Smith's avatar
Michael Smith committed
207
208
static int format_mp3_get_buffer(format_plugin_t *self, char *data, 
    unsigned long len, refbuf_t **buffer)
Michael Smith's avatar
Michael Smith committed
209
210
{
	refbuf_t *refbuf;
211
212
    mp3_state *state = self->_state;

Michael Smith's avatar
Michael Smith committed
213
214
215
216
    if(!data) {
        *buffer = NULL;
        return 0;
    }
217
218
219
220
221
    if(state->inline_metadata_interval) {
        return 0;
    }
    else {
        refbuf = refbuf_new(len);
Michael Smith's avatar
Michael Smith committed
222

223
        memcpy(refbuf->data, data, len);
Michael Smith's avatar
Michael Smith committed
224

225
226
227
        *buffer = refbuf;
	    return 0;
    }
Michael Smith's avatar
Michael Smith committed
228
229
}

Michael Smith's avatar
Michael Smith committed
230
static refbuf_queue_t *format_mp3_get_predata(format_plugin_t *self)
Michael Smith's avatar
Michael Smith committed
231
232
233
234
{
    return NULL;
}

Michael Smith's avatar
Michael Smith committed
235
236
237
238
239
240
241
242
243
static void *format_mp3_create_client_data(format_plugin_t *self, 
        source_t *source, client_t *client) 
{
    mp3_client_data *data = calloc(1,sizeof(mp3_client_data));
    char *metadata;

    data->interval = ICY_METADATA_INTERVAL;
    data->offset = 0;

244
    metadata = httpp_getvar(client->parser, "icy-metadata");
Michael Smith's avatar
Michael Smith committed
245
    if(metadata)
Michael Smith's avatar
Michael Smith committed
246
        data->use_metadata = atoi(metadata)>0?1:0;
Michael Smith's avatar
Michael Smith committed
247
248

    return data;
249
}
Michael Smith's avatar
Michael Smith committed
250

Michael Smith's avatar
Michael Smith committed
251
252
253
254
255
256
static void format_mp3_send_headers(format_plugin_t *self,
        source_t *source, client_t *client)
{
    int bytes;
    
    client->respcode = 200;
Michael Smith's avatar
Michael Smith committed
257
    /* TODO: This may need to be ICY/1.0 for shoutcast-compatibility? */
Michael Smith's avatar
Michael Smith committed
258
259
260
261
262
263
264
265
266
    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);

Michael Smith's avatar
Michael Smith committed
267
    if(((mp3_client_data *)(client->format_data))->use_metadata) {
Michael Smith's avatar
Michael Smith committed
268
269
270
271
272
273
274
275
276
        int bytes = sock_write(client->con->sock, "icy-metaint: %d\r\n", 
                ICY_METADATA_INTERVAL);
        if(bytes > 0)
            client->con->sent_bytes += bytes;
    }
}



Michael Smith's avatar
Michael Smith committed
277