# HG changeset patch # User michael # Date 1089081339 0 # Node ID 4cf46e9a1bb84fbf6d14f46ca51072c89c9cba0d # Parent c0849ef998e74c42246a11efd2d18977ed487694 Seeking in .mov/.mp4 files printf->av_log patch by (Gael Chardon ) diff -r c0849ef998e7 -r 4cf46e9a1bb8 mov.c --- a/mov.c Fri Jul 02 19:26:51 2004 +0000 +++ b/mov.c Tue Jul 06 02:35:39 2004 +0000 @@ -28,6 +28,7 @@ /* * First version by Francois Revol revol@free.fr + * Seek function by Gael Chardon gael.dev@4now.net * * Features and limitations: * - reads most of the QT files I have (at least the structure), @@ -40,6 +41,7 @@ * (to make .mov parser crash maybe ?), despite what they say in the MPEG FAQ at * http://mpeg.telecomitalialab.com/faq.htm * - the code is quite ugly... maybe I won't do it recursive next time :-) + * - seek is not supported with files that contain edit list * * Funny I didn't know about http://sourceforge.net/projects/qt-ffmpeg/ * when coding this :) (it's a writer anyway) @@ -47,8 +49,8 @@ * Reference documents: * http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt * Apple: - * http://developer.apple.com/techpubs/quicktime/qtdevdocs/QTFF/qtff.html - * http://developer.apple.com/techpubs/quicktime/qtdevdocs/PDF/QTFileFormat.pdf + * http://developer.apple.com/documentation/QuickTime/QTFF/ + * http://developer.apple.com/documentation/QuickTime/PDF/QTFileFormat.pdf * QuickTime is a trademark of Apple (AFAIK :)) */ @@ -60,6 +62,10 @@ #include "qtpalette.h" + +/* Allows seeking (MOV_SPLIT_CHUNKS should also be defined) */ +#define MOV_SEEK + /* allows chunk splitting - should work now... */ /* in case you can't read a file, try commenting */ #define MOV_SPLIT_CHUNKS @@ -217,6 +223,9 @@ long next_chunk; long chunk_count; int64_t *chunk_offsets; + int32_t stts_count; + uint64_t *stts_data; /* concatenated data from the time-to-sample atom (count|duration) */ + int32_t edit_count; /* number of 'edit' (elst atom) */ long sample_to_chunk_sz; MOV_sample_to_chunk_tbl *sample_to_chunk; long sample_to_chunk_index; @@ -289,9 +298,9 @@ i=debug_indent; if(tag == 0) tag = MKTAG('N', 'U', 'L', 'L'); while(i--) - printf("|"); - printf("parse:"); - printf(" %s: tag=%c%c%c%c offset=0x%x size=0x%x\n", + av_log(NULL, AV_LOG_DEBUG, "|"); + av_log(NULL, AV_LOG_DEBUG, "parse:"); + av_log(NULL, AV_LOG_DEBUG, " %s: tag=%c%c%c%c offset=0x%x size=0x%x\n", str, tag & 0xff, (tag >> 8) & 0xff, (tag >> 16) & 0xff, @@ -340,7 +349,7 @@ } total_size += 8; a.offset += 8; - //printf("type: %08x %.4s sz: %Lx %Lx %Lx\n", type, (char*)&type, size, atom.size, total_size); + //av_log(NULL, AV_LOG_DEBUG, "type: %08x %.4s sz: %Lx %Lx %Lx\n", type, (char*)&type, size, atom.size, total_size); if (a.size == 1) { /* 64 bit extended size */ a.size = get_be64(pb) - 8; a.offset += 8; @@ -356,7 +365,7 @@ /* empty */; a.size -= 8; -// printf(" i=%ld\n", i); +// av_log(NULL, AV_LOG_DEBUG, " i=%ld\n", i); if (c->parse_table[i].type == 0) { /* skip leaf atoms data */ // url_seek(pb, atom.offset+atom.size, SEEK_SET); #ifdef DEBUG @@ -376,7 +385,7 @@ } if (!err && total_size < atom.size && atom.size < 0x7ffff) { - //printf("RESET %Ld %Ld err:%d\n", atom.size, total_size, err); + //av_log(NULL, AV_LOG_DEBUG, "RESET %Ld %Ld err:%d\n", atom.size, total_size, err); url_fskip(pb, atom.size - total_size); } @@ -423,8 +432,8 @@ type = get_le32(pb); /* component subtype */ #ifdef DEBUG - printf("ctype= %c%c%c%c (0x%08lx)\n", *((char *)&ctype), ((char *)&ctype)[1], ((char *)&ctype)[2], ((char *)&ctype)[3], (long) ctype); - printf("stype= %c%c%c%c\n", *((char *)&type), ((char *)&type)[1], ((char *)&type)[2], ((char *)&type)[3]); + av_log(NULL, AV_LOG_DEBUG, "ctype= %c%c%c%c (0x%08lx)\n", *((char *)&ctype), ((char *)&ctype)[1], ((char *)&ctype)[2], ((char *)&ctype)[3], (long) ctype); + av_log(NULL, AV_LOG_DEBUG, "stype= %c%c%c%c\n", *((char *)&type), ((char *)&type)[1], ((char *)&type)[2], ((char *)&type)[3]); #endif #ifdef DEBUG /* XXX: yeah this is ugly... */ @@ -483,7 +492,7 @@ if (buf) { get_buffer(pb, buf, len); buf[len] = '\0'; - printf("**buf='%s'\n", buf); + av_log(NULL, AV_LOG_DEBUG, "**buf='%s'\n", buf); av_free(buf); } else #endif @@ -513,7 +522,7 @@ *tag = get_byte(pb); len = mov_mp4_read_descr_len(pb); #ifdef DEBUG - printf("MPEG4 description: tag=0x%02x len=%d\n", *tag, len); + av_log(NULL, AV_LOG_DEBUG, "MPEG4 description: tag=0x%02x len=%d\n", *tag, len); #endif return len; } @@ -554,10 +563,10 @@ sc->esds.avg_bitrate = get_be32(pb); len = mov_mp4_read_descr(pb, &tag); - //printf("LEN %d TAG %d m:%d a:%d\n", len, tag, sc->esds.max_bitrate, sc->esds.avg_bitrate); + //av_log(NULL, AV_LOG_DEBUG, "LEN %d TAG %d m:%d a:%d\n", len, tag, sc->esds.max_bitrate, sc->esds.avg_bitrate); if (tag == MP4DecSpecificDescrTag) { #ifdef DEBUG - printf("Specific MPEG4 header len=%d\n", len); + av_log(NULL, AV_LOG_DEBUG, "Specific MPEG4 header len=%d\n", len); #endif st->codec.extradata = (uint8_t*) av_mallocz(len); if (st->codec.extradata) { @@ -616,10 +625,10 @@ get_be32(pb); /* creation time */ get_be32(pb); /* modification time */ - c->streams[c->total_streams]->time_scale = get_be32(pb); + c->streams[c->fc->nb_streams-1]->time_scale = get_be32(pb); #ifdef DEBUG - printf("track[%i].time_scale = %i\n", c->fc->nb_streams-1, c->streams[c->total_streams]->time_scale); /* time scale */ + av_log(NULL, AV_LOG_DEBUG, "track[%i].time_scale = %i\n", c->fc->nb_streams-1, c->streams[c->fc->nb_streams-1]->time_scale); /* time scale */ #endif get_be32(pb); /* duration */ @@ -640,7 +649,7 @@ get_be32(pb); /* modification time */ c->time_scale = get_be32(pb); /* time scale */ #ifdef DEBUG - printf("time scale = %i\n", c->time_scale); + av_log(NULL, AV_LOG_DEBUG, "time scale = %i\n", c->time_scale); #endif c->duration = get_be32(pb); /* duration */ get_be32(pb); /* preferred scale */ @@ -675,7 +684,7 @@ if (st->codec.extradata) { strcpy(st->codec.extradata, "SVQ3"); // fake get_buffer(pb, st->codec.extradata + 0x5a, atom.size); - //printf("Reading SMI %Ld %s\n", atom.size, (char*)st->codec.extradata + 0x5a); + //av_log(NULL, AV_LOG_DEBUG, "Reading SMI %Ld %s\n", atom.size, (char*)st->codec.extradata + 0x5a); } else url_fskip(pb, atom.size); @@ -711,7 +720,7 @@ #ifdef DEBUG /* for(i=0; ichunk_offsets[i]); + av_log(NULL, AV_LOG_DEBUG, "chunk offset=0x%Lx\n", sc->chunk_offsets[i]); } */ #endif @@ -762,7 +771,7 @@ st->codec.codec_type = codec->type; } #ifdef DEBUG - printf("size=%d 4CC= %c%c%c%c codec_type=%d\n", + av_log(NULL, AV_LOG_DEBUG, "size=%d 4CC= %c%c%c%c codec_type=%d\n", size, (format >> 0) & 0xff, (format >> 8) & 0xff, @@ -794,7 +803,7 @@ get_be32(pb); /* data size, always 0 */ frames_per_sample = get_be16(pb); /* frames per samples */ #ifdef DEBUG - printf("frames/samples = %d\n", frames_per_sample); + av_log(NULL, AV_LOG_DEBUG, "frames/samples = %d\n", frames_per_sample); #endif get_buffer(pb, (uint8_t *)st->codec.codec_name, 32); /* codec name */ @@ -815,7 +824,7 @@ a.type = get_le32(pb); size -= 8; #ifdef DEBUG - printf("VIDEO: atom_type=%c%c%c%c atom.size=%Ld size_left=%d\n", + av_log(NULL, AV_LOG_DEBUG, "VIDEO: atom_type=%c%c%c%c atom.size=%Ld size_left=%d\n", (a.type >> 0) & 0xff, (a.type >> 8) & 0xff, (a.type >> 16) & 0xff, @@ -848,7 +857,7 @@ goto fail; /* MP4DecSpecificDescrTag */ #ifdef DEBUG - printf("Specific MPEG4 header len=%d\n", len); + av_log(NULL, AV_LOG_DEBUG, "Specific MPEG4 header len=%d\n", len); #endif sc->header_data = av_mallocz(len); if (sc->header_data) { @@ -863,7 +872,7 @@ break; } fail: - printf("ATOMENEWSIZE %Ld %d\n", atom.size, url_ftell(pb) - start_pos); + av_log(NULL, AV_LOG_DEBUG, "ATOMENEWSIZE %Ld %d\n", atom.size, url_ftell(pb) - start_pos); if (atom.size > 8) { url_fskip(pb, (atom.size - 8) - ((url_ftell(pb) - start_pos))); @@ -954,7 +963,7 @@ if(st->codec.codec_id==CODEC_ID_AMR_NB || st->codec.codec_id==CODEC_ID_AMR_WB) //from TS26.244 { #ifdef DEBUG - printf("AMR-NB or AMR-WB audio identified!!\n"); + av_log(NULL, AV_LOG_DEBUG, "AMR-NB or AMR-WB audio identified!!\n"); #endif get_be32(pb);get_be32(pb); //Reserved_8 get_be16(pb);//Reserved_2 @@ -1027,7 +1036,7 @@ else if(size>=(16+20)) {//16 bytes read, reading atleast 20 more #ifdef DEBUG - printf("audio size=0x%X\n",size); + av_log(NULL, AV_LOG_DEBUG, "audio size=0x%X\n",size); #endif uint16_t version = get_be16(pb); /* version */ get_be16(pb); /* revision level */ @@ -1041,7 +1050,7 @@ get_be16(pb); /* packet size = 0 */ st->codec.sample_rate = ((get_be32(pb) >> 16)); - //printf("CODECID %d %d %.4s\n", st->codec.codec_id, CODEC_ID_PCM_S16BE, (char*)&format); + //av_log(NULL, AV_LOG_DEBUG, "CODECID %d %d %.4s\n", st->codec.codec_id, CODEC_ID_PCM_S16BE, (char*)&format); switch (st->codec.codec_id) { case CODEC_ID_PCM_S16BE: @@ -1057,8 +1066,8 @@ //Read QT version 1 fields. In version 0 theese dont exist #ifdef DEBUG - printf("version =%d mp4=%d\n",version,c->mp4); - printf("size-(16+20+16)=%d\n",size-(16+20+16)); + av_log(NULL, AV_LOG_DEBUG, "version =%d mp4=%d\n",version,c->mp4); + av_log(NULL, AV_LOG_DEBUG, "size-(16+20+16)=%d\n",size-(16+20+16)); #endif if((version==1) && size>=(16+20+16)) { @@ -1070,7 +1079,7 @@ { //Optional, additional atom-based fields #ifdef DEBUG - printf("offest=0x%X, sizeleft=%d=0x%x,format=%c%c%c%c\n",(int)url_ftell(pb),size - (16 + 20 + 16 ),size - (16 + 20 + 16 ), + av_log(NULL, AV_LOG_DEBUG, "offest=0x%X, sizeleft=%d=0x%x,format=%c%c%c%c\n",(int)url_ftell(pb),size - (16 + 20 + 16 ),size - (16 + 20 + 16 ), (format >> 0) & 0xff, (format >> 8) & 0xff, (format >> 16) & 0xff, @@ -1086,7 +1095,7 @@ size-=(16+20); #ifdef DEBUG if(size>0) - printf("skipping 0x%X bytes\n",size-(16+20)); + av_log(NULL, AV_LOG_DEBUG, "skipping 0x%X bytes\n",size-(16+20)); #endif url_fskip(pb, size); } @@ -1096,7 +1105,7 @@ size-=16; //Unknown size, but lets do our best and skip the rest. #ifdef DEBUG - printf("Strange size, skipping 0x%X bytes\n",size); + av_log(NULL, AV_LOG_DEBUG, "Strange size, skipping 0x%X bytes\n",size); #endif url_fskip(pb, size); } @@ -1119,7 +1128,7 @@ entries = get_be32(pb); #ifdef DEBUG -printf("track[%i].stsc.entries = %i\n", c->fc->nb_streams-1, entries); +av_log(NULL, AV_LOG_DEBUG, "track[%i].stsc.entries = %i\n", c->fc->nb_streams-1, entries); #endif sc->sample_to_chunk_sz = entries; sc->sample_to_chunk = (MOV_sample_to_chunk_tbl*) av_malloc(entries * sizeof(MOV_sample_to_chunk_tbl)); @@ -1130,7 +1139,7 @@ sc->sample_to_chunk[i].count = get_be32(pb); sc->sample_to_chunk[i].id = get_be32(pb); #ifdef DEBUG -/* printf("sample_to_chunk first=%ld count=%ld, id=%ld\n", sc->sample_to_chunk[i].first, sc->sample_to_chunk[i].count, sc->sample_to_chunk[i].id); */ +/* av_log(NULL, AV_LOG_DEBUG, "sample_to_chunk first=%ld count=%ld, id=%ld\n", sc->sample_to_chunk[i].first, sc->sample_to_chunk[i].count, sc->sample_to_chunk[i].id); */ #endif } return 0; @@ -1179,7 +1188,7 @@ entries = get_be32(pb); sc->sample_count = entries; #ifdef DEBUG - printf("sample_size = %ld sample_count = %ld\n", sc->sample_size, sc->sample_count); + av_log(NULL, AV_LOG_DEBUG, "sample_size = %ld sample_count = %ld\n", sc->sample_size, sc->sample_count); #endif if(sc->sample_size) return 0; /* there isn't any table following */ @@ -1189,7 +1198,7 @@ for(i=0; isample_sizes[i] = get_be32(pb); #ifdef DEBUG -/* printf("sample_sizes[]=%ld\n", sc->sample_sizes[i]); */ +/* av_log(NULL, AV_LOG_DEBUG, "sample_sizes[]=%ld\n", sc->sample_sizes[i]); */ #endif } return 0; @@ -1209,18 +1218,21 @@ get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ entries = get_be32(pb); + c->streams[c->fc->nb_streams-1]->stts_count = entries; + c->streams[c->fc->nb_streams-1]->stts_data = (uint64_t*) av_malloc(entries * sizeof(uint64_t)); #ifdef DEBUG -printf("track[%i].stts.entries = %i\n", c->fc->nb_streams-1, entries); +av_log(NULL, AV_LOG_DEBUG, "track[%i].stts.entries = %i\n", c->fc->nb_streams-1, entries); #endif for(i=0; istreams[c->fc->nb_streams - 1]->stts_data[i] = (uint64_t)sample_count<<32 | (uint64_t)sample_duration; #ifdef DEBUG - printf("sample_count=%d, sample_duration=%d\n",sample_count,sample_duration); + av_log(NULL, AV_LOG_DEBUG, "sample_count=%d, sample_duration=%d\n",sample_count,sample_duration); #endif duration+=sample_duration*sample_count; total_sample_count+=sample_count; @@ -1229,9 +1241,9 @@ if (!i && st->codec.codec_type==CODEC_TYPE_VIDEO) { st->codec.frame_rate_base = sample_duration ? sample_duration : 1; - st->codec.frame_rate = c->streams[c->total_streams]->time_scale; + st->codec.frame_rate = c->streams[c->fc->nb_streams-1]->time_scale; #ifdef DEBUG - printf("VIDEO FRAME RATE= %i (sd= %i)\n", st->codec.frame_rate, sample_duration); + av_log(NULL, AV_LOG_DEBUG, "VIDEO FRAME RATE= %i (sd= %i)\n", st->codec.frame_rate, sample_duration); #endif } #endif @@ -1243,19 +1255,19 @@ av_reduce( &st->codec.frame_rate, &st->codec.frame_rate_base, - c->streams[c->total_streams]->time_scale * total_sample_count, + c->streams[c->fc->nb_streams-1]->time_scale * total_sample_count, duration, INT_MAX ); #ifdef DEBUG - printf("FRAME RATE average (video or audio)= %f (tot sample count= %i ,tot dur= %i timescale=%d)\n", (float)st->codec.frame_rate/st->codec.frame_rate_base,total_sample_count,duration,c->streams[c->total_streams]->time_scale); + av_log(NULL, AV_LOG_DEBUG, "FRAME RATE average (video or audio)= %f (tot sample count= %i ,tot dur= %i timescale=%d)\n", (float)st->codec.frame_rate/st->codec.frame_rate_base,total_sample_count,duration,c->streams[c->fc->nb_streams-1]->time_scale); #endif } else { st->codec.frame_rate_base = 1; - st->codec.frame_rate = c->streams[c->total_streams]->time_scale; + st->codec.frame_rate = c->streams[c->fc->nb_streams-1]->time_scale; } return 0; } @@ -1379,7 +1391,7 @@ if (get_le32(pb) != MKTAG( 'd', 'c', 'o', 'm' )) return -1; if (get_le32(pb) != MKTAG( 'z', 'l', 'i', 'b' )) { - dprintf("unknown compression for cmov atom !"); + av_log(NULL, AV_LOG_DEBUG, "unknown compression for cmov atom !"); return -1; } get_be32(pb); /* cmvd atom */ @@ -1416,6 +1428,20 @@ } #endif +/* edit list atom */ +static int mov_read_elst(MOVContext *c, ByteIOContext *pb, MOV_atom_t atom) +{ + print_atom("elst", atom); + + get_byte(pb); /* version */ + get_byte(pb); get_byte(pb); get_byte(pb); /* flags */ + c->streams[c->fc->nb_streams-1]->edit_count = get_be32(pb); /* entries */ +#ifdef DEBUG + av_log(NULL, AV_LOG_DEBUG, "track[%i].edit_count = %i\n", c->fc->nb_streams-1, c->streams[c->fc->nb_streams-1]->edit_count); +#endif + return 0; +} + static const MOVParseTableEntry mov_default_parse_table[] = { /* mp4 atoms */ { MKTAG( 'c', 'o', '6', '4' ), mov_read_stco }, @@ -1426,7 +1452,7 @@ { MKTAG( 'd', 'p', 'n', 'd' ), mov_read_leaf }, { MKTAG( 'd', 'r', 'e', 'f' ), mov_read_leaf }, { MKTAG( 'e', 'd', 't', 's' ), mov_read_default }, -{ MKTAG( 'e', 'l', 's', 't' ), mov_read_leaf }, +{ MKTAG( 'e', 'l', 's', 't' ), mov_read_elst }, { MKTAG( 'f', 'r', 'e', 'e' ), mov_read_leaf }, { MKTAG( 'h', 'd', 'l', 'r' ), mov_read_hdlr }, { MKTAG( 'h', 'i', 'n', 't' ), mov_read_leaf }, @@ -1502,6 +1528,7 @@ av_free(sc->sample_sizes); av_free(sc->keyframes); av_free(sc->header_data); + av_free(sc->stts_data); av_free(sc); } } @@ -1571,7 +1598,7 @@ atom.size = 0x7FFFFFFFFFFFFFFFLL; #ifdef DEBUG - printf("filesz=%Ld\n", atom.size); + av_log(NULL, AV_LOG_DEBUG, "filesz=%Ld\n", atom.size); #endif /* check MOV header */ @@ -1582,7 +1609,7 @@ return -1; } #ifdef DEBUG - printf("on_parse_exit_offset=%d\n", (int) url_ftell(pb)); + av_log(NULL, AV_LOG_DEBUG, "on_parse_exit_offset=%d\n", (int) url_ftell(pb)); #endif /* some cleanup : make sure we are on the mdat atom */ if(!url_is_streamed(pb) && (url_ftell(pb) != mov->mdat_offset)) @@ -1591,11 +1618,11 @@ mov->next_chunk_offset = mov->mdat_offset; /* initialise reading */ #ifdef DEBUG - printf("mdat_reset_offset=%d\n", (int) url_ftell(pb)); + av_log(NULL, AV_LOG_DEBUG, "mdat_reset_offset=%d\n", (int) url_ftell(pb)); #endif #ifdef DEBUG - printf("streams= %d\n", s->nb_streams); + av_log(NULL, AV_LOG_DEBUG, "streams= %d\n", s->nb_streams); #endif mov->total_streams = nb = s->nb_streams; @@ -1617,7 +1644,7 @@ } #endif #ifdef DEBUG - printf("real streams= %d\n", s->nb_streams); + av_log(NULL, AV_LOG_DEBUG, "real streams= %d\n", s->nb_streams); #endif return 0; } @@ -1660,12 +1687,12 @@ sc = 0; for(i=0; itotal_streams; i++) { MOVStreamContext *msc = mov->streams[i]; - //printf("MOCHUNK %ld %d %p pos:%Ld\n", mov->streams[i]->next_chunk, mov->total_streams, mov->streams[i], url_ftell(&s->pb)); + //av_log(NULL, AV_LOG_DEBUG, "MOCHUNK %ld %d %p pos:%Ld\n", mov->streams[i]->next_chunk, mov->total_streams, mov->streams[i], url_ftell(&s->pb)); if ((msc->next_chunk < msc->chunk_count) && msc->next_chunk >= 0 && (msc->chunk_offsets[msc->next_chunk] < offset)) { sc = msc; offset = msc->chunk_offsets[msc->next_chunk]; - //printf("SELETED %Ld i:%d\n", offset, i); + //av_log(NULL, AV_LOG_DEBUG, "SELETED %Ld i:%d\n", offset, i); } } if (!sc || offset==0x0FFFFFFFFFFFFFFFLL) @@ -1678,7 +1705,7 @@ mov->next_chunk_offset = offset; } -//printf("chunk: [%i] %lli -> %lli\n", st_id, mov->next_chunk_offset, offset); +//av_log(NULL, AV_LOG_DEBUG, "chunk: [%i] %lli -> %lli\n", st_id, mov->next_chunk_offset, offset); if(!sc->is_ff_stream) { url_fskip(&s->pb, (offset - mov->next_chunk_offset)); mov->next_chunk_offset = offset; @@ -1714,13 +1741,13 @@ foundsize=sc->sample_to_chunk[i].count*sc->sample_size; } #ifdef DEBUG - /*printf("sample_to_chunk first=%ld count=%ld, id=%ld\n", sc->sample_to_chunk[i].first, sc->sample_to_chunk[i].count, sc->sample_to_chunk[i].id);*/ + /*av_log(NULL, AV_LOG_DEBUG, "sample_to_chunk first=%ld count=%ld, id=%ld\n", sc->sample_to_chunk[i].first, sc->sample_to_chunk[i].count, sc->sample_to_chunk[i].id);*/ #endif } if( (foundsize>0) && (foundsize %lli (%i)\n", st_id, offset, offset + size, size); +//av_log(NULL, AV_LOG_DEBUG, "chunk: [%i] %lli -> %lli (%i)\n", st_id, offset, offset + size, size); if(size == 0x0FFFFFFF) size = mov->mdat_size + mov->mdat_offset - offset; if(size < 0) @@ -1756,7 +1783,7 @@ return -1; url_fseek(&s->pb, offset, SEEK_SET); - //printf("READCHUNK hlen: %d %d off: %Ld pos:%Ld\n", size, sc->header_len, offset, url_ftell(&s->pb)); + //av_log(NULL, AV_LOG_DEBUG, "READCHUNK hlen: %d %d off: %Ld pos:%Ld\n", size, sc->header_len, offset, url_ftell(&s->pb)); if (sc->header_len > 0) { av_new_packet(pkt, size + sc->header_len); memcpy(pkt->data, sc->header_data, sc->header_len); @@ -1793,11 +1820,11 @@ #ifdef DEBUG /* - printf("Packet (%d, %d, %ld) ", pkt->stream_index, st_id, pkt->size); + av_log(NULL, AV_LOG_DEBUG, "Packet (%d, %d, %ld) ", pkt->stream_index, st_id, pkt->size); for(i=0; i<8; i++) - printf("%02x ", pkt->data[i]); + av_log(NULL, AV_LOG_DEBUG, "%02x ", pkt->data[i]); for(i=0; i<8; i++) - printf("%c ", (pkt->data[i]) & 0x7F); + av_log(NULL, AV_LOG_DEBUG, "%c ", (pkt->data[i]) & 0x7F); puts(""); */ #endif @@ -1807,6 +1834,198 @@ return 0; } +#if defined(MOV_SPLIT_CHUNKS) && defined(MOV_SEEK) +/** + * Seek method based on the one described in the Appendix C of QTFileFormat.pdf + */ +static int mov_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp) +{ + MOVContext* mov = (MOVContext *) s->priv_data; + MOVStreamContext* sc; + int32_t i, a, b, m; + int64_t sample_time; + int64_t start_time; + int32_t seek_sample, sample; + int32_t duration; + int32_t count; + int32_t chunk; + int32_t left_in_chunk; + int64_t chunk_file_offset; + int64_t sample_file_offset; + int32_t first_chunk_sample; + int32_t sample_to_chunk_idx; + int mov_idx; + + // Find the corresponding mov stream + for (mov_idx = 0; mov_idx < mov->total_streams; mov_idx++) + if (mov->streams[mov_idx]->ffindex == stream_index) + break; + if (mov_idx == mov->total_streams) { + av_log(s, AV_LOG_ERROR, "mov: requested stream was not found in mov streams (idx=%i)\n", stream_index); + return -1; + } + sc = mov->streams[mov_idx]; + + // Step 1. Find the edit that contains the requested time (elst) + if (sc->edit_count) { + // FIXME should handle edit list + av_log(s, AV_LOG_ERROR, "mov: does not handle seeking in files that contain edit list (c:%d)\n", sc->edit_count); + return -1; + } + + // Step 2. Find the corresponding sample using the Time-to-sample atom (stts) */ +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Searching for time %li in stream #%i (time_scale=%i)\n", (long)timestamp, mov_idx, sc->time_scale); +#endif + // convert timestamp from time_base unit to timescale unit + sample_time = av_rescale( timestamp, + (int64_t)sc->time_scale * s->streams[stream_index]->time_base.num, + (int64_t)s->streams[stream_index]->time_base.den); + start_time = 0; // FIXME use elst atom + sample = 1; // sample are 0 based in table +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Searching for sample_time %li \n", (long)sample_time); +#endif + for (i = 0; i < sc->stts_count; i++) { + count = (uint32_t)(sc->stts_data[i]>>32); + duration = (uint32_t)(sc->stts_data[i]&0xffff); +//av_log(s, AV_LOG_DEBUG, "> sample_time %lli \n", (long)sample_time); +//av_log(s, AV_LOG_DEBUG, "> count=%i duration=%i\n", count, duration); + if ((start_time + count*duration) > sample_time) { + sample += (sample_time - start_time) / duration; + break; + } + sample += count; + start_time += count * duration; + } + /* NOTE: despite what qt doc say, the dt value (Display Time in qt vocabulary) computed with the stts atom + is a decoding time stamp (dts) not a presentation time stamp. And as usual dts != pts for stream with b frames */ + +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Found time %li at sample #%u\n", (long)sample_time, sample); +#endif + if (sample > sc->sample_count) { + av_log(s, AV_LOG_ERROR, "mov: sample pos is too high, unable to seek (req. sample=%i, sample count=%ld)\n", sample, sc->sample_count); + return -1; + } + + // Step 3. Find the prior sync. sample using the Sync sample atom (stss) + if (sc->keyframes) { + a = 0; + b = sc->keyframe_count - 1; + while (a < b) { + m = (a + b + 1) >> 1; + if (sc->keyframes[m] > sample) { + b = m - 1; + } else { + a = m; + } +#ifdef DEBUG + // av_log(s, AV_LOG_DEBUG, "a=%i (%i) b=%i (%i) m=%i (%i) stream #%i\n", a, sc->keyframes[a], b, sc->keyframes[b], m, sc->keyframes[m], mov_idx); +#endif + } + seek_sample = sc->keyframes[a]; + } + else + seek_sample = sample; // else all samples are key frames +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Found nearest keyframe at sample #%i \n", seek_sample); +#endif + + // Step 4. Find the chunk of the sample using the Sample-to-chunk-atom (stsc) + for (first_chunk_sample = 1, i = 0; i < (sc->sample_to_chunk_sz - 1); i++) { + b = (sc->sample_to_chunk[i + 1].first - sc->sample_to_chunk[i].first) * sc->sample_to_chunk[i].count; + if (seek_sample >= first_chunk_sample && seek_sample < (first_chunk_sample + b)) + break; + first_chunk_sample += b; + } + chunk = sc->sample_to_chunk[i].first + (seek_sample - first_chunk_sample) / sc->sample_to_chunk[i].count; + left_in_chunk = sc->sample_to_chunk[i].count - (seek_sample - first_chunk_sample) % sc->sample_to_chunk[i].count; + first_chunk_sample += ((seek_sample - first_chunk_sample) / sc->sample_to_chunk[i].count) * sc->sample_to_chunk[i].count; + sample_to_chunk_idx = i; +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Sample was found in chunk #%i at sample offset %i (idx %i)\n", chunk, seek_sample - first_chunk_sample, sample_to_chunk_idx); +#endif + + // Step 5. Find the offset of the chunk using the chunk offset atom + if (!sc->chunk_offsets) { + av_log(s, AV_LOG_ERROR, "mov: no chunk offset atom, unable to seek\n"); + return -1; + } + if (chunk > sc->chunk_count) { + av_log(s, AV_LOG_ERROR, "mov: chunk offset atom too short, unable to seek (req. chunk=%i, chunk count=%li)\n", chunk, sc->chunk_count); + return -1; + } + chunk_file_offset = sc->chunk_offsets[chunk - 1]; +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Chunk file offset is #%llu \n", chunk_file_offset); +#endif + + // Step 6. Find the byte offset within the chunk using the sample size atom + sample_file_offset = chunk_file_offset; + if (sc->sample_size) + sample_file_offset += (seek_sample - first_chunk_sample) * sc->sample_size; + else { + for (i = 0; i < (seek_sample - first_chunk_sample); i++) { + sample_file_offset += sc->sample_sizes[first_chunk_sample + i - 1]; + } + } +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Sample file offset is #%llu \n", sample_file_offset); +#endif + + // Step 6. Update the parser + mov->partial = sc; + mov->next_chunk_offset = sample_file_offset; + // Update current stream state + sc->current_sample = seek_sample - 1; // zero based + sc->left_in_chunk = left_in_chunk; + sc->next_chunk = chunk; // +1 -1 (zero based) + sc->sample_to_chunk_index = sample_to_chunk_idx; + + // Update other streams + for (i = 0; itotal_streams; i++) { + if (i == mov_idx) continue; + // Find the nearest 'next' chunk + MOVStreamContext *msc = mov->streams[i]; + a = 0; + b = msc->chunk_count - 1; + while (a < b) { + m = (a + b + 1) >> 1; + if (msc->chunk_offsets[m] > chunk_file_offset) { + b = m - 1; + } else { + a = m; + } +#ifdef DEBUG +/* av_log(s, AV_LOG_DEBUG, "a=%i (%li) b=%i (%li) m=%i (%li) stream #%i\n" + , a, (long)msc->chunk_offsets[a], b, (long)msc->chunk_offsets[b], m, (long)msc->chunk_offsets[m], i); */ +#endif + } + msc->next_chunk = a; + if (msc->chunk_offsets[a] < chunk_file_offset && a < (msc->chunk_count-1)) + msc->next_chunk ++; +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Nearest next chunk for stream #%i is #%i @%lli\n", i, msc->next_chunk+1, msc->chunk_offsets[msc->next_chunk]); +#endif + // Compute sample count and index in the sample_to_chunk table (what a pity) + msc->sample_to_chunk_index = 0; + msc->current_sample = 0; + for(; msc->sample_to_chunk_index < (msc->sample_to_chunk_sz - 1) + && msc->sample_to_chunk[msc->sample_to_chunk_index + 1].first <= (1 + msc->next_chunk); msc->sample_to_chunk_index++) { + msc->current_sample += (msc->sample_to_chunk[msc->sample_to_chunk_index + 1].first - msc->sample_to_chunk[msc->sample_to_chunk_index].first) \ + * msc->sample_to_chunk[msc->sample_to_chunk_index].count; + } + msc->current_sample += (msc->next_chunk - (msc->sample_to_chunk[msc->sample_to_chunk_index].first - 1)) * sc->sample_to_chunk[msc->sample_to_chunk_index].count; + msc->left_in_chunk = msc->sample_to_chunk[msc->sample_to_chunk_index].count - 1; +#ifdef DEBUG + av_log(s, AV_LOG_DEBUG, "Next Sample for stream #%i is #%i @%i\n", i, msc->current_sample + 1, msc->sample_to_chunk_index + 1); +#endif + } + return 0; +} +#endif + static int mov_read_close(AVFormatContext *s) { int i; @@ -1828,6 +2047,9 @@ mov_read_header, mov_read_packet, mov_read_close, +#if defined(MOV_SPLIT_CHUNKS) && defined(MOV_SEEK) + mov_read_seek, +#endif }; int mov_init(void)