• Posted on February 27, 2015

C Get MP4 Duration

I was recently working on a program that needed to get the duration of MP4 files and I really didn’t want to call an external program like ffprobe and parse the output. The code as it’s currently standing is below, but maybe the information along side it may be more useful. Now, this code isn’t 100% accurate and I coded it only for speed, lower IO usage and the videos I had to parse all were encoded under the same conditions.

The MP4 Container

The MP4 file format is a container for video and audio, and can contain multiple of each. Each media part of the file starts with the header of moov which equates to 6d 6f 6f 76. Inside of the “movie (presentation) box” you can find information under the headers of mvhd, iods,rmdr, and about 20 others. The one we want to focus on is the mvhd which is the “movie (presentation) header box”. This box will contain values such as time unit per second and time length.

The duration is calculated by time length / time unit per second and will end up giving you the duration of the video in seconds. Below is part of the format of the “movie (presentation) header box”, and the full mp4 spec document I’ve been using is over at Xhelmboyx Mp4 File Spec, but a backup is here just in case.

8+ bytes movie (presentation) header box = long unsigned offset + long ASCII text string 'mvhd'
	-> 1 byte version = 8-bit unsigned value
		- if version is 1 then date and duration values are 8 bytes in length
	-> 3 bytes flags =  24-bit hex flags (current = 0)

	-> 4 bytes created mac UTC date
		= long unsigned value in seconds since beginning 1904 to 2040
	-> 4 bytes modified mac UTC date
		= long unsigned value in seconds since beginning 1904 to 2040
	OR
	-> 8 bytes created mac UTC date
		= 64-bit unsigned value in seconds since beginning 1904
	-> 8 bytes modified mac UTC date
		= 64-bit unsigned value in seconds since beginning 1904

	-> 4 bytes time scale = long unsigned time unit per second (default = 600)

	-> 4 bytes duration = long unsigned time length (in time units)
	OR
	-> 8 bytes duration = 64-bit unsigned time length (in time units)

The C Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>

char* videoinfo_mem(char* haystack, unsigned int sizehaystack, char* needle, unsigned int sizeneedle) {
	int i = 0;
	int end = sizehaystack - sizeneedle;
	for(i = 0; i < end; ++i) {
		if(memcmp(haystack+i,needle,sizeneedle) == 0) {
			return haystack+i;
		}
	}
	return NULL;
}

void* videoinfo_find(char* filename, void* find, int size, int resultSize) {
	FILE* fp = NULL;
	char* buffer = NULL;
	char* result = NULL;
	char* pos = NULL;
	unsigned int bufferSize = 2048;
	double filesize = 0;
	double split = 16;
	double splitsize = 0;
	double start = 0;
	double end = 0;
	double i = 0;
	unsigned int read = 0;

	if(resultSize > bufferSize) {
		resultSize = bufferSize;
	}

	buffer = malloc(bufferSize);
	if(buffer == NULL) {
		return NULL;
	}

	fp = fopen(filename,"rb");
	if(fp == NULL) {
		free(buffer);
		return NULL;
	}

	fseek(fp,0,SEEK_END);
	filesize = ftell(fp);
	rewind(fp);

	split = ceil(filesize / 100000);
	splitsize = ceil(filesize/split);

	for(i = split-1; i >= 0; --i) {
		start = (i*splitsize);
		end = start + splitsize;
		fseek(fp,start,SEEK_SET);

		while((read = fread(buffer,1,bufferSize,fp)) != 0) {
			if((pos = videoinfo_mem(buffer,bufferSize,find,size)) != NULL) {
				result = malloc(resultSize);
				memcpy(result,pos,resultSize);
				i = -1;
				break;
			}

			if(read != bufferSize || ftell(fp) >= end) {
				break; // go onto next split
			}
		}

	}

	fclose(fp);
	free(buffer);
	return result;
}

unsigned long videoinfo_flip(unsigned long val) {
	unsigned long new = 0;
	new += (val & 0x000000FF) << 24;
	new += (val & 0xFF000000) >> 24;
	new += (val & 0x0000FF00) << 8;
	new += (val & 0x00FF0000) >> 8;

   return new;
}

unsigned long videoinfo_duration(char* filename) {
	unsigned long duration = 0;
	char version = 0;
	void* data = NULL;
	char* pos = NULL;
	unsigned long timescale = 0;
	unsigned long timeunits = 0;
	int bytesize = 4;

	data = videoinfo_find(filename,"mvhd",4,64);
	if(data == NULL) {
		goto clean;
	}

	pos = (char*)data;
	pos += 4; // skip mvhd

	version = *pos++;
	pos+=3; //skip flags

	if(version == 1) {
		bytesize = 8;
	}else{
		bytesize = 4;
	}

	pos += bytesize; // skip created date
	pos += bytesize; // skip modified date

	memcpy(&timescale,pos,4);
	memcpy(&timeunits,pos+4,bytesize);

	timescale = videoinfo_flip(timescale);
	timeunits = videoinfo_flip(timeunits);

	if(timescale > 0 && timeunits > 0) {
		duration = timeunits/timescale;
	}

	clean:
	free(data);
	return duration;
}