eebie/README.md
2024-07-25 10:52:24 +03:00

4.9 KiB

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:

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").