Skip to content

Instantly share code, notes, and snippets.

@dwbuiten
Created December 4, 2019 21:49
Show Gist options
  • Save dwbuiten/6757b9bd7682b2da6087af761ff9732b to your computer and use it in GitHub Desktop.
Save dwbuiten/6757b9bd7682b2da6087af761ff9732b to your computer and use it in GitHub Desktop.
#ifdef _WIN32
#define fseeko _fseeki64
#define ftello _fseeki64
#define off_t __int64
#else
#define _FILE_OFFSET_BITS 64
#define _LARGEFILE_SOURCE
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <libavutil/common.h>
#include <libavutil/intreadwrite.h>
/*
* WARNING: This is nothing even close to a real ISOBMFF parser!
* It does not actually do hierarchical box parsing, and also
* assumes boxes are well-ordered, and that the first trak box is
* the video. It also assumes the file is >2^32 bytes.
*
* Am I ashamed? Maybe.
*/
/* Finds a given box, and descends into it. */
int find_box(FILE *f, uint32_t box_name)
{
while (!feof(f)) {
uint8_t buf[8];
uint32_t box;
uint64_t size;
off_t adj = 8;
int ret;
ret = fread(&buf[0], 1, 8, f);
if (ret != 8) {
printf("failed to read.\n");
return -1;
}
box = AV_RB32(&buf[4]);
size = AV_RB32(&buf[0]);
if (size == 0) {
printf("too lazy to support this\n");
return -1;
} else if (size == 1) {
/* Extended box size. */
ret = fread(&buf[0], 1, 8, f);
if (ret != 8) {
printf("failed to read.\n");
return -1;
}
size = AV_RB64(&buf[0]);
adj += 8;
}
if (size < 8) {
printf("invalid box\n");
return -1;
} else if (size == 8) {
continue;
}
if (box == box_name)
return 0;
ret = fseeko(f, ((off_t) size) - adj, SEEK_CUR);
if (ret < 0) {
printf("failed to seek %"PRIu64" bytes forward\n", size);
return -1;
}
}
printf("end of file.\n");
return -1;
}
/* Locates the first trak's stsz and co64 boxes. */
int find_stsz_co64(FILE *f, off_t *stsz_pos, off_t *co64_pos)
{
const uint32_t hierarchy[5] = {
MKBETAG('m','o','o','v'),
MKBETAG('t','r','a','k'),
MKBETAG('m','d','i','a'),
MKBETAG('m','i','n','f'),
MKBETAG('s','t','b','l')
};
int i;
int ret;
off_t stbl_pos;
for (i = 0; i < 5; i++) {
ret = find_box(f, hierarchy[i]);
if (ret < 0) {
printf("failed to find box: %"PRIx32"\n");
return -1;
}
}
stbl_pos = ftello(f);
ret = find_box(f, MKBETAG('s','t','s','z'));
if (ret < 0) {
printf("failed to find the stsz box");
return -1;
}
*stsz_pos = ftello(f);
ret = fseeko(f, stbl_pos, SEEK_SET);
if (ret < 0) {
printf("failed to seek back to stbl offset\n");
return -1;
}
ret = find_box(f, MKBETAG('c','o','6','4'));
if (ret < 0) {
printf("failed to find the co64 box");
return -1;
}
*co64_pos = ftello(f);
return 0;
}
int patch_co64(FILE *f, off_t pos, uint64_t *values, size_t entries) {
size_t i;
int ret = fseeko(f, pos + 8, SEEK_SET); /* skip version + flags + entries */
if (ret < 0) {
printf("failed to seek\n");
return -1;
}
for (i = 0; i < entries; i++) {
uint8_t buf[8];
AV_WB64(&buf[0], values[i]);
fwrite(&buf[0], 1, 8, f);
}
return 0;
}
uint64_t *read_co64(FILE *f, off_t pos, size_t *entries)
{
uint8_t buf[8];
uint64_t *vals;
size_t i;
int ret = fseeko(f, pos + 4, SEEK_SET); /* skip version + flags */
if (ret < 0) {
printf("failed to seek\n");
return NULL;
}
ret = fread(&buf[0], 1, 4, f);
if (ret != 4) {
printf("failed to read\n");
return NULL;
}
*entries = AV_RB32(&buf[0]);
if (*entries == 0) {
printf("invalid co64 box\n");
return NULL;
}
vals = malloc(*entries * sizeof(*vals));
if (vals == NULL) {
printf("alloc failure\n");
return NULL;
}
for (i = 0; i < *entries; i++) {
ret = fread(&buf[0], 1, 8, f);
if (ret != 8) {
free(vals);
printf("failed to read entry\n");
return NULL;
}
vals[i] = AV_RB64(&buf[0]);
}
return vals;
}
int patch_stsz(FILE *f, off_t pos, uint32_t *values, uint64_t *covalues, uint64_t *new_offsets, size_t entries) {
size_t i;
int ret = fseeko(f, pos + 12, SEEK_SET); /* skip version + flags + uniform + entries */
if (ret < 0) {
printf("failed to seek\n");
return -1;
}
for (i = 0; i < entries - 1; i++) {
/*
* This is in no way correct, since the offsets have moved around a bit, but "good enough".
* The proper way would be to take audio packet placement into account manually. :effort:
*/
uint32_t adj = ((uint32_t)(covalues[i+1] - covalues[i])) - values[i];
uint32_t new_val = ((uint32_t)(new_offsets[i+1] - new_offsets[i])) - adj;
uint8_t buf[4];
AV_WB32(&buf[0], new_val);
fwrite(&buf[0], 1, 4, f);
}
/* TODO: Last sample size */
return 0;
}
uint32_t *read_stsz(FILE *f, off_t pos, size_t *entries)
{
uint8_t buf[8];
uint32_t *vals;
size_t i;
int ret = fseeko(f, pos + 4, SEEK_SET); /* skip version + flags */
if (ret < 0) {
printf("failed to seek\n");
return NULL;
}
ret = fread(&buf[0], 1, 8, f);
if (ret != 8) {
printf("failed to read\n");
return NULL;
}
if (AV_RB32(&buf[0]) != 0) {
printf("samples should not be uniform\n");
return NULL;
}
*entries = AV_RB32(&buf[4]);
if (*entries == 0) {
printf("invalid stsz box\n");
return NULL;
}
vals = malloc(*entries * sizeof(*vals));
if (vals == NULL) {
printf("alloc failure\n");
return NULL;
}
for (i = 0; i < *entries; i++) {
ret = fread(&buf[0], 1, 4, f);
if (ret != 4) {
free(vals);
printf("failed to read entry\n");
return NULL;
}
vals[i] = AV_RB32(&buf[0]);
}
return vals;
}
static inline void bshift(uint8_t *buf, uint8_t val) {
buf[0] = buf[1];
buf[1] = buf[2];
buf[2] = buf[3];
buf[3] = buf[4];
buf[4] = buf[5];
buf[5] = buf[6];
buf[6] = buf[7];
buf[7] = val;
}
uint64_t *index_frames(FILE *f, size_t max_size) {
const uint8_t header[8] = {0, 1, 0, 9, 0, 2, 0, 3};
uint8_t buf[8];
int count = 0;
uint64_t *vals;
int ret;
ret = fseeko(f, 0, SEEK_SET);
if (ret < 0) {
printf("failed to seek to start\n");
return NULL;
}
ret = find_box(f, MKBETAG('m','d','a','t'));
if (ret < 0) {
printf("couldn't find mdat\n");
return NULL;
}
vals = malloc(max_size * sizeof(*vals));
if (vals == NULL) {
printf("alloc failure\n");
return NULL;
}
/* Initial fill. */
fread(&buf[0], 1, 8, f);
/* World's slowest and worst search. Byte. By. Byte. Not even by block. */
while (!feof(f) && count < max_size) {
uint8_t new_val;
if (!memcmp(&header[0], &buf[0], 8)) {
off_t pos = ftello(f) - 8;
vals[count] = pos;
printf("frame found at pos %ld (count=%d)\n", pos, count);
count++;
}
fread(&new_val, 1, 1, f);
bshift(&buf[0], new_val);
}
if (count < max_size) {
free(vals);
printf("woops indexing didn't work...\n");
return NULL;
}
return vals;
}
int main(int argc, char *argv[])
{
FILE *in, *out;
int ret;
off_t stsz_pos, co64_pos;
size_t co64_entries, stsz_entries;
uint32_t *stsz_vals;
uint64_t *co64_vals, *new_offsets;
if (argc < 3) {
printf("usage: hack <input file> <output file>\n");
return 0;
}
in = fopen(argv[1], "rb");
if (in == NULL) {
printf("failed to open input file.\n");
return 1;
}
ret = find_stsz_co64(in, &stsz_pos, &co64_pos);
if (ret < 0) {
printf("failed to find stsz and co64 positions\n");
fclose(in);
return 1;
}
printf("stsz: %ld / co64: %ld\n", stsz_pos, co64_pos);
co64_vals = read_co64(in, co64_pos, &co64_entries);
if (co64_vals == NULL) {
printf("failed to read co64 box.\n");
fclose(in);
return 1;
}
printf("read %zu co64 offsets\n", co64_entries);
stsz_vals = read_stsz(in, stsz_pos, &stsz_entries);
if (stsz_vals == NULL) {
printf("failed to read stsz box.\n");
free(co64_vals);
fclose(in);
return 1;
}
printf("read %zu stsz sizes\n", stsz_entries);
if (stsz_entries != co64_entries) {
printf("stsz and co64 have differing numbers of entries!\n");
free(stsz_vals);
free(co64_vals);
fclose(in);
return 1;
}
new_offsets = index_frames(in, co64_entries);
if (new_offsets == NULL) {
printf("failed to index file\n");
free(stsz_vals);
free(co64_vals);
fclose(in);
}
fclose(in);
out = fopen(argv[2], "r+b");
if (out == NULL) {
printf("could not open output file\n");
return 1;
}
ret = patch_co64(out, co64_pos, new_offsets, co64_entries);
if (ret < 0) {
free(stsz_vals);
free(new_offsets);
free(co64_vals);
fclose(out);
printf("failed to patch co64 box\n");
return 1;
}
ret = patch_stsz(out, stsz_pos, stsz_vals, co64_vals, new_offsets, stsz_entries);
if (ret < 0) {
free(stsz_vals);
free(new_offsets);
free(co64_vals);
fclose(out);
printf("failed to patch stsz box\n");
return 1;
}
free(stsz_vals);
free(new_offsets);
free(co64_vals);
fclose(out);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment