Hi all,
This is my second attempt to have this patch revised. This patch adds
support for movie fragments in mov files (as specified in the
ISO/IEC-14496). The changes only affect the libmpdemux/demux_mov.c file.
This feature is not supported by the libavformat demuxer.
Regarding the size of the patch, I would like to list all the new
features that this patch adds:
- Support for multiple 'mdat' atoms.
- 'mvex','mehd' and 'trex' atoms parsing. Those atoms, which are
located inside the 'moov' atom, warn about presence of movie fragments
that extend one or several tracks.
- Support for 'moof' atoms (header for each movie fragment). It
includes 'mfhd','traf','tfhd' and 'trun' atoms which are contained
inside the 'moof'.
- Support for 'mfra' atoms that contain movie fragment random access
information (using 'tfra' and mfro' atoms).
This patch reads and adds the movie fragment information to each present
track. New functionality is implemented in new functions that parse the
content of the mentioned atoms and rebuild sample indexes. The lschunks
function only suffers minor changes.
I put some effort to have this new functionality working and it would be
nice to see it included in the MPlayer source code. I hope someone will
think that it is useful and will spend some time reviewing it.
Greetings,
David
Index: libmpdemux/demux_mov.c
===================================================================
--- libmpdemux/demux_mov.c (revision 24882)
+++ libmpdemux/demux_mov.c (working copy)
@@ -109,6 +109,7 @@
int id;
int type;
off_t pos;
+ int track_ID;
//
unsigned int media_handler;
unsigned int data_handler;
@@ -147,6 +148,158 @@
void* desc; // image/sound/etc description (pointer to ImageDescription etc)
} mov_track_t;
+#define MFRA_MAX_KEYFRAMES 500
+
+typedef struct {
+ int track_ID;
+ //
+ uint64_t base_data_offset;
+ uint32_t sample_description_index;
+ uint32_t default_sample_duration;
+ uint32_t default_sample_size;
+ uint32_t default_sample_flags;
+ //
+ int samples_size;
+ mov_sample_t* samples;
+ int chunks_size;
+ mov_chunk_t* chunks;
+ int durmap_size;
+ mov_durmap_t* durmap;
+ int keyframes_size;
+ unsigned int keyframes[MFRA_MAX_KEYFRAMES];
+} mov_traf_t;
+
+void add_traf_to_track(mov_track_t* trak,int timescale,mov_traf_t* traf)
+{
+ int i, s;
+ unsigned int original_samples_size, constant_sample_size;
+
+ // Add samples
+ original_samples_size = trak->samples_size;
+ constant_sample_size = 0;
+ if(!trak->samples_size){ // constant samplesize in trak
+ if( (trak->samplesize == traf->default_sample_size) || traf->default_sample_size==0 ){
+ constant_sample_size = 1;
+ } else {
+ s=0;
+ for(i=0;i<trak->chunks_size;i++){
+ trak->chunks[i].sample=s;
+ s+=trak->chunks[i].size;
+ }
+ trak->samples_size=s+traf->samples_size;
+ trak->samples=calloc(s+traf->samples_size, sizeof(mov_sample_t));
+ for(i=0;i<s;i++) trak->samples[i].size=trak->samplesize;
+ for(i=0;i<traf->samples_size;i++) trak->samples[s + i].size = traf->samples[i].size;
+ trak->samplesize=0;
+ }
+ } else {
+ trak->samples = realloc_struct(trak->samples, trak->samples_size+traf->samples_size, sizeof(mov_sample_t));
+ for(i=0;i<traf->samples_size;i++)
+ trak->samples[trak->samples_size + i].size = traf->samples[i].size;
+
+ trak->samples_size += traf->samples_size;
+ //mp_msg(MSGT_DEMUX, MSGL_V, "Added %d samples, total %d\n",traf->samples_size,trak->samples_size);
+ }
+
+ // Add chunks
+ trak->chunks = realloc_struct(trak->chunks, trak->chunks_size+traf->chunks_size, sizeof(mov_chunk_t));
+ for(i=0;i<traf->chunks_size;i++){
+ trak->chunks[trak->chunks_size + i].size = traf->chunks[i].size;
+ trak->chunks[trak->chunks_size + i].pos = traf->chunks[i].pos;
+ trak->chunks[trak->chunks_size + i].desc = traf->chunks[i].desc;
+ }
+ trak->chunks_size += traf->chunks_size;
+ //mp_msg(MSGT_DEMUX, MSGL_V, "Added %d chunks\n",traf->chunks_size);
+
+ // Add durmaps
+ if( !constant_sample_size ) {
+ trak->durmap = realloc_struct(trak->durmap, trak->durmap_size+traf->durmap_size, sizeof(mov_durmap_t));
+ for(i=0;i<traf->durmap_size;i++){
+ trak->durmap[trak->durmap_size + i].dur = traf->durmap[i].dur;
+ trak->durmap[trak->durmap_size + i].num = traf->durmap[i].num;
+ }
+ trak->durmap_size += traf->durmap_size;
+ } else {
+ for(i=0;i<traf->durmap_size;i++){
+ trak->durmap[0].dur += traf->durmap[i].dur;
+ trak->durmap[0].num += traf->durmap[i].num;
+ }
+ }
+
+ if( trak->keyframes_size>0 && trak->keyframes_size>0 ) {
+ trak->keyframes = realloc(trak->keyframes, (trak->keyframes_size+traf->keyframes_size)*sizeof(unsigned int));
+ for( i=0; i<traf->keyframes_size; i++ ) {
+ trak->keyframes[trak->keyframes_size+i]=original_samples_size+trak->keyframes[i];
+ }
+ trak->keyframes_size += traf->keyframes_size;
+ } else if( trak->keyframes_size>0 ) { // no mfra supplied but needed, need to create it??
+ trak->keyframes = realloc(trak->keyframes, (trak->keyframes_size+traf->samples_size)*sizeof(unsigned int));
+ for( i=0; i<traf->samples_size; i++ ) {
+ trak->keyframes[trak->keyframes_size+i]=original_samples_size+i;
+ }
+ trak->keyframes_size += traf->samples_size;
+ }
+}
+
+void mov_rebuild_index(mov_track_t* trak,int timescale)
+{
+ int i,j,s;
+ unsigned int pts=0;
+
+ // calc pts of chunks:
+ s=0;
+ for(j=0;j<trak->chunks_size;j++){
+ trak->chunks[j].sample=s;
+ s+=trak->chunks[j].size;
+ }
+ i = 0;
+ for (j = 0; j < trak->durmap_size; j++) i += trak->durmap[j].num;
+ if (i != s) {
+ mp_msg(MSGT_DEMUX, MSGL_WARN,"MOV: durmap and chunkmap sample count differ (%i vs %i)\n", i, s);
+ if (i > s) s = i;
+ }
+
+ if (trak->samples_size < s) {
+ if( trak->samples_size ) {
+ mp_msg(MSGT_DEMUX, MSGL_WARN,"MOV: durmap or chunkmap bigger than sample count (%i vs %i)\n",s, trak->samples_size);
+ }
+ trak->samples_size = s;
+ trak->samples = realloc_struct(trak->samples, s, sizeof(mov_sample_t));
+ }
+
+ // calc pts:
+ s=0;
+ for(j=0;j<trak->durmap_size;j++){
+ for(i=0;i<trak->durmap[j].num;i++){
+ trak->samples[s].pts=pts;
+ ++s;
+ pts+=trak->durmap[j].dur;
+ if(!trak->durmap[j].dur)
+ pts+=trak->durmap[0].dur;
+ }
+ }
+
+ if( trak->length != pts ) {
+ // normal behavior when TRAFS are present
+ //mp_msg(MSGT_DEMUX, MSGL_WARN,
+ // "MOV: track length is: %d, and durmap length is: %d\n", trak->length, pts );
+ trak->length = pts;
+ }
+
+ // calc sample offsets
+ s=0;
+ for(j=0;j<trak->chunks_size;j++){
+ off_t pos=trak->chunks[j].pos;
+ mp_msg(MSGT_DEMUX, MSGL_DBG3, "Chunk %5d: sample=%8d off=0x%08X size=%d\n",j,trak->chunks[j].sample,trak->chunks[j].pos, trak->chunks[j].size);
+ for(i=0;i<trak->chunks[j].size;i++){
+ trak->samples[s].pos=pos;
+ mp_msg(MSGT_DEMUX, MSGL_DBG3, "Sample %5d: pts=%8d off=0x%08X size=%d\n",s,trak->samples[s].pts,(int)trak->samples[s].pos,trak->samples[s].size);
+ pos+=trak->samples[s].size;
+ ++s;
+ }
+ }
+}
+
void mov_build_index(mov_track_t* trak,int timescale){
int i,j,s;
int last=trak->chunks_size;
@@ -224,9 +377,11 @@
}
if (trak->samples_size < s) {
+ if( trak->samples_size ) {
mp_msg(MSGT_DEMUX, MSGL_WARN,
"MOV: durmap or chunkmap bigger than sample count (%i vs %i)\n",
s, trak->samples_size);
+ }
trak->samples_size = s;
trak->samples = realloc_struct(trak->samples, s, sizeof(mov_sample_t));
}
@@ -269,10 +424,21 @@
// skip!
el->frames=0; continue;
}
- // find start sample
+ // find start sample. If track has keyframes, choose the nearest
+ // (earlier) keyframe. Otherwise, just choose the nearest sample.
+ if(trak->keyframes_size) {
+ unsigned int kfi;
+ for(kfi=0;kfi<trak->keyframes_size-1;kfi++){
+ if(trak->samples[trak->keyframes[kfi+1]].pts > pts){
+ sample = trak->keyframes[kfi];
+ break;
+ }
+ }
+ } else {
for(;sample<trak->samples_size;sample++){
if(pts<=trak->samples[sample].pts) break;
}
+ }
el->start_sample=sample;
el->pts_offset=((long long)e_pts*(long long)trak->timescale)/(long long)timescale-trak->samples[sample].pts;
pts+=((long long)el->dur*(long long)trak->timescale)/(long long)timescale;
@@ -293,12 +459,24 @@
#define MOV_MAX_TRACKS 256
#define MOV_MAX_SUBLEN 1024
+#define MOV_MAX_FRAGMENTS 256
+#define MOV_MAX_TRAFS 256
typedef struct {
off_t moov_start;
off_t moov_end;
- off_t mdat_start;
- off_t mdat_end;
+ off_t mdat_start[MOV_MAX_FRAGMENTS];
+ off_t mdat_end[MOV_MAX_FRAGMENTS];
+ off_t moof_start[MOV_MAX_FRAGMENTS];
+ off_t moof_end[MOV_MAX_FRAGMENTS];
+ off_t mfra_start;
+ off_t mfra_end;
+ int mvex_counter;
+ mov_traf_t mvexs[MOV_MAX_TRACKS];
+ int mdat_counter;
+ int moof_counter;
+ int traf_counter;
+ mov_traf_t* trafs[MOV_MAX_TRAFS];
int track_db;
mov_track_t* tracks[MOV_MAX_TRACKS];
int timescale; // movie timescale
@@ -310,6 +488,133 @@
#define MOV_FOURCC(a,b,c,d) ((a<<24)|(b<<16)|(c<<8)|(d))
+// Helper fnction, should be moved to stream.h
+inline static unsigned int stream_read_x(stream_t *s,int x)
+{
+ switch(x) {
+ case 0: return stream_read_char(s);
+ case 1: return stream_read_word(s);
+ case 2: return stream_read_int24(s);
+ case 3: return stream_read_dword(s);
+ }
+ return 0; // error
+}
+
+static void lschunks_inmfra(demuxer_t* demuxer,int level,off_t endpos,mov_track_t* trak)
+{
+ mov_priv_t* priv=demuxer->priv;
+ off_t len = 0;
+ off_t pos = stream_tell(demuxer->stream);
+
+ while(1){
+ unsigned int id;
+ pos += len;
+
+ if(!stream_seek(demuxer->stream,pos)) {
+ mp_msg(MSGT_DEMUX,MSGL_ERR,"MOV: Cannot seek to the beginning of the 'mfra' header (0x%"PRIx64")\n",(int64_t)pos);
+ return;
+ }
+
+ if(pos>=endpos) return; // END
+ len=stream_read_dword(demuxer->stream);
+ if(len<8) return; // error
+
+
+ id=stream_read_dword(demuxer->stream);
+ switch(id) {
+ case MOV_FOURCC('t','f','r','a'): {
+ int version = stream_read_dword(demuxer->stream);
+ int flags = version & 0x0ffffff;
+ version = version >> 24;
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:\tTrack Fragment Random Access Box 'tfra' (%d bytes): v=%d flags=%d\n",(int)len, (int)version,(int)flags);
+ uint32_t track_ID = stream_read_dword(demuxer->stream);
+ uint32_t tmp = stream_read_dword(demuxer->stream);
+ uint32_t reserved = tmp >> 6;
+ uint32_t length_size_of_traf_num = (tmp & 0x030) >> 4;
+ uint32_t length_size_of_trun_num = (tmp & 0x0c) >> 2;
+ uint32_t length_size_of_sample_num = (tmp & 0x03);
+ uint32_t number_of_entry = stream_read_dword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\ttrack_ID = %d\n",track_ID);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\treserved = %d\n",reserved);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tlength_size_of_traf_num = %d\n",length_size_of_traf_num);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tlength_size_of_trun_num = %d\n",length_size_of_trun_num);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tlength_size_of_sample_num = %d\n",length_size_of_sample_num);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tnumber_of_entry = %d\n",number_of_entry);
+ int i, j, k, count_trafs;
+ mov_traf_t *traf = NULL;
+ uint64_t count_sample;
+ for(i=0;i<number_of_entry;i++){
+ uint64_t time;
+ uint64_t moof_offset;
+ uint32_t traf_number;
+ uint32_t trun_number;
+ uint32_t sample_number, last_sample_number;
+
+ if(version==1){
+ time = stream_read_qword(demuxer->stream);
+ moof_offset = stream_read_qword(demuxer->stream);
+ }else{
+ time = stream_read_dword(demuxer->stream);;
+ moof_offset = stream_read_dword(demuxer->stream);;
+ }
+ traf_number = stream_read_x(demuxer->stream,length_size_of_traf_num);
+ trun_number = stream_read_x(demuxer->stream,length_size_of_trun_num);
+ sample_number = stream_read_x(demuxer->stream,length_size_of_sample_num);
+
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\t[entry %d]\n",i);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\ttime = %d\n",time);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tmoof_offset = %d\n",moof_offset);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\ttraf_number = %d\n",traf_number);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\ttrun_number = %d\n",trun_number);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tsample_number = %d\n",sample_number);
+
+ // look for the specified traf
+ if( traf!= NULL ) {
+ if( traf->track_ID == track_ID ) {
+ if( count_trafs == traf_number ) { // already selected traf, no need to search it
+ if( trun_number<traf->chunks_size && trun_number>0 ) {
+ for( k=0, count_sample=0; k<trun_number; k++ ) count_sample += traf->chunks[k].size;
+ count_sample += sample_number-1;
+ traf->keyframes[traf->keyframes_size++] = count_sample;
+ //printf( "keyframe created WHITHOUT!!!!: %d\n", count_sample );
+ continue;
+ }
+ }
+ }
+ }
+ // full search
+ for( j=0, count_trafs=0; j<priv->traf_counter; j++ ) {
+ if( priv->trafs[j]->track_ID == track_ID ) {
+ traf = priv->trafs[j];
+ if( ++count_trafs == traf_number ) { // found the right traf
+ if( trun_number<traf->chunks_size && trun_number>0 ) {
+ //then sample number is
+ for( k=0, count_sample=0; k<trun_number; k++ ) count_sample += traf->chunks[k].size;
+ count_sample += sample_number-1;
+ traf->keyframes[traf->keyframes_size++] = count_sample;
+ //printf( "keyframe created: %d\n", count_sample );
+ break;
+ }
+ }
+ }
+ }
+ } break;
+ }
+ case MOV_FOURCC('m','f','r','o'):{
+ int version = stream_read_dword(demuxer->stream);
+ int flags = version & 0x0ffffff;
+ version = version >> 24;
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:\tMovie Fragment Random Access Offset Box 'mfro' (%d bytes): v=%d flags=%d\n",(int)len, (int)version,(int)flags);
+ uint32_t size = stream_read_dword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tsize = %d\n",size);
+ } break;
+ default:
+ id = be2me_32(id);
+ mp_msg(MSGT_DEMUX,MSGL_DBG3,"MOV: unknown chunk: %.4s %d\n",(char *)&id,(int)len);
+ }
+ }
+}
+
static int mov_check_file(demuxer_t* demuxer){
int flags=0;
int no=0;
@@ -474,17 +779,20 @@
mp_msg(MSGT_DEMUX,MSGL_V,"MOV: 'WIDE' chunk found!\n");
if(flags&2) break;
case MOV_FOURCC('m','d','a','t'):
- mp_msg(MSGT_DEMUX,MSGL_V,"MOV: Movie DATA found!\n");
- priv->mdat_start=stream_tell(demuxer->stream);
- priv->mdat_end=priv->mdat_start+len-skipped;
- mp_msg(MSGT_DEMUX,MSGL_DBG2,"MOV: Movie data: start: %"PRIx64" end: %"PRIx64"\n",
- (int64_t)priv->mdat_start, (int64_t)priv->mdat_end);
+ mp_msg(MSGT_DEMUX,MSGL_V,"MOV: Movie DATA #%"PRIx64" found!\n",(int64_t)priv->mdat_counter);
+ priv->mdat_start[priv->mdat_counter]=stream_tell(demuxer->stream);
+ priv->mdat_end[priv->mdat_counter]=priv->mdat_start[priv->mdat_counter]+len-skipped;
+ priv->mdat_counter++;
+ mp_msg(MSGT_DEMUX,MSGL_DBG2,"MOV: Movie data #%"PRIx64": start: %"PRIx64" end: %"PRIx64"\n",
+ (int64_t)priv->mdat_counter,(int64_t)priv->mdat_start[priv->mdat_counter-1],
+ (int64_t)priv->mdat_end[priv->mdat_counter-1]);
flags|=2;
- if(flags==3){
+ //if(flags==3){
// if we're over the headers, then we can stop parsing here!
- demuxer->priv=priv;
- return DEMUXER_TYPE_MOV;
- }
+ // demuxer->priv=priv;
+ // return DEMUXER_TYPE_MOV;
+ //}
+ // Don't stop here, keep parsing the file, looking for moofs
break;
case MOV_FOURCC('f','r','e','e'):
case MOV_FOURCC('s','k','i','p'):
@@ -496,6 +804,21 @@
case MOV_FOURCC('P','I','C','T'):
/* dunno what, but we shoudl ignore it */
break;
+ case MOV_FOURCC('m','f','r','a'): {
+ off_t pos = stream_tell(demuxer->stream);
+ priv->mfra_start = pos;
+ priv->mfra_end = pos+len-skipped;
+ mp_msg(MSGT_DEMUX,MSGL_V,"MOV: Movie Fragment Random Access Box 'mfra' (%d bytes)\n",(int)len);
+ } break;
+ /* Now we handle moofs */
+ case MOV_FOURCC('m','o','o','f'):{
+ priv->moof_start[priv->moof_counter]=stream_tell(demuxer->stream);
+ priv->moof_end[priv->moof_counter]=priv->moof_start[priv->moof_counter]+len-skipped;
+ priv->moof_counter++;
+ mp_msg(MSGT_DEMUX,MSGL_V,"MOV: moof fragment #%d: start: %"PRIx64" end: %"PRIx64"\n",priv->moof_counter,
+ (int64_t)priv->moof_start[priv->moof_counter-1], (int64_t)priv->moof_end[priv->moof_counter-1]);
+ } break;
+
default:
if(no==0){ free(priv); return 0;} // first chunk is bad!
id = be2me_32(id);
@@ -544,9 +867,301 @@
free(track);
}
}
+ for (i = 0; i < MOV_MAX_TRAFS; i++) {
+ mov_traf_t *traf = priv->trafs[i];
+ if (traf) {
+ // free all traf fileds here
+ free(traf->samples);
+ free(traf->durmap);
+ free(traf->chunks);
+ }
+ }
free(priv);
}
+static void lschunks_intraf(demuxer_t* demuxer,int level,off_t endpos,mov_traf_t* traf)
+{
+ mov_priv_t* priv=demuxer->priv;
+ off_t len = 0;
+ off_t pos = stream_tell(demuxer->stream);
+ off_t start = stream_tell(demuxer->stream);
+ int trun_count = 0;
+ unsigned int found_variable_samplesize = 0;
+ unsigned int id;
+
+ // First read: parse tfhd and count number of truns
+ while((pos+len)<endpos){
+ pos += len;
+
+ if(!stream_seek(demuxer->stream,pos)) {
+ mp_msg(MSGT_DEMUX,MSGL_ERR,"MOV: Cannot seek to the beginning of the 'traf' atom (0x%"PRIx64")\n",(int64_t)pos);
+ return;
+ }
+
+ len=stream_read_dword(demuxer->stream);
+ if(len<8) return; // error
+ id=stream_read_dword(demuxer->stream);
+ switch(id) {
+ case MOV_FOURCC('t','f','h','d'): {
+ int k, version = stream_read_dword(demuxer->stream);
+ int tf_flags = version & 0x0ffffff;
+ version = version >> 24;
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:\tTrack Fragment Header Box 'tfhd'(%d bytes): v=%d tf_flags=%d\n",
+ (int)len, (int) version,(int)tf_flags);
+ traf->track_ID = stream_read_dword(demuxer->stream); // all the following are optional fields
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tTrack_ID=%d\n",traf->track_ID);
+
+ // first apply whatever mvex found!
+ for( k=0; k< priv->mvex_counter; k++ ) {
+ if( priv->mvexs[k].track_ID == traf->track_ID ) {
+ traf->sample_description_index = priv->mvexs[k].sample_description_index;
+ traf->default_sample_duration = priv->mvexs[k].default_sample_duration;
+ traf->default_sample_size = priv->mvexs[k].default_sample_size;
+ traf->default_sample_flags = priv->mvexs[k].default_sample_flags;
+ }
+ }
+
+ // now continue parse
+ if(tf_flags & 0x000001) {
+ traf->base_data_offset = stream_read_qword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tbase_data_offset=%d\n",traf->base_data_offset);
+ }
+ if(tf_flags & 0x000002) {
+ traf->sample_description_index = stream_read_dword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tsample_description_index=%d\n",traf->sample_description_index);
+ }
+ if(tf_flags & 0x000008) {
+ traf->default_sample_duration = stream_read_dword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tdefault_sample_duration=%d\n",traf->default_sample_duration);
+ }
+ if(tf_flags & 0x000010) {
+ traf->default_sample_size = stream_read_dword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tdefault_sample_size=%d\n",traf->default_sample_size);
+ }
+ if(tf_flags & 0x000020) {
+ traf->default_sample_flags = stream_read_dword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tdefault_sample_flags=%d\n",traf->default_sample_flags);
+ }
+ }break;
+ case MOV_FOURCC('t','r','u','n'): {
+ int i;
+ int version = stream_read_dword(demuxer->stream);
+ int tf_flags = version & 0x0ffffff;
+ version = version >> 24;
+ uint32_t sample_count = stream_read_dword(demuxer->stream);
+
+ traf->samples_size += sample_count;
+ if(tf_flags & 0x000200) found_variable_samplesize = 1;
+ traf->chunks_size += 1;
+ if(tf_flags & 0x000100)
+ traf->durmap_size += sample_count;
+ else
+ traf->durmap_size += 1;
+ } break;
+ default:
+ id = be2me_32(id);
+ mp_msg(MSGT_DEMUX,MSGL_V,"MOV:\tunknown chunk: %.4s %d\n",(char *)&id,(int)len);
+ }
+ }
+
+ // Alloc arrays
+ if( found_variable_samplesize ) {
+ traf->samples = (mov_sample_t*) malloc(traf->samples_size*sizeof(mov_sample_t));
+ memset( traf->samples, 0, traf->samples_size*sizeof(mov_sample_t));
+ } else {
+ traf->samples_size=0;
+ }
+ traf->durmap = (mov_durmap_t*)malloc(traf->durmap_size*sizeof(mov_durmap_t));
+ traf->chunks = (mov_chunk_t*)malloc(traf->chunks_size*sizeof(mov_chunk_t));
+ memset( traf->durmap, 0, traf->durmap_size*sizeof(mov_durmap_t) );
+ memset( traf->chunks, 0, traf->chunks_size*sizeof(mov_chunk_t));
+
+ len = 0;
+ pos = start;
+ int sample_count = 0;
+ int durmap_count = 0;
+ int chunk_count = 0;
+
+ // Second read: parse truns
+ while(1){
+ pos += len;
+
+ if(!stream_seek(demuxer->stream,pos)) {
+ mp_msg(MSGT_DEMUX,MSGL_ERR,"MOV: Cannot seek to the beginning of the 'traf' atom (0x%"PRIx64")\n", (int64_t)pos);
+ return;
+ }
+ if(pos>=endpos) {
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\tTraf #%d: samples: %d, chunks: %d\n",
+ priv->traf_counter-1,traf->samples_size,traf->chunks_size);
+ return; // END
+ }
+
+ len=stream_read_dword(demuxer->stream);
+ if(len<8) return; // error
+
+ id=stream_read_dword(demuxer->stream);
+ switch(id) {
+ case MOV_FOURCC('t','f','h','d'): break;
+ case MOV_FOURCC('t','r','u','n'): {
+ int i;
+ int version = stream_read_dword(demuxer->stream);
+ int tf_flags = version & 0x0ffffff;
+ int32_t data_offset = 0;
+ uint32_t first_sample_flags;
+ uint32_t K = stream_read_dword(demuxer->stream);
+
+ version = version >> 24;
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:\tTrack Fragment Run Box 'trun' (%d bytes): v=%d tf_flags=%d\n",
+ (int)len, (int)version,(int)tf_flags);
+
+ if(tf_flags & 0x000001) data_offset = stream_read_dword(demuxer->stream);
+ if(tf_flags & 0x000004) first_sample_flags = stream_read_dword(demuxer->stream);
+ traf->chunks[chunk_count].size = K;
+ traf->chunks[chunk_count].desc = traf->sample_description_index;
+ traf->chunks[chunk_count].pos = traf->base_data_offset + data_offset;
+ chunk_count++;
+
+ for(i=0;i<K;i++){
+ uint32_t sample_flags;
+ uint32_t sample_composition_time_offset;
+ if(tf_flags & 0x000100) {
+ traf->durmap[durmap_count].dur = stream_read_dword(demuxer->stream);
+ traf->durmap[durmap_count].num = 1;
+ durmap_count++;
+ }
+ if( found_variable_samplesize ) {
+ if(tf_flags & 0x000200) traf->samples[sample_count].size = stream_read_dword(demuxer->stream);
+ else traf->samples[sample_count].size = traf->default_sample_size;
+ sample_count++;
+ }
+ if(tf_flags & 0x000400) {
+ sample_flags = stream_read_dword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\t\tsample_flags=%d\n",sample_flags);
+ }
+ if(tf_flags & 0x000800) {
+ sample_composition_time_offset = stream_read_dword(demuxer->stream);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\t\tsample_composition_time_offset=%d\n",
+ sample_composition_time_offset);
+ }
+ }
+ if( !(tf_flags & 0x000100) ) {
+ traf->durmap[durmap_count].num = K;
+ traf->durmap[durmap_count].dur = traf->default_sample_duration;
+ durmap_count++;
+ }
+ } break;
+ default:
+ id = be2me_32(id);
+ mp_msg(MSGT_DEMUX,MSGL_V,"MOV:\tunknown chunk: %.4s %d\n",(char *)&id,(int)len);
+ }
+ }
+}
+
+static void lschunks_inmoof(demuxer_t* demuxer,int level,off_t endpos,mov_track_t* trak)
+{
+ mov_priv_t* priv=demuxer->priv;
+ off_t len = 0;
+ off_t pos = stream_tell(demuxer->stream);
+
+ while(1){
+ unsigned int id;
+
+ pos += len;
+ if(!stream_seek(demuxer->stream,pos)) {
+ mp_msg(MSGT_DEMUX,MSGL_ERR,"MOV: Cannot seek to the beginning of the 'moof' header (0x%"PRIx64")\n", (int64_t)pos);
+ return;
+ }
+ if(pos>=endpos) return; // END
+ len=stream_read_dword(demuxer->stream);
+ if(len<8) return; // error
+ id=stream_read_dword(demuxer->stream);
+ switch(id) {
+ case MOV_FOURCC('m','f','h','d'): {
+ int version = stream_read_dword(demuxer->stream);
+ int flags = version & 0x0ffffff;
+ version = version >> 24;
+ int sequence_number = stream_read_dword(demuxer->stream); // unordered or incomplete incoming trafs not supported
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:Movie Fragment Header Box 'mfhd' (%d bytes): v=%d flags=%d\n",
+ (int)len, (int)version,(int)flags);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\tsequence_number = %d\n",sequence_number);
+ }break;
+ case MOV_FOURCC('t','r','a','f'):{
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:Track Fragment Box 'traf' #%d (%d bytes)\n",
+ priv->traf_counter,(int)len);
+ priv->trafs[priv->traf_counter] = (mov_traf_t*) malloc(sizeof(mov_traf_t));
+ memset(priv->trafs[priv->traf_counter],0,sizeof(mov_traf_t));
+ lschunks_intraf(demuxer,1,pos+len,priv->trafs[priv->traf_counter]);
+ priv->traf_counter++;
+ } break;
+ default:
+ id = be2me_32(id);
+ mp_msg(MSGT_DEMUX,MSGL_V,"MOV: unknown chunk: %.4s %d\n",(char *)&id,(int)len);
+ }
+ }
+}
+
+static void lschunks_inmvex(demuxer_t* demuxer,int level,off_t endpos,mov_track_t* trak)
+{
+ mov_priv_t* priv=demuxer->priv;
+ off_t len = 0;
+ off_t pos = stream_tell(demuxer->stream);
+
+ while(1){
+ unsigned int id;
+ pos += len;
+ if(!stream_seek(demuxer->stream,pos)) {
+ mp_msg(MSGT_DEMUX,MSGL_ERR,"MOV: Cannot seek to the beginning of the 'mvex' header (0x%"PRIx64")\n",
+ (int64_t)pos);
+ return;
+ }
+ if(pos>=endpos) return; // END
+ len=stream_read_dword(demuxer->stream);
+ if(len<8) return; // error
+
+ id=stream_read_dword(demuxer->stream);
+ switch(id) {
+ case MOV_FOURCC('m','e','h','d'): {
+ int version = stream_read_dword(demuxer->stream);
+ int flags = version & 0x0ffffff;
+ int64_t fragment_duration;
+ version = version >> 24;
+ if (version==1) fragment_duration = stream_read_qword(demuxer->stream);
+ else fragment_duration = stream_read_dword(demuxer->stream); // version==0
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:\tMovie Extends Header Box 'mehd' (%d bytes): v=%d flags=%d\n",
+ (int)len, (int)version,(int)flags);
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:\t\tfragment_duration=%d\n",fragment_duration);
+ }break;
+ case MOV_FOURCC('t','r','e','x'):{
+ int version = stream_read_dword(demuxer->stream);
+ int flags = version & 0x0ffffff;
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:\tTrack Extends Box 'trex' (%d bytes): v=%d flags=%d\n",
+ (int)len, (int)version,(int)flags);
+
+ if( priv->mvex_counter>=MOV_MAX_TRACKS ) continue;
+ priv->mvexs[priv->mvex_counter].track_ID = stream_read_dword(demuxer->stream);
+ priv->mvexs[priv->mvex_counter].sample_description_index = stream_read_dword(demuxer->stream);
+ priv->mvexs[priv->mvex_counter].default_sample_duration = stream_read_dword(demuxer->stream);
+ priv->mvexs[priv->mvex_counter].default_sample_size = stream_read_dword(demuxer->stream);
+ priv->mvexs[priv->mvex_counter].default_sample_flags = stream_read_dword(demuxer->stream);
+ priv->mvex_counter++;
+
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tTrack_ID=%d\n",priv->mvexs[priv->mvex_counter].track_ID);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tdefault_sample_description_index=%d\n",
+ priv->mvexs[priv->mvex_counter].sample_description_index);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tdefault_sample_duration=%d\n",
+ priv->mvexs[priv->mvex_counter].default_sample_duration);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tdefault_sample_size=%d\n",
+ priv->mvexs[priv->mvex_counter].default_sample_size);
+ mp_msg(MSGT_DEMUX, MSGL_DBG3,"MOV:\t\tdefault_sample_flags=%d\n",
+ priv->mvexs[priv->mvex_counter].default_sample_flags);
+ } break;
+ default:
+ id = be2me_32(id);
+ mp_msg(MSGT_DEMUX,MSGL_V,"MOV: unknown chunk: %.4s %d\n",(char *)&id,(int)len);
+ }
+ }
+}
+
unsigned int store_ughvlc(unsigned char *s, unsigned int v){
unsigned int n = 0;
@@ -1100,8 +1715,30 @@
// printf("pos=%d max=%d\n",pos,trak->stdata_len);
}
}
- sh->fps=trak->timescale/
- ((trak->durmap_size>=1)?(float)trak->durmap[0].dur:1);
+ if(trak->durmap_size>=1){
+ int i,totsmpls=0;
+ double mean=0.0;
+ //mp_msg(MSGT_DEMUX, MSGL_DBG2,"timescale=%d durmap_siz=%d\ndurmap=\n",
+ // trak->timescale,trak->durmap_size);
+
+ for(i=0;i<trak->durmap_size;totsmpls+=trak->durmap[i++].num) {
+ mean+=trak->durmap[i].num*trak->durmap[i].dur;
+ //mp_msg(MSGT_DEMUX,MSGL_DBG2,"{%d,%d},",
+ // trak->durmap[i].num,trak->durmap[i].dur);
+ }
+ mean/=totsmpls;
+ //mp_msg(MSGT_DEMUX,MSGL_DBG2,"\n");
+
+ /*be conservative w/this adjustment for the time being. decreasing
+ the framerate and encoding w/o softskip is bound to cause decode
+ errors and possibly cause the output framerate to be suboptimal.
+ really this should be marked as variable framerate in the case
+ of durmap_size>1*/
+ if(mean>(double)trak->durmap[0].dur) mean= trak->durmap[0].dur;
+ sh->fps=trak->timescale/mean;
+ }else{
+ sh->fps=trak->timescale;
+ }
sh->frametime=1.0f/sh->fps;
sh->disp_w=trak->stdata[25]|(trak->stdata[24]<<8);
@@ -1277,6 +1914,10 @@
return;
} else { /* not in track */
switch(id) {
+ case MOV_FOURCC('m','v','e','x'): {
+ mp_msg(MSGT_DEMUX, MSGL_V,"MOV:Movie Extends Box (%d bytes)\n",(int)len);
+ lschunks_inmvex(demuxer, 0, pos+len, NULL);
+ } break;
case MOV_FOURCC('m','v','h','d'): {
int version = stream_read_char(demuxer->stream);
stream_skip(demuxer->stream, (version == 1) ? 19 : 11);
@@ -1607,6 +2248,7 @@
trak->tkdata_len = len;
trak->tkdata = malloc(trak->tkdata_len);
stream_read(demuxer->stream, trak->tkdata, trak->tkdata_len);
+ trak->track_ID = char2int(trak->tkdata, 12);
/*
0 1 Version
1 3 Flags
@@ -1906,6 +2548,7 @@
static demuxer_t* mov_read_header(demuxer_t* demuxer){
mov_priv_t* priv=demuxer->priv;
+ int i,j;
int t_no;
int best_a_id=-1, best_a_len=0;
int best_v_id=-1, best_v_len=0;
@@ -1921,6 +2564,44 @@
return 0;
}
lschunks(demuxer, 0, priv->moov_end, NULL);
+
+ // Parse moofs:
+ for(i=0;i<priv->moof_counter;i++) {
+ stream_reset(demuxer->stream);
+ if(!stream_seek(demuxer->stream,priv->moof_start[i])) {
+ mp_msg(MSGT_DEMUX,MSGL_ERR,"MOV: Cannot seek to the beginning of the moff (0x%"PRIx64")\n",
+ (int64_t)priv->moof_start[i]);
+ return 0;
+ }
+ mp_msg(MSGT_DEMUX,MSGL_V,"\nParsing moof #%d\n",i);
+ lschunks_inmoof(demuxer, 0, priv->moof_end[i], NULL);
+ }
+
+ // Parse mfra
+ stream_reset(demuxer->stream);
+ if( !stream_seek(demuxer->stream, priv->mfra_start ) ) {
+ mp_msg(MSGT_DEMUX,MSGL_ERR,"MOV: Cannot seek to the beginning of the mfra (0x%"PRIx64")\n",
+ (int64_t)priv->mfra_start);
+ return 0;
+ } else {
+ //mp_msg(MSGT_DEMUX,MSGL_V,"\nParsing mfra\n");
+ lschunks_inmfra(demuxer, 0, priv->mfra_end, NULL);
+ }
+
+ // Attach trafs
+ for(i=0;i<priv->track_db;i++){
+ mov_track_t* trak = priv->tracks[i];
+ mov_traf_t *traf;
+ for(j=0;j<priv->traf_counter;j++){
+ traf = priv->trafs[j];
+ if((trak->track_ID) == traf->track_ID) {
+ add_traf_to_track(trak,priv->timescale,traf);
+ mp_msg(MSGT_DEMUX,MSGL_V,"Added traf #%d to track #%d\n",j,trak->track_ID);
+ }
+ }
+ mov_rebuild_index(trak,priv->timescale);
+ }
+
// just in case we have hit eof while parsing...
demuxer->stream->eof = 0;
// mp_msg(MSGT_DEMUX, MSGL_INFO, "--------------\n");
@@ -2295,9 +2976,22 @@
case DEMUXER_CTRL_GET_PERCENT_POS:
{
off_t pos = track->pos;
- if (track->durmap_size >= 1)
- pos *= track->durmap[0].dur;
- *((int *)arg) = (int)(100 * pos / track->length);
+ int dur_count = 0;
+ int sample_count = 0;
+ int i;
+ if (track->durmap_size == 1)
+ pos *= track->durmap[0].dur;
+ else for( i=0; i<track->durmap_size; i++ ) {
+ if( pos>sample_count+track->durmap[i].num ) {
+ dur_count += track->durmap[i].num*track->durmap[i].dur;
+ sample_count += track->durmap[i].num;
+ } else {
+ dur_count += (((int)pos)-sample_count)*track->durmap[i].dur;
+ pos = dur_count;
+ break;
+ }
+ }
+ *((int *)arg) = (int)(100 *pos / track->length);
return DEMUXER_CTRL_OK;
}
}