mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2024-12-04 14:46:30 -05:00
Implement EBML-aware parser.
* Loop over elements in input buffer. * Most are literally copied with their contents without inspection into the header or data buffers as appropriate. * Some only copy the element header, to allow inspecting children elements. * Cluster elements are identified and used as sync points. No probing is done for keyframes *yet*
This commit is contained in:
parent
50c4984c78
commit
def5a4cf6c
@ -47,6 +47,14 @@ typedef enum ebml_read_mode {
|
|||||||
EBML_STATE_READING_CLUSTERS
|
EBML_STATE_READING_CLUSTERS
|
||||||
} ebml_read_mode;
|
} ebml_read_mode;
|
||||||
|
|
||||||
|
typedef enum ebml_parsing_state {
|
||||||
|
EBML_STATE_PARSING_HEADER = 0,
|
||||||
|
EBML_STATE_COPYING_TO_HEADER,
|
||||||
|
EBML_STATE_START_CLUSTER,
|
||||||
|
EBML_STATE_PARSING_CLUSTERS,
|
||||||
|
EBML_STATE_COPYING_TO_DATA
|
||||||
|
} ebml_parsing_state;
|
||||||
|
|
||||||
typedef enum ebml_chunk_type {
|
typedef enum ebml_chunk_type {
|
||||||
EBML_CHUNK_HEADER = 0,
|
EBML_CHUNK_HEADER = 0,
|
||||||
EBML_CHUNK_CLUSTER_START,
|
EBML_CHUNK_CLUSTER_START,
|
||||||
@ -65,8 +73,9 @@ struct ebml_client_data_st {
|
|||||||
struct ebml_st {
|
struct ebml_st {
|
||||||
|
|
||||||
ebml_read_mode output_state;
|
ebml_read_mode output_state;
|
||||||
|
ebml_parsing_state parse_state;
|
||||||
|
unsigned long long copy_len;
|
||||||
|
|
||||||
char *cluster_id;
|
|
||||||
int cluster_start;
|
int cluster_start;
|
||||||
|
|
||||||
int position;
|
int position;
|
||||||
@ -333,11 +342,9 @@ static ebml_t *ebml_create()
|
|||||||
ebml->output_state = EBML_STATE_READING_HEADER;
|
ebml->output_state = EBML_STATE_READING_HEADER;
|
||||||
|
|
||||||
ebml->header = calloc(1, EBML_HEADER_MAX_SIZE);
|
ebml->header = calloc(1, EBML_HEADER_MAX_SIZE);
|
||||||
ebml->buffer = calloc(1, EBML_SLICE_SIZE * 4);
|
ebml->buffer = calloc(1, EBML_SLICE_SIZE);
|
||||||
ebml->input_buffer = calloc(1, EBML_SLICE_SIZE);
|
ebml->input_buffer = calloc(1, EBML_SLICE_SIZE);
|
||||||
|
|
||||||
ebml->cluster_id = "\x1F\x43\xB6\x75";
|
|
||||||
|
|
||||||
ebml->cluster_start = -1;
|
ebml->cluster_start = -1;
|
||||||
|
|
||||||
return ebml;
|
return ebml;
|
||||||
@ -487,62 +494,151 @@ static unsigned char *ebml_get_write_buffer(ebml_t *ebml, int *bytes)
|
|||||||
*/
|
*/
|
||||||
static int ebml_wrote(ebml_t *ebml, int len)
|
static int ebml_wrote(ebml_t *ebml, int len)
|
||||||
{
|
{
|
||||||
|
int processing = 1;
|
||||||
|
int cursor = 0;
|
||||||
|
int to_copy;
|
||||||
|
unsigned char *end_of_buffer;
|
||||||
|
|
||||||
int b;
|
int tag_length;
|
||||||
|
unsigned long long payload_length;
|
||||||
|
int copy_state;
|
||||||
|
|
||||||
|
char *segment_id = "\x18\x53\x80\x67";
|
||||||
|
char *cluster_id = "\x1F\x43\xB6\x75";
|
||||||
|
|
||||||
|
ebml->input_position += len;
|
||||||
|
end_of_buffer = ebml->input_buffer + ebml->input_position;
|
||||||
|
|
||||||
|
while (processing) {
|
||||||
|
|
||||||
|
/*ICECAST_LOG_DEBUG("Parse State: %i", ebml->parse_state);*/
|
||||||
|
|
||||||
|
switch (ebml->parse_state) {
|
||||||
|
|
||||||
|
case EBML_STATE_PARSING_HEADER:
|
||||||
|
case EBML_STATE_PARSING_CLUSTERS:
|
||||||
|
|
||||||
|
if (ebml->parse_state == EBML_STATE_PARSING_HEADER) {
|
||||||
|
copy_state = EBML_STATE_COPYING_TO_HEADER;
|
||||||
|
} else {
|
||||||
|
copy_state = EBML_STATE_COPYING_TO_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag_length = ebml_parse_tag(ebml->input_buffer + cursor,
|
||||||
|
end_of_buffer, &payload_length);
|
||||||
|
|
||||||
|
if (tag_length > 0) {
|
||||||
|
|
||||||
|
if (payload_length == EBML_UNKNOWN) {
|
||||||
|
/* Parse all children for tags we can't skip */
|
||||||
|
payload_length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!memcmp(ebml->input_buffer + cursor, cluster_id, 4)) {
|
||||||
|
/* Found a cluster */
|
||||||
|
ebml->parse_state = EBML_STATE_START_CLUSTER;
|
||||||
|
|
||||||
|
} else if (!memcmp(ebml->input_buffer + cursor, segment_id, 4)) {
|
||||||
|
/* Segment tag, copy tag to buffer but parse children */
|
||||||
|
ebml->copy_len = tag_length;
|
||||||
|
ebml->parse_state = copy_state;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Non-cluster tag, copy it & children into buffer */
|
||||||
|
ebml->copy_len = tag_length + payload_length;
|
||||||
|
ebml->parse_state = copy_state;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (tag_length == 0) {
|
||||||
|
/* Wait for more data */
|
||||||
|
/* ICECAST_LOG_DEBUG("Wait"); */
|
||||||
|
processing = 0;
|
||||||
|
} else if (tag_length < 0) {
|
||||||
|
/* Parse error */
|
||||||
|
/* ICECAST_LOG_DEBUG("Stop"); */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EBML_STATE_START_CLUSTER:
|
||||||
|
/* found a cluster; wait to process it until
|
||||||
|
* any previous cluster tag has been flushed
|
||||||
|
* from the read buffer, so as to not lose the
|
||||||
|
* sync point.
|
||||||
|
*/
|
||||||
|
if (ebml->cluster_start >= 0) {
|
||||||
|
processing = 0;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
tag_length = ebml_parse_tag(ebml->input_buffer + cursor,
|
||||||
|
end_of_buffer, &payload_length);
|
||||||
|
|
||||||
|
/* The header has been fully read by now, publish its size. */
|
||||||
|
ebml->header_size = ebml->header_position;
|
||||||
|
|
||||||
|
/* Mark this sync point */
|
||||||
|
ebml->cluster_start = ebml->position;
|
||||||
|
|
||||||
|
/* Copy cluster tag to read buffer */
|
||||||
|
ebml->copy_len = tag_length;
|
||||||
|
ebml->parse_state = EBML_STATE_COPYING_TO_DATA;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EBML_STATE_COPYING_TO_HEADER:
|
||||||
|
case EBML_STATE_COPYING_TO_DATA:
|
||||||
|
to_copy = ebml->input_position - cursor;
|
||||||
|
if (to_copy > ebml->copy_len) {
|
||||||
|
to_copy = ebml->copy_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ebml->parse_state == EBML_STATE_COPYING_TO_HEADER) {
|
||||||
|
if ((ebml->header_position + to_copy) > EBML_HEADER_MAX_SIZE) {
|
||||||
|
ICECAST_LOG_ERROR("EBML Header too large, failing");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(ebml->header + ebml->header_position, ebml->input_buffer + cursor, to_copy);
|
||||||
|
ebml->header_position += to_copy;
|
||||||
|
|
||||||
|
} else if (ebml->parse_state == EBML_STATE_COPYING_TO_DATA) {
|
||||||
|
if ((ebml->position + to_copy) > EBML_SLICE_SIZE) {
|
||||||
|
to_copy = EBML_SLICE_SIZE - ebml->position;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(ebml->buffer + ebml->position, ebml->input_buffer + cursor, to_copy);
|
||||||
|
ebml->position += to_copy;
|
||||||
|
}
|
||||||
|
/* ICECAST_LOG_DEBUG("Copied %i of %hhu", to_copy, ebml->copy_len); */
|
||||||
|
|
||||||
|
cursor += to_copy;
|
||||||
|
ebml->copy_len -= to_copy;
|
||||||
|
|
||||||
|
if (ebml->copy_len == 0) {
|
||||||
|
/* resume parsing */
|
||||||
|
if (ebml->parse_state == EBML_STATE_COPYING_TO_HEADER) {
|
||||||
|
ebml->parse_state = EBML_STATE_PARSING_HEADER;
|
||||||
|
} else {
|
||||||
|
ebml->parse_state = EBML_STATE_PARSING_CLUSTERS;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* wait for more data */
|
||||||
|
processing = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
processing = 0;
|
||||||
|
|
||||||
if (ebml->header_size == 0) {
|
|
||||||
/* Still reading header */
|
|
||||||
if ((ebml->header_position + len) > EBML_HEADER_MAX_SIZE) {
|
|
||||||
ICECAST_LOG_ERROR("EBML Header too large, failing");
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
ICECAST_LOG_DEBUG("EBML: Adding to header, ofset is %d size is %d adding %d",
|
|
||||||
ebml->header_size, ebml->header_position, len);
|
|
||||||
*/
|
|
||||||
|
|
||||||
memcpy(ebml->header + ebml->header_position, ebml->input_buffer, len);
|
|
||||||
ebml->header_position += len;
|
|
||||||
} else {
|
|
||||||
/* Header's already been determined, read into data buffer */
|
|
||||||
memcpy(ebml->buffer + ebml->position, ebml->input_buffer, len);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (b = 0; b <= len - 4; b++)
|
/* Shift unprocessed data down to the start of the buffer */
|
||||||
{
|
memmove(ebml->input_buffer, ebml->input_buffer + cursor, ebml->input_position - cursor);
|
||||||
/* Scan for cluster start marker.
|
ebml->input_position -= cursor;
|
||||||
* False positives are possible, but unlikely, and only
|
|
||||||
* permanently corrupt a stream if they occur while scanning
|
|
||||||
* the initial header. Else, a client can reconnect in a few
|
|
||||||
* seconds and get a real sync point.
|
|
||||||
*/
|
|
||||||
if (!memcmp(ebml->input_buffer + b, ebml->cluster_id, 4))
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
ICECAST_LOG_DEBUG("EBML: found cluster");
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (ebml->header_size == 0)
|
|
||||||
{
|
|
||||||
/* We were looking for the header end; now we've found it */
|
|
||||||
ebml->header_size = ebml->header_position - len + b;
|
|
||||||
|
|
||||||
/* Shift data after the header into the data buffer */
|
|
||||||
memcpy(ebml->buffer, ebml->input_buffer + b, len - b);
|
|
||||||
ebml->position = len - b;
|
|
||||||
|
|
||||||
/* Mark the start of the data as the first sync point */
|
|
||||||
ebml->cluster_start = 0;
|
|
||||||
return len;
|
|
||||||
} else {
|
|
||||||
/* We've located a sync point in the data stream */
|
|
||||||
ebml->cluster_start = ebml->position + b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ebml->position += len;
|
|
||||||
|
|
||||||
return len;
|
return len;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user