174 lines
4.9 KiB
Markdown
174 lines
4.9 KiB
Markdown
|
# 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<stdio.h>
|
||
|
#include<stdlib.h>
|
||
|
#include<string.h>
|
||
|
|
||
|
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<stdio.h>
|
||
|
|
||
|
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").
|