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;
}