Commit 23552719 authored by Philipp Schafft's avatar Philipp Schafft 🦁

Merge branch 'webmKeyFrames'

parents eecbc647 d196e754
......@@ -20,6 +20,8 @@
#include <config.h>
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
......@@ -36,35 +38,135 @@
#include "logging.h"
/* The size of the header buffer; should be large enough to contain
* everything before the first Cluster in a reasonable stream
*/
#define EBML_HEADER_MAX_SIZE 131072
/* The size of the input/staging buffers; this much of a cluster
* will be buffered before being returned. Should be large enough
* that the first video block will be encountered before it is full,
* to allow probing for the keyframe flag while we still have the
* option to mark the cluster as a sync point.
*/
#define EBML_SLICE_SIZE 4096
/* A value that no EBML var-int is allowed to take. */
#define EBML_UNKNOWN ((uint_least64_t) -1)
/* The magic numbers for each element we are interested in.
* Defined here:
* http://www.matroska.org/technical/specs/index.html
* http://www.webmproject.org/docs/container/
*
* Some of the higher-level elements have 4-byte identifiers;
* The lower-level elements have 1-byte identifiers.
*/
#define UNCOMMON_MAGIC_LEN 4
#define SEGMENT_MAGIC "\x18\x53\x80\x67"
#define CLUSTER_MAGIC "\x1F\x43\xB6\x75"
#define TRACKS_MAGIC "\x16\x54\xAE\x6B"
typedef struct ebml_client_data_st ebml_client_data_t;
#define COMMON_MAGIC_LEN 1
struct ebml_client_data_st {
#define TRACK_ENTRY_MAGIC "\xAE"
#define TRACK_NUMBER_MAGIC "\xD7"
#define TRACK_TYPE_MAGIC "\x83"
#define SIMPLE_BLOCK_MAGIC "\xA3"
refbuf_t *header;
int header_pos;
/* If support for Tags gets added, it may make sense
* to convert this into a pair of flags signaling
* "new headers" and "new tags"
*/
typedef enum ebml_read_mode {
/* The header buffer has not been extracted yet */
EBML_STATE_READING_HEADER = 0,
/* The header buffer has been read, begin normal operation */
EBML_STATE_READING_CLUSTERS
} ebml_read_mode;
};
typedef enum ebml_parsing_state {
/* Examine EBML elements, output to header buffer */
EBML_STATE_PARSING_HEADER = 0,
struct ebml_st {
/* Blindly copy a specified number of bytes to the header buffer */
EBML_STATE_COPYING_TO_HEADER,
char *cluster_id;
int cluster_start;
/* Finalize header buffer and wait for previous cluster to flush (as necessary) */
EBML_STATE_START_CLUSTER,
int position;
unsigned char *input_buffer;
/* Examine EBML elements, output to data buffer */
EBML_STATE_PARSING_CLUSTERS,
/* Blindly copy a specified number of bytes to the data buffer */
EBML_STATE_COPYING_TO_DATA
} ebml_parsing_state;
typedef enum ebml_chunk_type {
/* This chunk is the header buffer */
EBML_CHUNK_HEADER = 0,
/* This chunk starts a cluster that works as a sync point */
EBML_CHUNK_CLUSTER_START,
/* This chunk continues the previous cluster, or
* else starts a non-sync-point cluster
*/
EBML_CHUNK_CLUSTER_CONTINUE
} ebml_chunk_type;
typedef enum ebml_keyframe_status {
/* Have not found a video track block yet */
EBML_KEYFRAME_UNKNOWN = -1,
/* Found the first video track block, it was not a keyframe */
EBML_KEYFRAME_DOES_NOT_START_CLUSTER = 0,
/* Found the first video track block, it was a keyframe */
EBML_KEYFRAME_STARTS_CLUSTER = 1
} ebml_keyframe_status;
typedef struct ebml_st {
ebml_read_mode output_state;
ebml_parsing_state parse_state;
uint_least64_t copy_len;
ssize_t cluster_start;
ebml_keyframe_status cluster_starts_with_keyframe;
bool flush_cluster;
size_t position;
unsigned char *buffer;
int header_read;
int header_size;
int header_position;
int header_read_position;
size_t input_position;
unsigned char *input_buffer;
size_t header_size;
size_t header_position;
size_t header_read_position;
unsigned char *header;
};
uint_least64_t keyframe_track_number;
uint_least64_t parsing_track_number;
bool parsing_track_is_video;
} ebml_t;
typedef struct ebml_source_state_st {
ebml_t *ebml;
refbuf_t *header;
bool file_headers_written;
} ebml_source_state_t;
typedef struct ebml_client_data_st {
refbuf_t *header;
size_t header_pos;
} ebml_client_data_t;
static void ebml_free_plugin(format_plugin_t *plugin);
static refbuf_t *ebml_get_buffer(source_t *source);
......@@ -75,11 +177,22 @@ static void ebml_free_client_data(client_t *client);
static ebml_t *ebml_create();
static void ebml_destroy(ebml_t *ebml);
static int 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 size_t ebml_read_space(ebml_t *ebml);
static size_t ebml_read(ebml_t *ebml, char *buffer, size_t len, ebml_chunk_type *chunk_type);
static unsigned char *ebml_get_write_buffer(ebml_t *ebml, size_t *bytes);
static ssize_t ebml_wrote(ebml_t *ebml, size_t len);
static ssize_t ebml_parse_tag(unsigned char *buffer,
unsigned char *buffer_end,
uint_least64_t *payload_length);
static ssize_t ebml_parse_var_int(unsigned char *buffer,
unsigned char *buffer_end,
uint_least64_t *out_value);
static ssize_t ebml_parse_sized_int(unsigned char *buffer,
unsigned char *buffer_end,
size_t len,
bool is_signed,
uint_least64_t *out_value);
static inline void ebml_check_track(ebml_t *ebml);
int format_ebml_get_plugin(source_t *source)
{
......@@ -102,6 +215,7 @@ int format_ebml_get_plugin(source_t *source)
source->format = plugin;
ebml_source_state->ebml = ebml_create();
return 0;
}
......@@ -117,11 +231,13 @@ static void ebml_free_plugin(format_plugin_t *plugin)
free(plugin);
}
/* Write to a client from the header buffer.
*/
static int send_ebml_header(client_t *client)
{
ebml_client_data_t *ebml_client_data = client->format_data;
int len = EBML_SLICE_SIZE;
size_t len = EBML_SLICE_SIZE;
int ret;
if (ebml_client_data->header->len - ebml_client_data->header_pos < len)
......@@ -141,6 +257,8 @@ static int send_ebml_header(client_t *client)
}
/* Initial write-to-client function.
*/
static int ebml_write_buf_to_client (client_t *client)
{
......@@ -152,64 +270,79 @@ static int ebml_write_buf_to_client (client_t *client)
}
else
{
/* Now that the header's sent, short-circuit to the generic
* write-refbufs function. */
client->write_to_client = format_generic_write_to_client;
return client->write_to_client(client);
}
}
/* Return a refbuf to add to the queue.
*/
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;
unsigned char *write_buffer = NULL;
size_t read_bytes = 0;
size_t write_bytes = 0;
ebml_chunk_type chunk_type;
refbuf_t *refbuf;
int ret;
size_t ret;
while (1)
{
if ((bytes = ebml_read_space(ebml_source_state->ebml)) > 0)
{
refbuf = refbuf_new(bytes);
ebml_read(ebml_source_state->ebml, refbuf->data, bytes);
read_bytes = ebml_read_space(ebml_source_state->ebml);
if (read_bytes > 0) {
/* A chunk is available for reading */
refbuf = refbuf_new(read_bytes);
ebml_read(ebml_source_state->ebml, refbuf->data, read_bytes, &chunk_type);
if (ebml_source_state->header == NULL)
{
/* Capture header before adding clusters to the queue */
ebml_source_state->header = refbuf;
continue;
}
if (ebml_last_was_sync(ebml_source_state->ebml))
/* ICECAST_LOG_DEBUG("EBML: generated refbuf, size %i : %hhi %hhi %hhi",
* read_bytes, refbuf->data[0], refbuf->data[1], refbuf->data[2]);
*/
if (chunk_type == EBML_CHUNK_CLUSTER_START)
{
refbuf->sync_point = 1;
/* ICECAST_LOG_DEBUG("EBML: ^ was sync point"); */
}
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)
{
} else if(read_bytes == 0) {
/* Feed more bytes into the parser */
write_buffer = ebml_get_write_buffer(ebml_source_state->ebml, &write_bytes);
read_bytes = client_read_bytes (source->client, write_buffer, write_bytes);
if (read_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) {
format->read_bytes += read_bytes;
ret = ebml_wrote (ebml_source_state->ebml, read_bytes);
if (ret != read_bytes) {
ICECAST_LOG_ERROR("Problem processing stream");
source->running = 0;
return NULL;
}
} else {
ICECAST_LOG_ERROR("Problem processing stream");
source->running = 0;
return NULL;
}
}
}
/* Initialize client state.
*/
static int ebml_create_client_data(source_t *source, client_t *client)
{
ebml_client_data_t *ebml_client_data;
......@@ -229,7 +362,6 @@ static int ebml_create_client_data(source_t *source, client_t *client)
return 0;
}
static void ebml_free_client_data (client_t *client)
{
......@@ -240,7 +372,6 @@ static void ebml_free_client_data (client_t *client)
client->format_data = NULL;
}
static void ebml_write_buf_to_file_fail (source_t *source)
{
ICECAST_LOG_WARN("Write to dump file failed, disabling");
......@@ -248,20 +379,19 @@ static void ebml_write_buf_to_file_fail (source_t *source)
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 ( ! ebml_source_state->file_headers_written)
{
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;
ebml_source_state->file_headers_written = true;
}
if (fwrite (refbuf->data, 1, refbuf->len, source->dumpfile) != refbuf->len)
......@@ -271,7 +401,6 @@ static void ebml_write_buf_to_file (source_t *source, refbuf_t *refbuf)
}
/* internal ebml parsing */
static void ebml_destroy(ebml_t *ebml)
......@@ -289,178 +418,588 @@ static ebml_t *ebml_create()
ebml_t *ebml = calloc(1, sizeof(ebml_t));
ebml->output_state = EBML_STATE_READING_HEADER;
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->cluster_id = "\x1F\x43\xB6\x75";
ebml->cluster_start = -1;
ebml->cluster_start = -2;
ebml->keyframe_track_number = EBML_UNKNOWN;
ebml->parsing_track_number = EBML_UNKNOWN;
ebml->parsing_track_is_video = false;
return ebml;
}
static int ebml_read_space(ebml_t *ebml)
/* Return the size of a buffer needed to store the next
* chunk that ebml_read can yield.
*/
static size_t ebml_read_space(ebml_t *ebml)
{
int read_space;
size_t read_space;
if (ebml->header_read == 1)
{
if (ebml->cluster_start > 0)
read_space = ebml->cluster_start;
else
read_space = ebml->position - 4;
switch (ebml->output_state) {
case EBML_STATE_READING_HEADER:
return read_space;
}
else
{
if (ebml->header_size != 0)
return ebml->header_size;
else
return 0;
if (ebml->header_size != 0) {
/* The header can be read */
return ebml->header_size;
} else {
/* The header's not ready yet */
return 0;
}
break;
case EBML_STATE_READING_CLUSTERS:
if (ebml->cluster_start > 0) {
/* return up until just before a new cluster starts */
read_space = ebml->cluster_start;
} else {
if (ebml->position == EBML_SLICE_SIZE) {
/* The current cluster fills the buffer,
* we have no choice but to start flushing it.
*/
ebml->flush_cluster = true;
}
if (ebml->flush_cluster) {
/* return what we have */
read_space = ebml->position;
} else {
/* wait until we've read more, so the parser has
* time to gather metadata
*/
read_space = 0;
}
}
return read_space;
}
ICECAST_LOG_ERROR("EBML: Invalid parser read state");
return 0;
}
static int ebml_read(ebml_t *ebml, char *buffer, int len)
/* Return a chunk of the EBML/MKV/WebM stream.
* The header will be buffered until it can be returned as one chunk.
* A cluster element's opening tag will always start a new chunk.
*
* chunk_type will be set to indicate if the chunk is the header,
* the start of a cluster, or continuing the current cluster.
*/
static size_t ebml_read(ebml_t *ebml, char *buffer, size_t len, ebml_chunk_type *chunk_type)
{
int read_space;
int to_read;
size_t read_space;
size_t to_read;
*chunk_type = EBML_CHUNK_HEADER;
if (len < 1)
if (len < 1) {
return 0;
}
if (ebml->header_read == 1)
{
if (ebml->cluster_start > 0)
read_space = ebml->cluster_start;
else
read_space = ebml->position - 4;
switch (ebml->output_state) {
case EBML_STATE_READING_HEADER:
if (ebml->header_size != 0)
{
/* Can read a chunk of the header */
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;
*chunk_type = EBML_CHUNK_HEADER;
if (ebml->header_read_position == ebml->header_size) {
ebml->output_state = EBML_STATE_READING_CLUSTERS;
}
} else {
/* The header's not ready yet */
return 0;
}
if (read_space < 1)
return 0;
break;
if (read_space >= len )
to_read = len;
else
to_read = read_space;
case EBML_STATE_READING_CLUSTERS:
memcpy(buffer, ebml->buffer, to_read);
memmove(ebml->buffer, ebml->buffer + to_read, ebml->position - to_read);
ebml->position -= to_read;
*chunk_type = EBML_CHUNK_CLUSTER_CONTINUE;
read_space = ebml->position;
if (ebml->cluster_start > 0)
ebml->cluster_start -= to_read;
}
else
{
if (ebml->header_size != 0)
{
read_space = ebml->header_size - ebml->header_read_position;
if (ebml->cluster_start == 0) {
/* new cluster is starting now */
if (ebml->cluster_starts_with_keyframe != EBML_KEYFRAME_DOES_NOT_START_CLUSTER) {
/* If we positively identified the first video frame as a non-keyframe,
* don't use this cluster as a sync point. Since some files lack
* video tracks completely, or we may have failed to probe
* the first video frame, it's better to be pass through
* ambiguous cases to avoid blocking the stream forever.
*/
*chunk_type = EBML_CHUNK_CLUSTER_START;
}
/* mark end of cluster */
ebml->cluster_start = -1;
} else if (ebml->cluster_start > 0) {
/* return up until just before a new cluster starts */
read_space = ebml->cluster_start;
}
if (read_space < 1) {
return 0;
}
if (read_space >= len)
if (read_space >= len ) {
to_read = len;
else
} else {
to_read = read_space;
}
memcpy(buffer, ebml->header, to_read);
ebml->header_read_position += to_read;
memcpy(buffer, ebml->buffer, to_read);
if (ebml->header_read_position == ebml->header_size)
ebml->header_read = 1;
}
else
{
return 0;
}
/* Shift unread data down to the start of the buffer */
memmove(ebml->buffer, ebml->buffer + to_read, ebml->position - to_read);
ebml->position -= to_read;
if (ebml->cluster_start > 0) {
ebml->cluster_start -= to_read;
}
break;
}
return to_read;
}
static int ebml_last_was_sync(ebml_t *ebml)
/* Get pointer & length of the buffer able to accept input.
*
* Returns the start of the writable space;
* Sets bytes to the amount of space available.
*/
static unsigned char *ebml_get_write_buffer(ebml_t *ebml, size_t *bytes)
{
*bytes = EBML_SLICE_SIZE - ebml->input_position;
return ebml->input_buffer + ebml->input_position;
}
if (ebml->cluster_start == 0)
{
ebml->cluster_start -= 1;
return 0;
}
/* Process data that has been written to the EBML parser's input buffer.
*/
static ssize_t ebml_wrote(ebml_t *ebml, size_t len)
{
bool processing = true;
size_t cursor = 0;
size_t to_copy;
unsigned char *end_of_buffer;
ssize_t tag_length;
ssize_t value_length;
ssize_t track_number_length;
uint_least64_t payload_length;
uint_least64_t data_value;
uint_least64_t track_number;
unsigned char flags;
ebml_parsing_state copy_state;
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;
}
/* Recognize tags of interest */
if (tag_length > UNCOMMON_MAGIC_LEN) {
if (!memcmp(ebml->input_buffer + cursor, CLUSTER_MAGIC, UNCOMMON_MAGIC_LEN)) {
/* Found a Cluster */
ebml->parse_state = EBML_STATE_START_CLUSTER;
break;
} else if (!memcmp(ebml->input_buffer + cursor, SEGMENT_MAGIC, UNCOMMON_MAGIC_LEN)) {
/* Parse all Segment children */
payload_length = 0;
} else if (!memcmp(ebml->input_buffer + cursor, TRACKS_MAGIC, UNCOMMON_MAGIC_LEN)) {
/* Parse all Tracks children */
payload_length = 0;
}
}
if (tag_length > COMMON_MAGIC_LEN) {
if (!memcmp(ebml->input_buffer + cursor, SIMPLE_BLOCK_MAGIC, COMMON_MAGIC_LEN)) {
/* Probe SimpleBlock header for the keyframe status */
if (ebml->cluster_starts_with_keyframe == EBML_KEYFRAME_UNKNOWN) {
track_number_length = ebml_parse_var_int(ebml->input_buffer + cursor + tag_length,
end_of_buffer, &track_number);
if (track_number_length == 0) {
/* Wait for more data */
processing = false;
} else if (track_number_length < 0) {
return -1;
} else if (track_number == ebml->keyframe_track_number) {
/* this block belongs to the video track */
/* skip the 16-bit timecode for now, read the flags byte */
if (cursor + tag_length + track_number_length + 2 >= ebml->input_position) {
/* Wait for more data */
processing = false;
} else {
flags = ebml->input_buffer[cursor + tag_length + track_number_length + 2];
if (flags & 0x80) {
/* "keyframe" flag is set */
ebml->cluster_starts_with_keyframe = EBML_KEYFRAME_STARTS_CLUSTER;
/* ICECAST_LOG_DEBUG("Found keyframe in track %hhu", track_number); */
} else {
ebml->cluster_starts_with_keyframe = EBML_KEYFRAME_DOES_NOT_START_CLUSTER;
/* ICECAST_LOG_DEBUG("Found non-keyframe in track %hhu", track_number); */
}