# Eebie Eebie is a very small C99 library for reading and writing EBML files, namely Matroska & WebM. The library is meant to be embedded directly into another project. ## Reading example #include"eebie/reader.h" #include #include #include static EBMLElementType on_enter_element(EBMLReader *reader, uint64_t id, uint64_t length) { printf("Found element %lX of size %lu at depth %u\n", id, length, reader->currentDepth); if(id == 0x2468AC || id == 0x1A45DFA3) { // Delve deeper. return EBML_TREE; } // Don't delve deeper, just get the data. // Important: the only distinction here is between EBML_TREE and not EBML_TREE. return EBML_BINARY; } static uint8_t *accumulationBuffer; static size_t accumulationBufferLength = 0; static void on_data_chunk(EBMLReader *reader, const uint8_t *data, size_t length) { // Add new data into our accumulation buffer. accumulationBuffer = realloc(accumulationBuffer, accumulationBufferLength + length); memcpy(accumulationBuffer + accumulationBufferLength, data, length); accumulationBufferLength += length; } static void on_exit_element(EBMLReader *reader) { // If needed, the current element ID can be found at `reader->inside.id`. // Use accumulated buffer here. free(accumulationBuffer); accumulationBuffer = NULL; accumulationBufferLength = 0; } int main() { EBMLReader reader; ebml_reader_init(&reader); // Set userdata here. reader.ud = NULL; // eventEnterElement is the only mandatory event handler. // It must specify whether an EBML element is a master or a primitive value. reader.eventEnterElement = on_enter_element; // eventDataChunk is called for every *CHUNK* of data of the element that has been read. // Once the element is exited, the accumulated buffer may be decoded fully. reader.eventDataChunk = on_data_chunk; // eventExitElement is called for every element finished parsing, including masters or primitive values. reader.eventExitElement = on_exit_element; FILE *f = fopen("test.mkv", "rb"); size_t i = 0; uint8_t buffer[4096]; while(1) { // Fill up buffer size_t readBytes = fread(buffer + i, 1, sizeof(buffer) - i, f); i += readBytes; // Use buffer size_t fedBytes = ebml_reader_feed(&reader, buffer, i); // Shift buffer by used amount memmove(buffer, buffer + fedBytes, sizeof(buffer) - fedBytes); i -= fedBytes; if(readBytes == 0 && fedBytes == 0) { // Give up. break; } } } ## Writing example TBA ## `schemagen` Given an EBML schema, `schemagen` generates a specialized header-only parser that uses `EBMLReader` to call handlers for each defined element, decoding them all to their correct types. It does not yet perform validation such as enums or ranges. Example using the [Matroska schema](https://github.com/ietf-wg-cellar/matroska-specification/blob/master/ebml_matroska.xml): cd schemagen make ./schemagen /path/to/the/ebml_matroska.xml Mkv > mkv.h The first argument is the path to the schema file. The second argument is the prefix to use in CamelCase. After this, `mkv.h` may be used similarly to EBMLReader: #define MKV_PARSER_IMPLEMENTATION #include"mkv.h" #include static void on_segment(MkvParser *parser) { printf("Entered new segment.\n"); } static void on_cluster(MkvParser *parser) { printf("Entered new cluster.\n"); } static void on_timestamp(MkvParser *parser, uint64_t timestamp) { printf("Cluster timestamp: %lu\n", timestamp); } int main() { MkvParser parser; mkv_parser_init(&parser); parser.ud = NULL; parser.OnSegment = on_segment; parser.OnCluster = on_cluster; parser.OnTimestamp = on_timestamp; // Many more event handlers can be used. FILE *f = fopen("test.mkv", "rb"); size_t i = 0; uint8_t buffer[4096]; while(1) { // Fill up buffer size_t readBytes = fread(buffer + i, 1, sizeof(buffer) - i, f); i += readBytes; // Use buffer size_t fedBytes = mkv_parser_feed(&parser, buffer, i); // Shift buffer by used amount memmove(buffer, buffer + fedBytes, sizeof(buffer) - fedBytes); i -= fedBytes; if(readBytes == 0 && fedBytes == 0) { // Give up. break; } } } ## License Eebie is licensed under the BSD 3-clause license (SPDX identifier "BSD-3-Clause").