Re: [Icecast] Icecast WebM Support Patch Second Edition
Krad Radio <[email protected]>
| Newsgroups | gmane.comp.audio.icecast.devel,gmane.comp.audio.icecast.general |
|---|---|
| Message-ID | <CA+8JHGgb+p=ASk9Ma8vF7FLMgFO9x3MszwgaUEqHKk9kBpUWSw@mail.gmail.com> |
One liner fix update for a potential segfault when a client connects and the header is not yet received from the source. Updated full patch attached. On Thu, Feb 9, 2012 at 5:37 AM, Dennis Heerema <[email protected]> wrote: > Hi David, > > Streaming live WebM would also give a boost to this open format. > Whitch Client do you use to stream the webm format to icecast with? > I'm using Krad Cam, which is in a very early alpha form. More information is here: https://gist.github.com/1773943 There is also a "Test Signal" client that sends a video only stream for testing purposes. -David > Regards, > > Dennis > > > -----Original Message----- > From: Krad Radio <[email protected]> > To: [email protected], [email protected] > Date: Wed, 8 Feb 2012 23:53:05 -0500 > Subject: [Icecast] Icecast WebM Support Patch Second Edition > > Howdy, > > > tl;dr: Nothing new or interesting to non-developers > > Attached is a newer Icecast WebM support patch for Icecast SVN, there is > simply aesthetic changes. Whitespace has been altered to match Icecast > project style, > some functions have been renamed and moved around. > > Some discussion. > > The format_ebml.c file lines 0-296 operates much the same as the > format_ogg.c file does. It uses a parsing library api to feed in and pull > out bytes via > the _get_buffer function. In the case of Ogg this is of course libogg, in > the case of ebml, the "parsing library" consists of the functions below > line 296. > This mini-included library comes from some debugging code that I wrote > during my work on a much more robust EBML muxing library, originally I had > thought > icecast would need to do some manipulation of the EBML segment header in > all cases, but it turns out in most cases this is better done by the source > client. > This mini-included library implements the same function calls that > Icecast would need to use if it was using this EBML muxing library, but > "parse" is a very > generous descriptor of whats actually happening. If you are familiar with > the internals of Ogg streaming, you know that the stream starts out with a > few header pages > that are then followed by pages containing the acutual video and audio > packets. EBML doctype WebM and MKV (The only currently known) work in > a similar way. > There is a "Segment Header" followed by "Clusters" (Its actually more > nuanced than this, but this oversimplification will suffice for the > moment). Clusters are comparable to > Ogg pages. The connecting client needs to be sent the header, and then it > can start on any cluster as long as it starts exactly on a cluster. (This > is actually not a format requirement per se but all media players I am > aware of lack the ability to re-sync themselves if started on a random > byte, something I intend not to be a limitation of my own work). So, in a > live WebM stream, everything before the first cluster is the "Header" and > then the rest is the clusters, of which the boundary between them is marked > with 4 specific bytes. So at every cluster the refbuf is marked as a sync > point. The size of clusters could vary significantly during a stream or > from stream to stream, but on a properly constructed one would indicate a > keyframe in the case of a stream with video. The 'mini-library' doesn't > actually 'parse' the stream at all, all it does is look for the four byte > sequence indicating a cluster boundary and informs the format functions > appropriately. I am a fraudulent mathematician at best, but I calculate > that there is a 1 in 4.2 billion chance of this happening for any given 4 > bytes, and 1 in 4294 per megabyte, and likely once per 4.2 gigabytes. This > is not a problem once the stream has started for the client however, it > only matters when the source connects and the header is stored, and when a > client connects and needs a proper starting point. I suppose that makes it > very unlikely to cause a problem, even though its technically wrong. It > also means that each byte is being compared, whilst when properly parsing > most would be skipped, but computers are so damn fast that its moot. At any > rate proper parsing could be added this this mini-library or provided by > the external library as it matures. > > Enjoy, > > David > > > > > > > _______________________________________________ Icecast-dev mailing list [email protected] http://lists.xiph.org/mailman/listinfo/icecast-dev
icecast-webm-support-clean1-fix.patch
(text/x-patch, 20.1 KB)
diff --git a/AUTHORS b/AUTHORS index 0d4abc0..b063deb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,3 +4,4 @@ oddsock <[email protected]> Karl Heyes <[email protected]> Philipp "ph3-der-loewe" Schafft <[email protected]> Thomas B. "dm8tbr" Ruecker <[email protected]> +David "oneman" Richards <[email protected]> diff --git a/src/Makefile.am b/src/Makefile.am index 8c2ef21..3ed529e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,13 +10,13 @@ noinst_HEADERS = admin.h cfgfile.h logging.h sighandler.h connection.h \ global.h util.h slave.h source.h stats.h refbuf.h client.h \ compat.h fserve.h xslt.h yp.h event.h md5.h \ auth.h auth_htpasswd.h auth_url.h \ - format.h format_ogg.h format_mp3.h \ + format.h format_ogg.h format_mp3.h format_ebml.h\ format_vorbis.h format_theora.h format_flac.h format_speex.h format_midi.h \ format_kate.h format_skeleton.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 \ xslt.c fserve.c event.c admin.c md5.c \ - format.c format_ogg.c format_mp3.c format_midi.c format_flac.c \ + format.c format_ogg.c format_mp3.c format_midi.c format_flac.c format_ebml.c\ auth.c auth_htpasswd.c format_kate.c format_skeleton.c EXTRA_icecast_SOURCES = yp.c \ auth_url.c \ diff --git a/src/format.c b/src/format.c index 415391c..8409294 100644 --- a/src/format.c +++ b/src/format.c @@ -40,6 +40,7 @@ #include "format_ogg.h" #include "format_mp3.h" +#include "format_ebml.h" #include "logging.h" #include "stats.h" @@ -64,6 +65,16 @@ format_type_t format_get_type (const char *contenttype) return FORMAT_TYPE_OGG; else if(strcmp(contenttype, "video/ogg") == 0) return FORMAT_TYPE_OGG; + else if(strcmp(contenttype, "audio/webm") == 0) + return FORMAT_TYPE_EBML; + else if(strcmp(contenttype, "video/webm") == 0) + return FORMAT_TYPE_EBML; + else if(strcmp(contenttype, "audio/x-matroska") == 0) + return FORMAT_TYPE_EBML; + else if(strcmp(contenttype, "video/x-matroska") == 0) + return FORMAT_TYPE_EBML; + else if(strcmp(contenttype, "video/x-matroska-3d") == 0) + return FORMAT_TYPE_EBML; else /* We default to the Generic format handler, which can handle many more formats than just mp3 */ @@ -78,6 +89,9 @@ int format_get_plugin(format_type_t type, source_t *source) case FORMAT_TYPE_OGG: ret = format_ogg_get_plugin (source); break; + case FORMAT_TYPE_EBML: + ret = format_ebml_get_plugin (source); + break; case FORMAT_TYPE_GENERIC: ret = format_mp3_get_plugin (source); break; diff --git a/src/format.h b/src/format.h index d52b9e9..0a96ee9 100644 --- a/src/format.h +++ b/src/format.h @@ -29,6 +29,7 @@ typedef enum _format_type_tag { FORMAT_ERROR, /* No format, source not processable */ FORMAT_TYPE_OGG, + FORMAT_TYPE_EBML, FORMAT_TYPE_GENERIC } format_type_t; diff --git a/src/format_ebml.c b/src/format_ebml.c new file mode 100644 index 0000000..16b7fb7 --- /dev/null +++ b/src/format_ebml.c @@ -0,0 +1,571 @@ +/* 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-2012, Jack Moffitt <[email protected], + * Michael Smith <[email protected]>, + * oddsock <[email protected]>, + * Karl Heyes <[email protected]> + * and others (see AUTHORS for details). + */ + +/* format_ebml.c + * + * format plugin for EBML + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "refbuf.h" +#include "source.h" +#include "client.h" + +#include "stats.h" +#include "format.h" +#include "format_ebml.h" + +#include "logging.h" + +#define CATMODULE "format-ebml" + +#define EBML_DEBUG 0 +#define EBML_HEADER_MAX_SIZE 131072 +#define EBML_SLICE_SIZE 4096 + +#define EBML_CLUSTER_BYTE1 0x1F +#define EBML_CLUSTER_BYTE2 0x43 +#define EBML_CLUSTER_BYTE3 0xB6 +#define EBML_CLUSTER_BYTE4 0x75 + +typedef struct ebml_client_data_st ebml_client_data_t; + +struct ebml_client_data_st { + + refbuf_t *header; + int header_pos; + +}; + +struct ebml_st { + + char cluster_mark[4]; + + uint64_t position; + uint64_t read_position; + int buffer_position; + uint64_t cluster_position; + + int header_read; + + int header_size; + int header_position; + int header_read_position; + + unsigned char *input_buffer; + unsigned char *buffer; + unsigned char *header; + + uint64_t found; + uint64_t matched_byte_num; + unsigned char match_byte; + + int last_was_cluster_end; + int this_was_cluster_start; + +}; + +static void ebml_free_plugin (format_plugin_t *plugin); +static refbuf_t *ebml_get_buffer (source_t *source); +static int ebml_write_buf_to_client (client_t *client); +static void ebml_write_buf_to_file (source_t *source, refbuf_t *refbuf); +static int ebml_create_client_data (source_t *source, client_t *client); +static void ebml_free_client_data (client_t *client); + +static ebml_t *ebml_create(); +static void ebml_destroy(ebml_t *ebml); +static size_t ebml_read_space(ebml_t *ebml); +static int ebml_read(ebml_t *ebml, char *buffer, int len); +static int ebml_last_was_sync(ebml_t *ebml); +static char *ebml_write_buffer(ebml_t *ebml, int len); +static int ebml_wrote(ebml_t *ebml, int len); +static void ebml_debug(ebml_t *ebml); +static unsigned char ebml_get_next_match_byte(unsigned char match_byte, uint64_t position, + uint64_t *matched_byte_num, uint64_t *found); + +int format_ebml_get_plugin (source_t *source) +{ + + ebml_source_state_t *ebml_source_state = calloc(1, sizeof(ebml_source_state_t)); + format_plugin_t *plugin = calloc(1, sizeof(format_plugin_t)); + + plugin->get_buffer = ebml_get_buffer; + plugin->write_buf_to_client = ebml_write_buf_to_client; + plugin->create_client_data = ebml_create_client_data; + plugin->free_plugin = ebml_free_plugin; + plugin->write_buf_to_file = ebml_write_buf_to_file; + plugin->set_tag = NULL; + plugin->apply_settings = NULL; + + plugin->contenttype = httpp_getvar (source->parser, "content-type"); + + plugin->_state = ebml_source_state; + source->format = plugin; + + ebml_source_state->ebml = ebml_create(); + return 0; +} + +static void ebml_free_plugin (format_plugin_t *plugin) +{ + + ebml_source_state_t *ebml_source_state = plugin->_state; + + refbuf_release (ebml_source_state->header); + ebml_destroy(ebml_source_state->ebml); + free (ebml_source_state); + free (plugin); + +} + +static int send_ebml_header (client_t *client) +{ + + ebml_client_data_t *ebml_client_data = client->format_data; + int len = EBML_SLICE_SIZE; + int ret; + + if (ebml_client_data->header->len - ebml_client_data->header_pos < len) + { + len = ebml_client_data->header->len - ebml_client_data->header_pos; + } + ret = client_send_bytes (client, + ebml_client_data->header->data + ebml_client_data->header_pos, + len); + + if (ret > 0) + { + ebml_client_data->header_pos += ret; + } + + return ret; + +} + +static int ebml_write_buf_to_client (client_t *client) +{ + + ebml_client_data_t *ebml_client_data = client->format_data; + + if (ebml_client_data->header_pos != ebml_client_data->header->len) + { + return send_ebml_header (client); + } + else + { + client->write_to_client = format_generic_write_to_client; + return client->write_to_client(client); + } + +} + +static refbuf_t *ebml_get_buffer (source_t *source) +{ + + ebml_source_state_t *ebml_source_state = source->format->_state; + format_plugin_t *format = source->format; + char *data = NULL; + int bytes = 0; + refbuf_t *refbuf; + int ret; + + while (1) + { + + if (EBML_DEBUG) { + ebml_debug(ebml_source_state->ebml); + } + + if ((bytes = ebml_read_space(ebml_source_state->ebml)) > 0) + { + refbuf = refbuf_new(bytes); + ebml_read(ebml_source_state->ebml, refbuf->data, bytes); + + if (ebml_source_state->header == NULL) + { + ebml_source_state->header = refbuf; + continue; + } + + if (ebml_last_was_sync(ebml_source_state->ebml)) + { + refbuf->sync_point = 1; + } + return refbuf; + + } + else + { + + data = ebml_write_buffer(ebml_source_state->ebml, EBML_SLICE_SIZE); + bytes = client_read_bytes (source->client, data, EBML_SLICE_SIZE); + if (bytes <= 0) + { + ebml_wrote (ebml_source_state->ebml, 0); + return NULL; + } + format->read_bytes += bytes; + ret = ebml_wrote (ebml_source_state->ebml, bytes); + if (ret != bytes) { + ERROR0 ("Problem processing stream"); + source->running = 0; + return NULL; + } + } + } +} + +static int ebml_create_client_data (source_t *source, client_t *client) +{ + + ebml_client_data_t *ebml_client_data = calloc(1, sizeof(ebml_client_data_t)); + ebml_source_state_t *ebml_source_state = source->format->_state; + + int ret = -1; + + if ((ebml_client_data) && (ebml_source_state->header)) + { + ebml_client_data->header = ebml_source_state->header; + refbuf_addref (ebml_client_data->header); + client->format_data = ebml_client_data; + client->free_client_data = ebml_free_client_data; + ret = 0; + } + + return ret; + +} + + +static void ebml_free_client_data (client_t *client) +{ + + ebml_client_data_t *ebml_client_data = client->format_data; + + refbuf_release (ebml_client_data->header); + free (client->format_data); + client->format_data = NULL; +} + + +static void ebml_write_buf_to_file_fail (source_t *source) +{ + WARN0 ("Write to dump file failed, disabling"); + fclose (source->dumpfile); + source->dumpfile = NULL; +} + + +static void ebml_write_buf_to_file (source_t *source, refbuf_t *refbuf) +{ + + ebml_source_state_t *ebml_source_state = source->format->_state; + + if (ebml_source_state->file_headers_written == 0) + { + if (fwrite (ebml_source_state->header->data, 1, + ebml_source_state->header->len, + source->dumpfile) != ebml_source_state->header->len) + ebml_write_buf_to_file_fail(source); + else + ebml_source_state->file_headers_written = 1; + } + + if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len) + { + ebml_write_buf_to_file_fail(source); + } + +} + + +/* internal ebml parsing */ + +static void ebml_debug(ebml_t *ebml) { + printf("EBML Stream Write Position: %zu Read Position: %zu Buffer Position: %d " + "Cluster Position: %zu Header Read: %d Header Size: %d Header Write " + "Position: %d Header Read Position: %d\n", + ebml->position, + ebml->read_position, + ebml->buffer_position, + ebml->cluster_position, + ebml->header_read, + ebml->header_size, + ebml->header_position, + ebml->header_read_position); +} + + +static unsigned char ebml_get_next_match_byte(unsigned char match_byte, uint64_t position, + uint64_t *matched_byte_num, uint64_t *found) { + + if (found != NULL) { + *found = 0; + } + + if (matched_byte_num != NULL) { + if (match_byte == EBML_CLUSTER_BYTE1) { + if (matched_byte_num != NULL) { + *matched_byte_num = position; + } + return EBML_CLUSTER_BYTE2; + } + + if ((*matched_byte_num == position - 1) && (match_byte == EBML_CLUSTER_BYTE2)) { + return EBML_CLUSTER_BYTE3; + } + + if ((*matched_byte_num == position - 2) && (match_byte == EBML_CLUSTER_BYTE3)) { + return EBML_CLUSTER_BYTE4; + } + + if ((*matched_byte_num == position - 3) && (match_byte == EBML_CLUSTER_BYTE4)) { + if (found != NULL) { + *found = *matched_byte_num; + } + *matched_byte_num = 0; + return EBML_CLUSTER_BYTE1; + } + + *matched_byte_num = 0; + } + + return EBML_CLUSTER_BYTE1; + +} + +static void ebml_destroy(ebml_t *ebml) { + + free(ebml->header); + free(ebml->input_buffer); + free(ebml->buffer); + free(ebml); + +} + +static ebml_t *ebml_create() { + + ebml_t *ebml = calloc(1, sizeof(ebml_t)); + + ebml->header = calloc(1, EBML_HEADER_MAX_SIZE); + ebml->buffer = calloc(1, EBML_SLICE_SIZE); + ebml->input_buffer = calloc(1, EBML_SLICE_SIZE); + + ebml->cluster_mark[0] = EBML_CLUSTER_BYTE1; + ebml->cluster_mark[1] = EBML_CLUSTER_BYTE2; + ebml->cluster_mark[2] = EBML_CLUSTER_BYTE3; + ebml->cluster_mark[3] = EBML_CLUSTER_BYTE4; + + return ebml; + +} + +static size_t ebml_read_space(ebml_t *ebml) { + + size_t read_space; + + if (ebml->header_read == 1) { + read_space = (ebml->position - ebml->header_size) - ebml->read_position; + + return read_space; + } else { + if (ebml->header_size != 0) { + return ebml->header_size - ebml->header_read_position; + } else { + return 0; + } + } + + +} + +static int ebml_read(ebml_t *ebml, char *buffer, int len) { + + size_t read_space; + size_t read_space_to_cluster; + int to_read; + + read_space_to_cluster = 0; + + if (len < 1) { + return 0; + } + + if (ebml->header_read == 1) { + read_space = (ebml->position - ebml->header_size) - ebml->read_position; + + if (read_space < 1) { + return 0; + } + + if (read_space >= len ) { + to_read = len; + } else { + to_read = read_space; + } + + if (ebml->cluster_position != 0) { + read_space_to_cluster = + (ebml->cluster_position - ebml->header_size) - ebml->read_position; + if ((read_space_to_cluster != 0) && (read_space_to_cluster <= to_read)) { + to_read = read_space_to_cluster; + ebml->cluster_position = 0; + ebml->last_was_cluster_end = 1; + } else { + if (read_space_to_cluster == 0) { + ebml->this_was_cluster_start = 1; + } + } + } + + memcpy(buffer, ebml->buffer, to_read); + ebml->read_position += to_read; + memmove(ebml->buffer, ebml->buffer + to_read, ebml->buffer_position - to_read); + ebml->buffer_position -= to_read; + + } else { + if (ebml->header_size != 0) { + + read_space = ebml->header_size - ebml->header_read_position; + + if (read_space >= len ) { + to_read = len; + } else { + to_read = read_space; + } + + memcpy(buffer, ebml->header, to_read); + ebml->header_read_position += to_read; + + if (ebml->header_read_position == ebml->header_size) { + ebml->header_read = 1; + } + + } else { + return 0; + } + } + + + return to_read; + +} + +static int ebml_last_was_sync(ebml_t *ebml) { + + if (ebml->last_was_cluster_end == 1) { + ebml->last_was_cluster_end = 0; + ebml->this_was_cluster_start = 1; + } + + if (ebml->this_was_cluster_start == 1) { + ebml->this_was_cluster_start = 0; + return 1; + } + + return 0; + +} + +static char *ebml_write_buffer(ebml_t *ebml, int len) { + + return (char *)ebml->input_buffer; + +} + + +static int ebml_wrote(ebml_t *ebml, int len) { + + int b; + + for (b = 0; b < len; b++) { + if ((ebml->input_buffer[b] == ebml->match_byte) || (ebml->matched_byte_num > 0)) { + ebml->match_byte = ebml_get_next_match_byte(ebml->input_buffer[b], + ebml->position + b, + &ebml->matched_byte_num, + &ebml->found); + if (ebml->found > 0) { + if (ebml->header_size == 0) { + if (b > 0) { + if ((ebml->header_position + b) > EBML_HEADER_MAX_SIZE) { + ERROR0("EBML Header to large, failing"); + return -1; + } + memcpy(ebml->header + ebml->header_position, ebml->input_buffer, b); + ebml->header_position += b; + } + ebml->header_size = (ebml->header_position - 4) + 1; + if (EBML_DEBUG) { + printf("EBML: Got header %d bytes\n", ebml->header_size); + } + /* first cluster */ + memcpy(ebml->buffer, ebml->cluster_mark, 4); + ebml->buffer_position += 4; + if ((b + 1) < len) { + if ((ebml->buffer_position + (len - (b + 1))) > EBML_SLICE_SIZE) { + ERROR0("EBML Overflow, failing"); + return -1; + } + memcpy(ebml->buffer + ebml->buffer_position, + ebml->input_buffer + (b + 1), + len - (b + 1)); + ebml->buffer_position += len - (b + 1); + } + if (EBML_DEBUG) { + printf("EBML: Found first cluster starting at offset: %zu\n", + ebml->found); + } + ebml->cluster_position = ebml->found; + ebml->position += len; + return len; + + } + if (EBML_DEBUG) { + printf("EBML: Found cluster starting at offset: %zu\n", ebml->found); + } + ebml->cluster_position = ebml->found; + } + } + } + + if (ebml->header_size == 0) { + if ((ebml->header_position + len) > EBML_HEADER_MAX_SIZE) { + ERROR0("EBML Header to large, failing"); + return -1; + } + if (EBML_DEBUG) { + printf("EBML: Adding to header, ofset is %d size is %d adding %d\n", + ebml->header_size, ebml->header_position, len); + } + memcpy(ebml->header + ebml->header_position, ebml->input_buffer, len); + ebml->header_position += len; + } else { + if ((ebml->buffer_position + len) > EBML_SLICE_SIZE) { + ERROR0("EBML Overflow, failing"); + return -1; + } + memcpy(ebml->buffer + ebml->buffer_position, ebml->input_buffer, len); + ebml->buffer_position += len; + } + + ebml->position += len; + + return len; +} diff --git a/src/format_ebml.h b/src/format_ebml.h new file mode 100644 index 0000000..c1d6d07 --- /dev/null +++ b/src/format_ebml.h @@ -0,0 +1,36 @@ +/* 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-2012, Jack Moffitt <[email protected], + * Michael Smith <[email protected]>, + * oddsock <[email protected]>, + * Karl Heyes <[email protected]> + * and others (see AUTHORS for details). + */ + +/* format_ebml.h +** +** ebml format plugin header +** +*/ +#ifndef __FORMAT_EBML_H__ +#define __FORMAT_EBML_H__ + +#include "format.h" + +typedef struct ebml_st ebml_t; +typedef struct ebml_source_state_st ebml_source_state_t; + +struct ebml_source_state_st { + + ebml_t *ebml; + refbuf_t *header; + int file_headers_written; + +}; + +int format_ebml_get_plugin (source_t *source); + +#endif /* __FORMAT_EBML_H__ */