static unsigned int
NextFragmentIndex(unsigned int track_id)
{
    int track_index = -1;
    for (unsigned int i=0; i<TrackIds.ItemCount(); i++) {
        if (TrackIds[i] == track_id) {
            track_index = i;
            break;
        }
    }
    if (track_index == -1) {
        track_index = TrackCounters.ItemCount();
        TrackIds.Append(track_id);
        TrackCounters.Append(0);
    }
    return TrackCounters[track_index]++;
}
Exemple #2
0
/*----------------------------------------------------------------------
|   AP4_Processor::Process
+---------------------------------------------------------------------*/
AP4_Result
AP4_Processor::Process(AP4_ByteStream&   input, 
                       AP4_ByteStream&   output,
                       ProgressListener* listener,
                       AP4_AtomFactory&  atom_factory)
{
    // read all atoms 
    AP4_AtomParent top_level;
    AP4_Atom*      atom;
    while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom))) {
        top_level.AddChild(atom);
    }

    // remove the [mdat] atom, keep a ref to [moov]
    AP4_MoovAtom* moov = NULL;
    AP4_List<AP4_Atom>::Item* atom_item = top_level.GetChildren().FirstItem();
    while (atom_item) {
        atom = atom_item->GetData();
        AP4_List<AP4_Atom>::Item* next = atom_item->GetNext();
        if (atom->GetType() == AP4_ATOM_TYPE_MDAT) {
            atom->Detach();
            delete atom;
        } else if (atom->GetType() == AP4_ATOM_TYPE_MOOV) {
            moov = (AP4_MoovAtom*)atom;
        }
        atom_item = next;
    }

    // initialize the processor
    AP4_Result result = Initialize(top_level, input);
    if (AP4_FAILED(result)) return result;

    // process the tracks if we have a moov atom
    AP4_Array<AP4_SampleLocator> locators;
    AP4_Cardinal                 track_count       = 0;
    AP4_List<AP4_TrakAtom>*      trak_atoms        = NULL;
    AP4_LargeSize                mdat_payload_size = 0;
    TrackHandler**               handlers          = NULL;
    AP4_SampleCursor*            cursors           = NULL;
    if (moov) {
        // build an array of track sample locators
        trak_atoms = &moov->GetTrakAtoms();
        track_count = trak_atoms->ItemCount();
        cursors = new AP4_SampleCursor[track_count];
        handlers = new TrackHandler*[track_count];
        for (AP4_Ordinal i=0; i<track_count; i++) {
            handlers[i] = NULL;
        }
        
        unsigned int index = 0;
        for (AP4_List<AP4_TrakAtom>::Item* item = trak_atoms->FirstItem(); item; item=item->GetNext()) {
            AP4_TrakAtom* trak = item->GetData();

            // find the stsd atom
            AP4_ContainerAtom* stbl = AP4_DYNAMIC_CAST(AP4_ContainerAtom, trak->FindChild("mdia/minf/stbl"));
            if (stbl == NULL) continue;
            
            // see if there's an external data source for this track
            AP4_ByteStream* trak_data_stream = &input;
            for (AP4_List<ExternalTrackData>::Item* ditem = m_ExternalTrackData.FirstItem(); ditem; ditem=ditem->GetNext()) {
                ExternalTrackData* tdata = ditem->GetData();
                if (tdata->m_TrackId == trak->GetId()) {
                    trak_data_stream = tdata->m_MediaData;
                    break;
                }
            }

            // create the track handler    
            handlers[index] = CreateTrackHandler(trak);
            cursors[index].m_Locator.m_TrakIndex   = index;
            cursors[index].m_Locator.m_SampleTable = new AP4_AtomSampleTable(stbl, *trak_data_stream);
            cursors[index].m_Locator.m_SampleIndex = 0;
            cursors[index].m_Locator.m_ChunkIndex  = 0;
            cursors[index].m_Locator.m_SampleTable->GetSample(0, cursors[index].m_Locator.m_Sample);

            index++;            
        }

        // figure out the layout of the chunks
        for (;;) {
            // see which is the next sample to write
            AP4_UI64 min_offset = (AP4_UI64)(-1);
            int cursor = -1;
            for (unsigned int i=0; i<track_count; i++) {
                if (!cursors[i].m_EndReached &&
                    cursors[i].m_Locator.m_Sample.GetOffset() <= min_offset) {
                    min_offset = cursors[i].m_Locator.m_Sample.GetOffset();
                    cursor = i;
                }
            }

            // stop if all cursors are exhausted
            if (cursor == -1) break;

            // append this locator to the layout list
            AP4_SampleLocator& locator = cursors[cursor].m_Locator;
            locators.Append(locator);

            // move the cursor to the next sample
            locator.m_SampleIndex++;
            if (locator.m_SampleIndex == locator.m_SampleTable->GetSampleCount()) {
                // mark this track as completed
                cursors[cursor].m_EndReached = true;
            } else {
                // get the next sample info
                locator.m_SampleTable->GetSample(locator.m_SampleIndex, locator.m_Sample);
                AP4_Ordinal skip, sdesc;
                locator.m_SampleTable->GetChunkForSample(locator.m_SampleIndex,
                                                         locator.m_ChunkIndex,
                                                         skip, sdesc);
            }
        }

        // update the stbl atoms and compute the mdat size
        int current_track = -1;
        int current_chunk = -1;
        AP4_Position current_chunk_offset = 0;
        AP4_Size current_chunk_size = 0;
        for (AP4_Ordinal i=0; i<locators.ItemCount(); i++) {
            AP4_SampleLocator& locator = locators[i];
            if ((int)locator.m_TrakIndex  != current_track ||
                (int)locator.m_ChunkIndex != current_chunk) {
                // start a new chunk for this track
                current_chunk_offset += current_chunk_size;
                current_chunk_size = 0;
                current_track = locator.m_TrakIndex;
                current_chunk = locator.m_ChunkIndex;
                locator.m_SampleTable->SetChunkOffset(locator.m_ChunkIndex, current_chunk_offset);
            } 
            AP4_Size sample_size;
            TrackHandler* handler = handlers[locator.m_TrakIndex];
            if (handler) {
                sample_size = handler->GetProcessedSampleSize(locator.m_Sample);
                locator.m_SampleTable->SetSampleSize(locator.m_SampleIndex, sample_size);
            } else {
                sample_size = locator.m_Sample.GetSize();
            }
            current_chunk_size += sample_size;
            mdat_payload_size  += sample_size;
        }

        // process the tracks (ex: sample descriptions processing)
        for (AP4_Ordinal i=0; i<track_count; i++) {
            TrackHandler* handler = handlers[i];
            if (handler) handler->ProcessTrack();
        }
    }

    // initialize the processor
    Finalize(top_level);

    // calculate the size of all atoms combined
    AP4_UI64 atoms_size = 0;
    top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size));

    // see if we need a 64-bit or 32-bit mdat
    AP4_Size mdat_header_size = AP4_ATOM_HEADER_SIZE;
    if (mdat_payload_size+mdat_header_size > 0xFFFFFFFF) {
        // we need a 64-bit size
        mdat_header_size += 8;
    }
    
    // adjust the chunk offsets
    for (AP4_Ordinal i=0; i<track_count; i++) {
        AP4_TrakAtom* trak;
        trak_atoms->Get(i, trak);
        trak->AdjustChunkOffsets(atoms_size+mdat_header_size);
    }

    // write all atoms
    top_level.GetChildren().Apply(AP4_AtomListWriter(output));

    // write mdat header
    if (mdat_payload_size) {
        if (mdat_header_size == AP4_ATOM_HEADER_SIZE) {
            // 32-bit size
            output.WriteUI32((AP4_UI32)(mdat_header_size+mdat_payload_size));
            output.WriteUI32(AP4_ATOM_TYPE_MDAT);
        } else {
            // 64-bit size
            output.WriteUI32(1);
            output.WriteUI32(AP4_ATOM_TYPE_MDAT);
            output.WriteUI64(mdat_header_size+mdat_payload_size);
        }
    }
    
#if defined(AP4_DEBUG)
    AP4_Position before;
    output.Tell(before);
#endif

    // write the samples
    if (moov) {
        AP4_Sample     sample;
        AP4_DataBuffer data_in;
        AP4_DataBuffer data_out;
        for (unsigned int i=0; i<locators.ItemCount(); i++) {
            AP4_SampleLocator& locator = locators[i];
            locator.m_Sample.ReadData(data_in);
            TrackHandler* handler = handlers[locator.m_TrakIndex];
            if (handler) {
                result = handler->ProcessSample(data_in, data_out);
                if (AP4_FAILED(result)) return result;
                output.Write(data_out.GetData(), data_out.GetDataSize());
            } else {
                output.Write(data_in.GetData(), data_in.GetDataSize());            
            }

            // notify the progress listener
            if (listener) {
                listener->OnProgress(i+1, locators.ItemCount());
            }
        }

        // cleanup
        for (AP4_Ordinal i=0; i<track_count; i++) {
            delete cursors[i].m_Locator.m_SampleTable;
            delete handlers[i];
        }
        delete[] cursors;
        delete[] handlers;
    }

#if defined(AP4_DEBUG)
    AP4_Position after;
    output.Tell(after);
    AP4_ASSERT(after-before == mdat_payload_size);
#endif

    return AP4_SUCCESS;
}
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc < 2) {
        PrintUsageAndExit();
    }

    // init the variables
    const char*  input_filename                = NULL;
    const char*  output_filename               = NULL;
    const char*  track_selector                = NULL;
    AP4_UI32     selected_track_id             = 0;
    unsigned int fragment_duration             = 0;
    bool         auto_detect_fragment_duration = true;
    bool         create_segment_index          = false;
    bool         quiet                         = false;
    AP4_UI32     timescale = 0;
    AP4_Result   result;

    Options.verbosity          = 1;
    Options.debug              = false;
    Options.trim               = false;
    Options.no_tfdt            = false;
    Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE;
    
    // parse the command line
    argv++;
    char* arg;
    while ((arg = *argv++)) {
        if (!strcmp(arg, "--verbosity")) {
            arg = *argv++;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument after --verbosity option\n");
                return 1;
            }
            Options.verbosity = strtoul(arg, NULL, 10);
        } else if (!strcmp(arg, "--debug")) {
            Options.debug = true;
        } else if (!strcmp(arg, "--index")) {
            create_segment_index = true;
        } else if (!strcmp(arg, "--quiet")) {
            quiet = true;
        } else if (!strcmp(arg, "--trim")) {
            Options.trim = true;
        } else if (!strcmp(arg, "--no-tfdt")) {
            Options.no_tfdt = true;
        } else if (!strcmp(arg, "--force-i-frame-sync")) {
            arg = *argv++;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument after --fragment-duration option\n");
                return 1;
            }
            if (!strcmp(arg, "all")) {
                Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_ALL;
            } else if (!strcmp(arg, "auto")) {
                Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_AUTO;
            } else {
                fprintf(stderr, "ERROR: unknown mode for --force-i-frame-sync\n");
                return 1;
            }
        } else if (!strcmp(arg, "--fragment-duration")) {
            arg = *argv++;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument after --fragment-duration option\n");
                return 1;
            }
            fragment_duration = strtoul(arg, NULL, 10);
            auto_detect_fragment_duration = false;
        } else if (!strcmp(arg, "--timescale")) {
            arg = *argv++;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument after --timescale option\n");
                return 1;
            }
            timescale = strtoul(arg, NULL, 10);
        } else if (!strcmp(arg, "--track")) {
            track_selector = *argv++;
            if (track_selector == NULL) {
                fprintf(stderr, "ERROR: missing argument after --track option\n");
                return 1;
            }
        } else {
            if (input_filename == NULL) {
                input_filename = arg;
            } else if (output_filename == NULL) {
                output_filename = arg;
            } else {
                fprintf(stderr, "ERROR: unexpected argument '%s'\n", arg);
                return 1;
            }
        }
    }
    if (Options.debug && Options.verbosity == 0) {
        Options.verbosity = 1;
    }
    
    if (input_filename == NULL) {
        fprintf(stderr, "ERROR: no input specified\n");
        return 1;
    }
    AP4_ByteStream* input_stream = NULL;
    result = AP4_FileByteStream::Create(input_filename, 
                                        AP4_FileByteStream::STREAM_MODE_READ, 
                                        input_stream);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open input (%d)\n", result);
        return 1;
    }
    if (output_filename == NULL) {
        fprintf(stderr, "ERROR: no output specified\n");
        return 1;
    }
    AP4_ByteStream* output_stream = NULL;
    result = AP4_FileByteStream::Create(output_filename, 
                                        AP4_FileByteStream::STREAM_MODE_WRITE,
                                        output_stream);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot create/open output (%d)\n", result);
        return 1;
    }
    
    // parse the input MP4 file (moov only)
    AP4_File input_file(*input_stream, AP4_DefaultAtomFactory::Instance, true);
    
    // check the file for basic properties
    if (input_file.GetMovie() == NULL) {
        fprintf(stderr, "ERROR: no movie found in the file\n");
        return 1;
    }
    if (!quiet && input_file.GetMovie()->HasFragments()) {
        fprintf(stderr, "NOTICE: file is already fragmented, it will be re-fragmented\n");
    }
    
    // create a cusor list to keep track of the tracks we will read from
    AP4_Array<TrackCursor*> cursors;
    
    // iterate over all tracks
    TrackCursor*  video_track = NULL;
    TrackCursor*  audio_track = NULL;
    TrackCursor*  subtitles_track = NULL;
    unsigned int video_track_count = 0;
    unsigned int audio_track_count = 0;
    unsigned int subtitles_track_count = 0;
    for (AP4_List<AP4_Track>::Item* track_item = input_file.GetMovie()->GetTracks().FirstItem();
                                    track_item;
                                    track_item = track_item->GetNext()) {
        AP4_Track* track = track_item->GetData();

        // sanity check
        if (track->GetSampleCount() == 0 && !input_file.GetMovie()->HasFragments()) {
            fprintf(stderr, "WARNING: track %d has no samples, it will be skipped\n", track->GetId());
            continue;
        }

        // create a sample array for this track
        SampleArray* sample_array;
        if (input_file.GetMovie()->HasFragments()) {
            sample_array = new CachedSampleArray(track);
        } else {
            sample_array = new SampleArray(track);
        }

        // create a cursor for the track
        TrackCursor* cursor = new TrackCursor(track, sample_array);
        cursor->m_Tfra->SetTrackId(track->GetId());
        cursors.Append(cursor);

        if (track->GetType() == AP4_Track::TYPE_VIDEO) {
            if (video_track) {
                fprintf(stderr, "WARNING: more than one video track found\n");
            } else {
                video_track = cursor;
            }
            video_track_count++;
        } else if (track->GetType() == AP4_Track::TYPE_AUDIO) {
            if (audio_track == NULL) {
                audio_track = cursor;
            }
            audio_track_count++;
        } else if (track->GetType() == AP4_Track::TYPE_SUBTITLES) {
            if (subtitles_track == NULL) {
                subtitles_track = cursor;
            }
            subtitles_track_count++;
        }
    }

    if (cursors.ItemCount() == 0) {
        fprintf(stderr, "ERROR: no valid track found\n");
        return 1;
    }
    
    if (track_selector) {
        if (!strncmp("audio", track_selector, 5)) {
            if (audio_track) {
                selected_track_id = audio_track->m_Track->GetId();
            } else {
                fprintf(stderr, "ERROR: no audio track found\n");
                return 1;
            }
        } else if (!strncmp("video", track_selector, 5)) {
            if (video_track) {
                selected_track_id = video_track->m_Track->GetId();
            } else {
                fprintf(stderr, "ERROR: no video track found\n");
                return 1;
            }
        } else if (!strncmp("subtitles", track_selector, 9)) {
            if (subtitles_track) {
                selected_track_id = subtitles_track->m_Track->GetId();
            } else {
                fprintf(stderr, "ERROR: no subtitles track found\n");
                return 1;
            }
        } else {
            selected_track_id = (AP4_UI32)strtol(track_selector, NULL, 10);
            bool found = false;
            for (unsigned int i=0; i<cursors.ItemCount(); i++) {
                if (cursors[i]->m_Track->GetId() == selected_track_id) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                fprintf(stderr, "ERROR: track not found\n");
                return 1;
            }
        }
    }
    
    if (video_track_count == 0 && audio_track_count == 0 && subtitles_track_count == 0) {
        fprintf(stderr, "ERROR: no audio, video, or subtitles track in the file\n");
        return 1;
    }
    AP4_AvcSampleDescription* avc_desc = NULL;
    if (video_track && (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE)) {
        // that feature is only supported for AVC
        AP4_SampleDescription* sdesc = video_track->m_Track->GetSampleDescription(0);
        if (sdesc) {
            avc_desc = AP4_DYNAMIC_CAST(AP4_AvcSampleDescription, sdesc);
        }
        if (avc_desc == NULL) {
            fprintf(stderr, "--force-i-frame-sync can only be used with AVC/H.264 video\n");
            return 1;
        }
    }
    
    // remember where the stream was
    AP4_Position position;
    input_stream->Tell(position);

    // for fragmented input files, we need to populate the sample arrays
    if (input_file.GetMovie()->HasFragments()) {
        AP4_LinearReader reader(*input_file.GetMovie(), input_stream);
        for (unsigned int i=0; i<cursors.ItemCount(); i++) {
            reader.EnableTrack(cursors[i]->m_Track->GetId());
        }
        AP4_UI32 track_id;
        AP4_Sample sample;
        do {
            result = reader.GetNextSample(sample, track_id);
            if (AP4_SUCCEEDED(result)) {
                for (unsigned int i=0; i<cursors.ItemCount(); i++) {
                    if (cursors[i]->m_Track->GetId() == track_id) {
                        cursors[i]->m_Samples->AddSample(sample);
                        break;
                    }
                }
            }
        } while (AP4_SUCCEEDED(result));
        
    } else if (video_track && (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE)) {
        AP4_Sample sample;
        if (Options.force_i_frame_sync == AP4_FRAGMENTER_FORCE_SYNC_MODE_AUTO) {
            // detect if this looks like an open-gop source
            for (unsigned int i=1; i<video_track->m_Samples->GetSampleCount(); i++) {
                if (AP4_SUCCEEDED(video_track->m_Samples->GetSample(i, sample))) {
                    if (sample.IsSync()) {
                        // we found a sync i-frame, assume this is *not* an open-gop source
                        Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE;
                        if (Options.debug) {
                            printf("this does not look like an open-gop source, not forcing i-frame sync flags\n");
                        }
                        break;
                    }
                }
            }
        }
        if (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE) {
            for (unsigned int i=0; i<video_track->m_Samples->GetSampleCount(); i++) {
                if (AP4_SUCCEEDED(video_track->m_Samples->GetSample(i, sample))) {
                    if (IsIFrame(sample, avc_desc)) {
                        video_track->m_Samples->ForceSync(i);
                    }
                }
            }
        }
    }

    // return the stream to its original position
    input_stream->Seek(position);

    // auto-detect the fragment duration if needed
    if (auto_detect_fragment_duration) {
        if (video_track) {
            fragment_duration = AutoDetectFragmentDuration(video_track);
        } else if (audio_track && input_file.GetMovie()->HasFragments()) {
            fragment_duration = AutoDetectAudioFragmentDuration(*input_stream, audio_track);
        }
        if (fragment_duration == 0) {
            if (Options.verbosity > 0) {
                fprintf(stderr, "unable to autodetect fragment duration, using default\n");
            }
            fragment_duration = AP4_FRAGMENTER_DEFAULT_FRAGMENT_DURATION;
        } else if (fragment_duration > AP4_FRAGMENTER_MAX_AUTO_FRAGMENT_DURATION) {
            if (Options.verbosity > 0) {
                fprintf(stderr, "auto-detected fragment duration too large, using default\n");
            }
            fragment_duration = AP4_FRAGMENTER_DEFAULT_FRAGMENT_DURATION;
        }
    }
    
    // fragment the file
    Fragment(input_file, *output_stream, cursors, fragment_duration, timescale, selected_track_id, create_segment_index);
    
    // cleanup and exit
    if (input_stream)  input_stream->Release();
    if (output_stream) output_stream->Release();

    return 0;
}
/*----------------------------------------------------------------------
|   AP4_Processor::MuxStream
+---------------------------------------------------------------------*/
AP4_Result
AP4_Processor::MuxStream(
	AP4_Array<AP4_ByteStream *> &input,
	AP4_ByteStream& output,
	AP4_UI08		partitions,
	AP4_AtomFactory& atom_factory)
{
	AP4_Result		result;
	AP4_UI64		stream_offset = 0;

	if (partitions & 1)
	{
		// read all atoms.
		// keep all atoms except [mdat]
		// keep a ref to [moov]
		// put [moof] atoms in a separate list
		AP4_AtomParent              top_level;
		AP4_Array<AP4_MoovAtom*>	moov;
		AP4_Size					track_count(0);

		for(AP4_Size streamid(0); streamid < input.ItemCount(); ++streamid)
		{
			for (AP4_Atom* atom = NULL; AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*input[streamid], atom)); input[streamid]->Tell(stream_offset))
			{
				if (atom->GetType() == AP4_ATOM_TYPE_MFRA) {
					delete atom;
					continue;
				}
				else if (atom->GetType() == AP4_ATOM_TYPE_SIDX) {
					delete atom;
					continue;
				}
				else if (atom->GetType() == AP4_ATOM_TYPE_SSIX) {
					delete atom;
					continue;
				}
				if (streamid == 0)
					top_level.AddChild(atom);
				else if (atom->GetType() != AP4_ATOM_TYPE_MOOV)
					delete atom;
				if (atom->GetType() == AP4_ATOM_TYPE_MOOV)
				{
					moov.Append(AP4_DYNAMIC_CAST(AP4_MoovAtom,atom));
					break;
				}
			}
			if (moov.ItemCount() == streamid)
				return AP4_ERROR_INVALID_FORMAT;
			
			while (AP4_SUCCEEDED(moov[streamid]->DeleteChild(AP4_ATOM_TYPE_PSSH, 0)));

			// Remove tracks we cannot handle
			for (AP4_List<AP4_TrakAtom>::Item *item(moov[streamid]->GetTrakAtoms().FirstItem()); item;)
				if (!item->GetData()->FindChild("mdia/minf/stbl"))
					moov[streamid]->GetTrakAtoms().Remove(item);
				else
					item = item->GetNext();
			track_count += moov[streamid]->GetTrakAtoms().ItemCount();
		}

		// initialize the processor
		if (AP4_FAILED(result = Initialize(top_level, *input[0]))) 
			return result;

		// process the tracks if we have a moov atom
		m_TrackData.SetItemCount(track_count);
		m_StreamData.SetItemCount(input.ItemCount());
	
		//NormalizeTREX(mvex, 0, m_TrackCounts[0], m_TrackCounts[1]);
		AP4_Cardinal internal_index(0);
		AP4_ContainerAtom *mvex_base(0);
		AP4_List<AP4_TrakAtom>::Item *item = NULL;

		for (AP4_Size streamid(0); streamid < input.ItemCount(); ++streamid)
		{
			m_StreamData[streamid].trackStart = internal_index;
			m_StreamData[streamid].stream = input[streamid];
			if (streamid)
				moov[0]->AddTrakAtoms(moov[streamid]->GetTrakAtoms(), item);
			else
				item = moov[streamid]->GetTrakAtoms().FirstItem();

			for (; item; item = item->GetNext())
			{
				PERTRACK &track_data(m_TrackData[internal_index]);
				track_data.original_id = item->GetData()->GetId();
				item->GetData()->SetId(track_data.new_id = internal_index + 1);

        if (AP4_MdhdAtom* mdhd = AP4_DYNAMIC_CAST(AP4_MdhdAtom, item->GetData()->FindChild("mdia/mdhd")))
          track_data.timescale = mdhd->GetTimeScale();
        else
          track_data.timescale = 1;
        
        AP4_ContainerAtom *mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov[streamid]->GetChild(AP4_ATOM_TYPE_MVEX, 0));
				if (!mvex)
					return AP4_ERROR_INVALID_FORMAT;
				
				if (!item->GetData()->GetDuration())
				{
					AP4_MehdAtom *mehd(AP4_DYNAMIC_CAST(AP4_MehdAtom, mvex->GetChild(AP4_ATOM_TYPE_MEHD, 0)));
					item->GetData()->SetDuration(mehd? mehd->GetDuration():0);
				}
				
				AP4_TrexAtom *trex(NULL);
				unsigned int index(0);
				for (; !trex && (trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, mvex->GetChild(AP4_ATOM_TYPE_TREX, index++)));)
					if(trex->GetTrackId() != track_data.original_id)
						trex = NULL;
				if (!trex)
					return AP4_ERROR_INVALID_FORMAT;

				if (mvex_base)
				{
					trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, trex->Clone());
					mvex_base->AddChild(trex);
				}
				else
					mvex_base = mvex;
				trex->SetTrackId(track_data.new_id);

				track_data.track_handler = CreateTrackHandler(item->GetData(), trex);
				track_data.track_handler->ProcessTrack();
				track_data.streamId = streamid;
				++m_StreamData[streamid].trackCount;
				++internal_index;
			}
		}
		// We don't need the other moovs anymore.....
		moov.SetItemCount(1);

		AP4_MvhdAtom *mvhd(AP4_DYNAMIC_CAST(AP4_MvhdAtom, moov[0]->GetChild(AP4_ATOM_TYPE_MVHD, 0)));
		if (!mvhd->GetDuration())
		{
			AP4_MehdAtom *mehd(AP4_DYNAMIC_CAST(AP4_MehdAtom, mvex_base->GetChild(AP4_ATOM_TYPE_MEHD, 0)));
			mvhd->SetDuration(mehd ? mehd->GetDuration() : 0);
		}

		// finalize the processor
		Finalize(top_level);

		// calculate the size of all atoms combined
		AP4_UI64 atoms_size = 0;
		top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size));

		// write all atoms
		top_level.GetChildren().Apply(AP4_AtomListWriter(output));
		m_MoovAtom = moov[0];
		m_MoovAtom->Detach();
	}

	if (partitions & 2)
	{
		// process the fragments, if any
		result = AP4_SUCCESS;
		AP4_Array<AP4_UI64> moof_positions, mdat_positions;
		moof_positions.SetItemCount(input.ItemCount());
		mdat_positions.SetItemCount(input.ItemCount());

		for (;;)
		{
			AP4_ContainerAtom *moof = NULL;
			AP4_UI32 track_index(0);
			
#if 0			
      for (AP4_Cardinal streamid(0); streamid < input.ItemCount(); ++streamid)
			{
				AP4_Atom* atom = NULL;
				if (AP4_SUCCEEDED(input[streamid]->Tell(stream_offset)) && AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*input[streamid], atom)))
				{
					if (atom->GetType() != AP4_ATOM_TYPE_MOOF)
						return AP4_ERROR_INVALID_FORMAT;
					
					moof_positions[streamid] = stream_offset;
					mdat_positions[streamid] = stream_offset + atom->GetSize() + +AP4_ATOM_HEADER_SIZE;

					if (moof)
					{
						int index(0);
						for (; AP4_Atom* child = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom)->GetChild(AP4_ATOM_TYPE_TRAF, index++);)
							moof->AddChild(child->Clone());
						delete atom;
					}
					else
						moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
					NormalizeTRAF(AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof), m_StreamData[streamid].trackStart,
						m_StreamData[streamid].trackStart + m_StreamData[streamid].trackCount, track_index);
				}
				else
					delete atom;
			}
#else
      double mindts(9999999999.0);
      AP4_Cardinal nextStream(~0);
      for (AP4_Cardinal track(0); track < m_TrackData.ItemCount(); ++track)
        if ((double)m_TrackData[track].dts / m_TrackData[track].timescale  < mindts)
        {
          mindts = (double)m_TrackData[track].dts / m_TrackData[track].timescale;
          nextStream = m_TrackData[track].streamId;
        }
      
      AP4_Atom* atom = NULL;
      if (AP4_SUCCEEDED(result = input[nextStream]->Tell(stream_offset)) && AP4_SUCCEEDED(result = atom_factory.CreateAtomFromStream(*input[nextStream], atom)))
      {
        if (atom->GetType() != AP4_ATOM_TYPE_MOOF)
          return AP4_ERROR_INVALID_FORMAT;
			} else if (atom)
				return result;

      moof_positions[nextStream] = stream_offset;
      mdat_positions[nextStream] = stream_offset + atom->GetSize() + +AP4_ATOM_HEADER_SIZE;

      moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
      NormalizeTRAF(AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof), m_StreamData[nextStream].trackStart,
        m_StreamData[nextStream].trackStart + m_StreamData[nextStream].trackCount, track_index);
#endif
			if (!moof)
				break;
			
			if (AP4_FAILED(result = ProcessFragment(moof, NULL, 0, output, moof_positions, mdat_positions)))
				return result;
			
			delete moof;
			moof = NULL;
		}

		// cleanup
		m_TrackData.Clear();
		m_StreamData.Clear();
	}

	return AP4_SUCCESS;
}
/*----------------------------------------------------------------------
|   AP4_Processor::Process
+---------------------------------------------------------------------*/
AP4_Result
AP4_Processor::Process(AP4_ByteStream&   input, 
                       AP4_ByteStream&   output,
                       AP4_ByteStream*   fragments,
                       ProgressListener* listener,
                       AP4_AtomFactory&  atom_factory)
{
    // read all atoms.
    // keep all atoms except [mdat]
    // keep a ref to [moov]
    // put [moof] atoms in a separate list
    AP4_AtomParent              top_level;
    AP4_MoovAtom*               moov = NULL;
    AP4_ContainerAtom*          mfra = NULL;
    AP4_SidxAtom*               sidx = NULL;
    AP4_List<AP4_AtomLocator>   frags;
    AP4_UI64                    stream_offset = 0;
    bool                        in_fragments = false;
    unsigned int                sidx_count = 0;
    for (AP4_Atom* atom = NULL;
        AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom));
        input.Tell(stream_offset)) {
        if (atom->GetType() == AP4_ATOM_TYPE_MDAT) {
            delete atom;
            continue;
        } else if (atom->GetType() == AP4_ATOM_TYPE_MOOV) {
            moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, atom);
            if (fragments) break;
        } else if (atom->GetType() == AP4_ATOM_TYPE_MFRA) {
            mfra = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
            continue;
        } else if (atom->GetType() == AP4_ATOM_TYPE_SIDX) {
            // don't keep the index, it is likely to be invalidated, we will recompute it later
            ++sidx_count;
            if (sidx == NULL) {
                sidx = AP4_DYNAMIC_CAST(AP4_SidxAtom, atom);
            } else {
                delete atom;
                continue;
            }
        } else if (atom->GetType() == AP4_ATOM_TYPE_SSIX) {
            // don't keep the index, it is likely to be invalidated
            delete atom;
            continue;
        } else if (!fragments && (in_fragments || atom->GetType() == AP4_ATOM_TYPE_MOOF)) {
            in_fragments = true;
            frags.Add(new AP4_AtomLocator(atom, stream_offset));
			break;
        }
        top_level.AddChild(atom);
    }

    // check that we have at most one sidx (we can't deal with multi-sidx streams here
    if (sidx_count > 1) {
        top_level.RemoveChild(sidx);
        delete sidx;
        sidx = NULL;
    }
    
    // if we have a fragments stream, get the fragment locators from there
    if (fragments) {
        stream_offset = 0;
        for (AP4_Atom* atom = NULL;
            AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*fragments, atom));
            fragments->Tell(stream_offset)) {
            if (atom->GetType() == AP4_ATOM_TYPE_MDAT) {
                delete atom;
                continue;
            }
            frags.Add(new AP4_AtomLocator(atom, stream_offset));
        }
    }
    
    // initialize the processor
    AP4_Result result = Initialize(top_level, input);
    if (AP4_FAILED(result)) return result;

    // process the tracks if we have a moov atom
    AP4_Array<AP4_SampleLocator> locators;
    AP4_Cardinal                 track_count       = 0;
    AP4_List<AP4_TrakAtom>*      trak_atoms        = NULL;
    AP4_LargeSize                mdat_payload_size = 0;
    AP4_SampleCursor*            cursors           = NULL;
    if (moov) {
        // build an array of track sample locators
        trak_atoms = &moov->GetTrakAtoms();
        track_count = trak_atoms->ItemCount();
        cursors = new AP4_SampleCursor[track_count];
		m_TrackData.SetItemCount(track_count);
		m_StreamData.SetItemCount(1);
		m_StreamData[0].stream = &input;

		unsigned int index = 0;
        for (AP4_List<AP4_TrakAtom>::Item* item = trak_atoms->FirstItem(); item; item=item->GetNext()) {
            AP4_TrakAtom* trak = item->GetData();

            // find the stsd atom
            AP4_ContainerAtom* stbl = AP4_DYNAMIC_CAST(AP4_ContainerAtom, trak->FindChild("mdia/minf/stbl"));
            if (stbl == NULL) continue;
            
            // see if there's an external data source for this track
            AP4_ByteStream* trak_data_stream = &input;
            for (AP4_List<ExternalTrackData>::Item* ditem = m_ExternalTrackData.FirstItem(); ditem; ditem=ditem->GetNext()) {
                ExternalTrackData* tdata = ditem->GetData();
                if (tdata->m_TrackId == trak->GetId()) {
                    trak_data_stream = tdata->m_MediaData;
                    break;
                }
            }
			AP4_ContainerAtom *mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov->GetChild(AP4_ATOM_TYPE_MVEX));
			AP4_TrexAtom*      trex = NULL;
			if (mvex) {
				for (AP4_List<AP4_Atom>::Item* item = mvex->GetChildren().FirstItem(); item; item = item->GetNext()) {
					AP4_Atom* atom = item->GetData();
					if (atom->GetType() == AP4_ATOM_TYPE_TREX) {
						trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, atom);
						if (trex && trex->GetTrackId() == trak->GetId()) 
							break;
						trex = NULL;
					}
				}
			}
			// create the track handler    
            m_TrackData[index].track_handler	= CreateTrackHandler(trak, trex);
			m_TrackData[index].new_id = trak->GetId();

			cursors[index].m_Locator.m_TrakIndex   = index;
            cursors[index].m_Locator.m_SampleTable = new AP4_AtomSampleTable(stbl, *trak_data_stream);
            cursors[index].m_Locator.m_SampleIndex = 0;
            cursors[index].m_Locator.m_ChunkIndex  = 0;
            if (cursors[index].m_Locator.m_SampleTable->GetSampleCount()) {
                cursors[index].m_Locator.m_SampleTable->GetSample(0, cursors[index].m_Locator.m_Sample);
            } else {
                cursors[index].m_EndReached = true;
            }

            index++;            
        }

        // figure out the layout of the chunks
        for (;;) {
            // see which is the next sample to write
            AP4_UI64 min_offset = (AP4_UI64)(-1);
            int cursor = -1;
            for (unsigned int i=0; i<track_count; i++) {
                if (!cursors[i].m_EndReached &&
                    cursors[i].m_Locator.m_Sample.GetOffset() <= min_offset) {
                    min_offset = cursors[i].m_Locator.m_Sample.GetOffset();
                    cursor = i;
                }
            }

            // stop if all cursors are exhausted
            if (cursor == -1) break;

            // append this locator to the layout list
            AP4_SampleLocator& locator = cursors[cursor].m_Locator;
            locators.Append(locator);

            // move the cursor to the next sample
            locator.m_SampleIndex++;
            if (locator.m_SampleIndex == locator.m_SampleTable->GetSampleCount()) {
                // mark this track as completed
                cursors[cursor].m_EndReached = true;
            } else {
                // get the next sample info
                locator.m_SampleTable->GetSample(locator.m_SampleIndex, locator.m_Sample);
                AP4_Ordinal skip, sdesc;
                locator.m_SampleTable->GetChunkForSample(locator.m_SampleIndex,
                                                         locator.m_ChunkIndex,
                                                         skip, sdesc);
            }
        }

        // update the stbl atoms and compute the mdat size
        int current_track = -1;
        int current_chunk = -1;
        AP4_Position current_chunk_offset = 0;
        AP4_Size current_chunk_size = 0;
        for (AP4_Ordinal i=0; i<locators.ItemCount(); i++) {
            AP4_SampleLocator& locator = locators[i];
            if ((int)locator.m_TrakIndex  != current_track ||
                (int)locator.m_ChunkIndex != current_chunk) {
                // start a new chunk for this track
                current_chunk_offset += current_chunk_size;
                current_chunk_size = 0;
                current_track = locator.m_TrakIndex;
                current_chunk = locator.m_ChunkIndex;
                locator.m_SampleTable->SetChunkOffset(locator.m_ChunkIndex, current_chunk_offset);
            } 
            AP4_Size sample_size;
            TrackHandler* handler = m_TrackData[locator.m_TrakIndex].track_handler;
            if (handler) {
                sample_size = handler->GetProcessedSampleSize(locator.m_Sample);
                locator.m_SampleTable->SetSampleSize(locator.m_SampleIndex, sample_size);
            } else {
                sample_size = locator.m_Sample.GetSize();
            }
            current_chunk_size += sample_size;
            mdat_payload_size  += sample_size;
        }

        // process the tracks (ex: sample descriptions processing)
        for (AP4_Ordinal i=0; i<track_count; i++) {
            TrackHandler* handler = m_TrackData[i].track_handler;
            if (handler)
				handler->ProcessTrack();
        }
    }

    // finalize the processor
    Finalize(top_level);

    if (!fragments) {
        // calculate the size of all atoms combined
        AP4_UI64 atoms_size = 0;
        top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size));

        // see if we need a 64-bit or 32-bit mdat
        AP4_Size mdat_header_size = AP4_ATOM_HEADER_SIZE;
        if (mdat_payload_size+mdat_header_size > 0xFFFFFFFF) {
            // we need a 64-bit size
            mdat_header_size += 8;
        }
        
        // adjust the chunk offsets
        for (AP4_Ordinal i=0; i<track_count; i++) {
            AP4_TrakAtom* trak;
            trak_atoms->Get(i, trak);
            trak->AdjustChunkOffsets(atoms_size+mdat_header_size);
        }

        // write all atoms
        top_level.GetChildren().Apply(AP4_AtomListWriter(output));

        // write mdat header
        if (mdat_payload_size) {
            if (mdat_header_size == AP4_ATOM_HEADER_SIZE) {
                // 32-bit size
                output.WriteUI32((AP4_UI32)(mdat_header_size+mdat_payload_size));
                output.WriteUI32(AP4_ATOM_TYPE_MDAT);
            } else {
                // 64-bit size
                output.WriteUI32(1);
                output.WriteUI32(AP4_ATOM_TYPE_MDAT);
                output.WriteUI64(mdat_header_size+mdat_payload_size);
            }
        }        
    }
    
    // write the samples
    if (moov) {
        if (!fragments) {
#if defined(AP4_DEBUG)
            AP4_Position before;
            output.Tell(before);
#endif
            AP4_Sample     sample;
            AP4_DataBuffer data_in;
            AP4_DataBuffer data_out;
            for (unsigned int i=0; i<locators.ItemCount(); i++) {
                AP4_SampleLocator& locator = locators[i];
                locator.m_Sample.ReadData(data_in);
                TrackHandler* handler = m_TrackData[locator.m_TrakIndex].track_handler;
                if (handler) {
                    result = handler->ProcessSample(data_in, data_out);
                    if (AP4_FAILED(result)) return result;
                    output.Write(data_out.GetData(), data_out.GetDataSize());
                } else {
                    output.Write(data_in.GetData(), data_in.GetDataSize());            
                }

                // notify the progress listener
                if (listener) {
                    listener->OnProgress(i+1, locators.ItemCount());
                }
            }

#if defined(AP4_DEBUG)
            AP4_Position after;
            output.Tell(after);
            AP4_ASSERT(after-before == mdat_payload_size);
#endif
		}
		else
			m_StreamData[0].stream = fragments;
        
        // find the position of the sidx atom
        AP4_Position sidx_position = 0;
		if (sidx) {
			for (AP4_List<AP4_Atom>::Item* item = top_level.GetChildren().FirstItem();
				item;
				item = item->GetNext()) {
				AP4_Atom* atom = item->GetData();
				if (atom->GetType() == AP4_ATOM_TYPE_SIDX) {
					break;
				}
				sidx_position += atom->GetSize();
			}
		}
        
        // process the fragments, if any
		AP4_Array<AP4_Position> moof_offsets, mdat_offsets;
		moof_offsets.SetItemCount(1);
		mdat_offsets.SetItemCount(1);

		while (frags.ItemCount() > 0)
		{
			for (AP4_List<AP4_AtomLocator>::Item *locator(frags.FirstItem()); locator; locator = locator->GetNext())
			{
				AP4_ContainerAtom *moof(AP4_DYNAMIC_CAST(AP4_ContainerAtom, locator->GetData()->m_Atom));
				moof_offsets[0] = locator->GetData()->m_Offset;
				mdat_offsets[0] = moof_offsets[0] + moof->GetSize() + AP4_ATOM_HEADER_SIZE;

				result = ProcessFragment(moof, sidx, sidx_position, output, moof_offsets, mdat_offsets);
				if (AP4_FAILED(result))
					return result;
			}
			frags.DeleteReferences();

			AP4_Atom* atom = NULL;
			input.Tell(stream_offset);
			if (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom)))
			{
				if (atom->GetType() == AP4_ATOM_TYPE_MOOF)
					frags.Add(new AP4_AtomLocator(atom, stream_offset));
				else
					delete atom;
			}
		}

		// update the mfra if we have one
		if (mfra) {
			for (AP4_List<AP4_Atom>::Item* mfra_item = mfra->GetChildren().FirstItem(); mfra_item; mfra_item = mfra_item->GetNext())
			{
				if (mfra_item->GetData()->GetType() != AP4_ATOM_TYPE_TFRA)
					continue;
				AP4_TfraAtom* tfra = AP4_DYNAMIC_CAST(AP4_TfraAtom, mfra_item->GetData());
				if (tfra == NULL)
					continue;
				AP4_Array<AP4_TfraAtom::Entry>& entries = tfra->GetEntries();
				AP4_Cardinal entry_count = entries.ItemCount();
				for (unsigned int i = 0; i<entry_count; i++) {
					entries[i].m_MoofOffset = FindFragmentMapEntry(entries[i].m_MoofOffset);
				}
			}
		}

        // update and re-write the sidx if we have one
        if (sidx && sidx_position) {
            AP4_Position where = 0;
            output.Tell(where);
            output.Seek(sidx_position);
            result = sidx->Write(output);
            if (AP4_FAILED(result)) return result;
            output.Seek(where);
        }
        
        if (!fragments) {
            // write the mfra atom at the end if we have one
            if (mfra) {
                mfra->Write(output);
            }
        }
        
        // cleanup
        for (AP4_Ordinal i=0; i<track_count; i++)
            delete cursors[i].m_Locator.m_SampleTable;
        m_TrackData.Clear();
        delete[] cursors;
    }

    // cleanup
    frags.DeleteReferences();
    delete mfra;
    
    return AP4_SUCCESS;
}
/*----------------------------------------------------------------------
|   AP4_Processor::ProcessFragments
+---------------------------------------------------------------------*/
AP4_Result
AP4_Processor::ProcessFragment( AP4_ContainerAtom*		    moof,
                                AP4_SidxAtom*			    sidx,
                                AP4_Position			    sidx_position,
                                AP4_ByteStream&			    output,
								AP4_Array<AP4_Position>&    moof_positions,
								AP4_Array<AP4_Position>&    mdat_positions)
{
	unsigned int fragment_index = 0;
    
    //AP4_UI64           mdat_payload_offset = atom_offset+atom->GetSize()+AP4_ATOM_HEADER_SIZE;
    AP4_Sample         sample;
    AP4_DataBuffer     sample_data_in;
    AP4_DataBuffer     sample_data_out;
    AP4_Result         result = AP4_SUCCESS;
        
    // parse the moof
    //AP4_MovieFragment* fragment = new AP4_MovieFragment(moof);

    // process all the traf atoms
    AP4_Array<AP4_Processor::FragmentHandler*> handlers;
    AP4_Array<AP4_FragmentSampleTable*> sample_tables;

	for (; AP4_Atom* child = moof->GetChild(AP4_ATOM_TYPE_TRAF, handlers.ItemCount());) {
		AP4_TrafAtom* traf = AP4_DYNAMIC_CAST(AP4_TrafAtom, child);

		PERTRACK &track_data(m_TrackData[traf->GetInternalTrackId()]);
		AP4_TrakAtom* trak = track_data.track_handler->GetTrakAtom();
		AP4_TrexAtom* trex = track_data.track_handler->GetTrexAtom();

		// create the handler for this traf
		AP4_Processor::FragmentHandler* handler = CreateFragmentHandler(trak, trex, traf,
			*(m_StreamData[track_data.streamId].stream),
			moof_positions[track_data.streamId]);
		if (handler) {
			result = handler->ProcessFragment();
			if (AP4_FAILED(result)) return result;
		}
		handlers.Append(handler);

		// create a sample table object so we can read the sample data
		AP4_FragmentSampleTable* sample_table = new AP4_FragmentSampleTable(
			traf,
			trex,
      traf->GetInternalTrackId(),
			m_StreamData[track_data.streamId].stream,
			moof_positions[traf->GetInternalTrackId()],
			mdat_positions[traf->GetInternalTrackId()],
			0);
		sample_tables.Append(sample_table);

		// let the handler look at the samples before we process them
		if (handler)
			result = handler->PrepareForSamples(sample_table);
		if (AP4_FAILED(result))
			return result;
	}
             
	output.Buffer();
		
	// write the moof
    AP4_UI64 moof_out_start = 0;
    output.Tell(moof_out_start);
    moof->Write(output);
        
    // remember the location of this fragment
	FragmentMapEntry map_entry = { moof_positions[0], moof_out_start };
    fragment_map_.Append(map_entry);

    // write an mdat header
    AP4_Position mdat_out_start;
    AP4_UI64 mdat_size = AP4_ATOM_HEADER_SIZE;
    output.Tell(mdat_out_start);
    output.WriteUI32(0);
    output.WriteUI32(AP4_ATOM_TYPE_MDAT);

    // process all track runs
    for (unsigned int i=0; i<handlers.ItemCount(); i++) {
        AP4_Processor::FragmentHandler* handler = handlers[i];

        // get the track ID
        AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof->GetChild(AP4_ATOM_TYPE_TRAF, i));
        if (traf == NULL) continue;
        AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD));
            
        // compute the base data offset
        AP4_UI64 base_data_offset;
        if (tfhd->GetFlags() & AP4_TFHD_FLAG_BASE_DATA_OFFSET_PRESENT) {
            base_data_offset = mdat_out_start+AP4_ATOM_HEADER_SIZE;
        } else {
            base_data_offset = moof_out_start;
        }
            
        // build a list of all trun atoms
        AP4_Array<AP4_TrunAtom*> truns;
        for (AP4_List<AP4_Atom>::Item* child_item = traf->GetChildren().FirstItem();
                                        child_item;
                                        child_item = child_item->GetNext()) {
            AP4_Atom* child_atom = child_item->GetData();
            if (child_atom->GetType() == AP4_ATOM_TYPE_TRUN) {
                AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, child_atom);
                truns.Append(trun);
            }
        }    
        AP4_Ordinal   trun_index        = 0;
        AP4_Ordinal   trun_sample_index = 0;
        AP4_TrunAtom* trun = truns[0];
        trun->SetDataOffset((AP4_SI32)((mdat_out_start+mdat_size)-base_data_offset));
            
        // write the mdat
        for (unsigned int j=0; j<sample_tables[i]->GetSampleCount(); j++, trun_sample_index++) {
            // advance the trun index if necessary
            if (trun_sample_index >= trun->GetEntries().ItemCount()) {
                trun = truns[++trun_index];
                trun->SetDataOffset((AP4_SI32)((mdat_out_start+mdat_size)-base_data_offset));
                trun_sample_index = 0;
            }
                
            // get the next sample
            result = sample_tables[i]->GetSample(j, sample);
            if (AP4_FAILED(result)) return result;
            sample.ReadData(sample_data_in);
                
            m_TrackData[sample_tables[i]->GetInteralTrackId()].dts = sample.GetDts();
            
            // process the sample data
            if (handler) {
                result = handler->ProcessSample(sample_data_in, sample_data_out);
                if (AP4_FAILED(result)) return result;

                // write the sample data
                result = output.Write(sample_data_out.GetData(), sample_data_out.GetDataSize());
                if (AP4_FAILED(result)) return result;

                // update the mdat size
                mdat_size += sample_data_out.GetDataSize();
                    
                // update the trun entry
                trun->UseEntries()[trun_sample_index].sample_size = sample_data_out.GetDataSize();
            } else {
                // write the sample data (unmodified)
                result = output.Write(sample_data_in.GetData(), sample_data_in.GetDataSize());
                if (AP4_FAILED(result)) return result;

                // update the mdat size
                mdat_size += sample_data_in.GetDataSize();
            }
        }

        if (handler) {
            // update the tfhd header
            if (tfhd->GetFlags() & AP4_TFHD_FLAG_BASE_DATA_OFFSET_PRESENT) {
                tfhd->SetBaseDataOffset(mdat_out_start+AP4_ATOM_HEADER_SIZE);
            }
            if (tfhd->GetFlags() & AP4_TFHD_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT) {
                tfhd->SetDefaultSampleSize(trun->GetEntries()[0].sample_size);
            }
                
            // give the handler a chance to update the atoms
            handler->FinishFragment();
        }
    }

    // update the mdat header
    AP4_Position mdat_out_end;
    output.Tell(mdat_out_end);
#if defined(AP4_DEBUG)
    AP4_ASSERT(mdat_out_end-mdat_out_start == mdat_size);
#endif
	if (AP4_FAILED(result = output.Seek(mdat_out_start)))
		return result;
    output.WriteUI32((AP4_UI32)mdat_size);
    output.Seek(mdat_out_end);
        
    // update the moof if needed
	if (AP4_FAILED(result = output.Seek(moof_out_start)))
		return result;
    moof->Write(output);
    output.Seek(mdat_out_end);
        
    // update the sidx if we have one
    if (sidx && fragment_index < sidx->GetReferences().ItemCount()) {
        if (fragment_index == 0) {
            sidx->SetFirstOffset(moof_out_start-(sidx_position+sidx->GetSize()));
        }
        AP4_LargeSize fragment_size = mdat_out_end-moof_out_start;
        AP4_SidxAtom::Reference& sidx_ref = sidx->UseReferences()[fragment_index];
        sidx_ref.m_ReferencedSize = (AP4_UI32)fragment_size;
    }
        
    // cleanup
    //delete fragment;
        
    for (unsigned int i=0; i<handlers.ItemCount(); i++) {
        delete handlers[i];
    }
    for (unsigned int i=0; i<sample_tables.ItemCount(); i++) {
        delete sample_tables[i];
    }
	if (AP4_FAILED(result = output.Flush()))
		return result;
    
    return AP4_SUCCESS;
}
Exemple #7
0
/*----------------------------------------------------------------------
|   Fragment
+---------------------------------------------------------------------*/
static void
Fragment(AP4_File&       input_file,
         AP4_ByteStream& output_stream,
         unsigned int    fragment_duration,
         AP4_UI32        timescale)
{
    AP4_Result result;
    
    AP4_Movie* input_movie = input_file.GetMovie();
    if (input_movie == NULL) {
        fprintf(stderr, "ERROR: no moov found in the input file\n");
        return;
    }

    // create the output file object
    AP4_Movie* output_movie = new AP4_Movie(1000);
    
    // create an mvex container
    AP4_ContainerAtom* mvex = new AP4_ContainerAtom(AP4_ATOM_TYPE_MVEX);
    AP4_MehdAtom* mehd = new AP4_MehdAtom(0); 
    mvex->AddChild(mehd);
    
    // create a cusor list to keep track of the tracks we will read from
    AP4_Array<TrackCursor*> cursors;
    
    // add an output track for each track in the input file
    for (AP4_List<AP4_Track>::Item* track_item = input_movie->GetTracks().FirstItem();
                                    track_item;
                                    track_item = track_item->GetNext()) {
        AP4_Track* track = track_item->GetData();
        TrackCursor* cursor = new TrackCursor();
        cursor->m_TrackId = track->GetId();
        cursor->m_Tfra->SetTrackId(track->GetId());
        cursors.Append(cursor);
                    
        // create a sample table (with no samples) to hold the sample description
        AP4_SyntheticSampleTable* sample_table = new AP4_SyntheticSampleTable();
        for (unsigned int i=0; i<track->GetSampleDescriptionCount(); i++) {
            AP4_SampleDescription* sample_description = track->GetSampleDescription(i);
            sample_table->AddSampleDescription(sample_description, false);
        }
        
        // create the track
        AP4_Track* output_track = new AP4_Track(track->GetType(),
                                                sample_table,
                                                cursor->m_TrackId,
                                                timescale?timescale:1000,
                                                AP4_ConvertTime(track->GetDuration(),
                                                                input_movie->GetTimeScale(),
                                                                timescale?timescale:1000),
                                                timescale?timescale:track->GetMediaTimeScale(),
                                                0,//track->GetMediaDuration(),
                                                track->GetTrackLanguage(),
                                                track->GetWidth(),
                                                track->GetHeight());
        output_movie->AddTrack(output_track);
        result = cursor->SetTrack(track);
        if (AP4_FAILED(result)) {
            fprintf(stderr, "ERROR: failed to read sample (%d)\n", result);
            return;
        }
                
        // add a trex entry to the mvex container
        AP4_TrexAtom* trex = new AP4_TrexAtom(cursor->m_TrackId,
                                              1,
                                              0,
                                              0,
                                              0);
        mvex->AddChild(trex);
    }
    
    if (cursors.ItemCount() == 0) {
        fprintf(stderr, "ERROR: no track found\n");
        return;
    }

    for (unsigned int i=0; i<cursors.ItemCount(); i++) {
        if (cursors[i]->m_Track->GetType() == AP4_Track::TYPE_VIDEO) {
            cursors[i]->m_TargetDuration = AP4_ConvertTime(fragment_duration>AP4_FRAGMENTER_FRAGMENT_DURATION_TOLERANCE ?
                                                           fragment_duration-AP4_FRAGMENTER_FRAGMENT_DURATION_TOLERANCE : 0,
                                                           1000,
                                                           cursors[i]->m_Track->GetMediaTimeScale());
        } else {
            cursors[i]->m_TargetDuration = AP4_ConvertTime(fragment_duration,
                                                           1000,
                                                           cursors[i]->m_Track->GetMediaTimeScale());
        }
    }
    
    // update the mehd duration
    mehd->SetDuration(output_movie->GetDuration());
    
    // the mvex container to the moov container
    output_movie->GetMoovAtom()->AddChild(mvex);
    
    // write the ftyp atom
    AP4_FtypAtom* ftyp = input_file.GetFileType();
    if (ftyp) {
        ftyp->Write(output_stream);
    }
                 
    // write the moov atom
    output_movie->GetMoovAtom()->Write(output_stream);
    
    // write all the fragments
    unsigned int sequence_number = 1;
    for(;;) {
        // select the next track to read from
        TrackCursor* cursor = NULL;
        AP4_UI64 min_dts = (AP4_UI64)(-1);
        for (unsigned int i=0; i<cursors.ItemCount(); i++) {
            if (cursors[i]->m_Eos) continue;
            AP4_UI64 dts = AP4_ConvertTime(cursors[i]->m_Sample.GetDts(),
                                           cursors[i]->m_Track->GetMediaTimeScale(),
                                           AP4_FRAGMENTER_BASE_TIMESCALE);
            if (dts < min_dts) {
                min_dts = dts;
                cursor = cursors[i];
            }
        }
        if (cursor == NULL) break; // all done
        
        // compute the target end for the segment
        cursor->m_EndDts = cursor->m_Sample.GetDts()+cursor->m_TargetDuration;
        
        // emit a fragment for the selected track
        if (Options.verbosity > 0) {
            printf("fragment: track ID %d ", cursor->m_Track->GetId());
        }

        // remember the time and position of this fragment
        AP4_Position moof_offset = 0;
        output_stream.Tell(moof_offset);
        cursor->m_Tfra->AddEntry(cursor->m_Timestamp, moof_offset);
        
        // decide which sample description index to use
        // (this is not very sophisticated, we only look at the sample description
        // index of the first sample in the group, which may not be correct. This
        // should be fixed later)
        unsigned int sample_desc_index = cursor->m_Sample.GetDescriptionIndex();
        unsigned int tfhd_flags = AP4_TFHD_FLAG_DEFAULT_BASE_IS_MOOF;
        if (sample_desc_index > 0) {
            tfhd_flags |= AP4_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT;
        }
        if (cursor->m_Track->GetType() == AP4_Track::TYPE_VIDEO) {
            tfhd_flags |= AP4_TFHD_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT;
        }
            
        // setup the moof structure
        AP4_ContainerAtom* moof = new AP4_ContainerAtom(AP4_ATOM_TYPE_MOOF);
        AP4_MfhdAtom* mfhd = new AP4_MfhdAtom(sequence_number++);
        moof->AddChild(mfhd);
        AP4_ContainerAtom* traf = new AP4_ContainerAtom(AP4_ATOM_TYPE_TRAF);
        AP4_TfhdAtom* tfhd = new AP4_TfhdAtom(tfhd_flags,
                                              cursor->m_TrackId,
                                              0,
                                              sample_desc_index+1,
                                              0,
                                              0,
                                              0);
        if (tfhd_flags & AP4_TFHD_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT) {
            tfhd->SetDefaultSampleFlags(0x1010000); // sample_is_non_sync_sample=1, sample_depends_on=1 (not I frame)
        }
        
        traf->AddChild(tfhd);
        AP4_TfdtAtom* tfdt = new AP4_TfdtAtom(1, cursor->m_Timestamp);
        traf->AddChild(tfdt);
        AP4_UI32 trun_flags = AP4_TRUN_FLAG_DATA_OFFSET_PRESENT     |
                              AP4_TRUN_FLAG_SAMPLE_DURATION_PRESENT |
                              AP4_TRUN_FLAG_SAMPLE_SIZE_PRESENT;
        AP4_UI32 first_sample_flags = 0;
        if (cursor->m_Track->GetType() == AP4_Track::TYPE_VIDEO) {
            trun_flags |= AP4_TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT;
            first_sample_flags = 0x2000000; // sample_depends_on=2 (I frame)
        }
        AP4_TrunAtom* trun = new AP4_TrunAtom(trun_flags, 0, first_sample_flags);
        
        traf->AddChild(trun);
        moof->AddChild(traf);
            
        // decide which samples go in this fragment
        AP4_Array<AP4_UI32>            sample_indexes;
        unsigned int                   sample_count = 0;
        AP4_Array<AP4_TrunAtom::Entry> trun_entries;
        AP4_UI32                       mdat_size = AP4_ATOM_HEADER_SIZE;
        for (;;) {
            // if we have one non-zero CTS delta, we'll need to express it
            if (cursor->m_Sample.GetCtsDelta()) {
                trun->SetFlags(trun->GetFlags() | AP4_TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT);
            }
            
            // add one sample
            trun_entries.SetItemCount(sample_count+1);
            AP4_TrunAtom::Entry& trun_entry = trun_entries[sample_count];
            trun_entry.sample_duration                = timescale?
                                                        (AP4_UI32)AP4_ConvertTime(cursor->m_Sample.GetDuration(),
                                                                                  cursor->m_Track->GetMediaTimeScale(),
                                                                                  timescale):
                                                        cursor->m_Sample.GetDuration();
            trun_entry.sample_size                    = cursor->m_Sample.GetSize();
            trun_entry.sample_composition_time_offset = timescale?
                                                        (AP4_UI32)AP4_ConvertTime(cursor->m_Sample.GetCtsDelta(),
                                                                                  cursor->m_Track->GetMediaTimeScale(),
                                                                                  timescale):
                                                        cursor->m_Sample.GetCtsDelta();
                        
            sample_indexes.SetItemCount(sample_count+1);
            sample_indexes[sample_count] = cursor->m_SampleIndex;
            mdat_size += trun_entry.sample_size;
            
            // next sample
            cursor->m_Timestamp += trun_entry.sample_duration;
            cursor->m_SampleIndex++;
            sample_count++;
            if (cursor->m_SampleIndex >= cursor->m_Track->GetSampleCount()) {
                cursor->m_Eos = true;

                AP4_UI64 end_dts = cursor->m_Sample.GetDts()+cursor->m_Sample.GetDuration();
                cursor->m_Sample.Reset();
                cursor->m_Sample.SetDts(end_dts);

                break;
            }
            result = cursor->m_Track->GetSample(cursor->m_SampleIndex, cursor->m_Sample);
            if (AP4_FAILED(result)) {
                cursor->m_Eos = true;

                AP4_UI64 end_dts = cursor->m_Sample.GetDts()+cursor->m_Sample.GetDuration();
                cursor->m_Sample.Reset();
                cursor->m_Sample.SetDts(end_dts);

                break;
            }
            if (cursor->m_Sample.IsSync()) {
                if (cursor->m_Sample.GetDts() >= cursor->m_EndDts) {
                    break; // done with this segment
                }
            }
        }
        if (Options.verbosity) {
            printf(" %d samples\n", sample_count);
        }
                
        // update moof and children
        trun->SetEntries(trun_entries);
        trun->SetDataOffset((AP4_UI32)moof->GetSize()+AP4_ATOM_HEADER_SIZE);
        
        // write moof
        moof->Write(output_stream);
        
        // write mdat
        output_stream.WriteUI32(mdat_size);
        output_stream.WriteUI32(AP4_ATOM_TYPE_MDAT);
        AP4_Sample     sample;
        AP4_DataBuffer sample_data;
        for (unsigned int i=0; i<sample_indexes.ItemCount(); i++) {
            result = cursor->m_Track->ReadSample(sample_indexes[i], sample, sample_data);
            if (AP4_FAILED(result)) {
                fprintf(stderr, "ERROR: failed to read sample %d (%d)\n", sample_indexes[i], result);
                return;
            }
            result = output_stream.Write(sample_data.GetData(), sample_data.GetDataSize());
            if (AP4_FAILED(result)) {
                fprintf(stderr, "ERROR: failed to write sample data (%d)\n", result);
                return;
            }
        }
        
        // cleanup
        delete moof;
    }
    
    // create an mfra container and write out the index
    AP4_ContainerAtom mfra(AP4_ATOM_TYPE_MFRA);
    for (unsigned int i=0; i<cursors.ItemCount(); i++) {
        mfra.AddChild(cursors[i]->m_Tfra);
        cursors[i]->m_Tfra = NULL;
    }
    AP4_MfroAtom* mfro = new AP4_MfroAtom((AP4_UI32)mfra.GetSize()+16);
    mfra.AddChild(mfro);
    result = mfra.Write(output_stream);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: failed to write 'mfra' (%d)\n", result);
        return;
    }
    
    // cleanup
    for (unsigned int i=0; i<cursors.ItemCount(); i++) {
        delete cursors[i];
    }
    delete output_movie;
}
/*----------------------------------------------------------------------
|   AP4_Processor::ProcessFragments
+---------------------------------------------------------------------*/
AP4_Result
AP4_Processor::ProcessFragments(AP4_MoovAtom*              moov, 
                                AP4_List<AP4_MoofLocator>& moofs, 
                                AP4_ContainerAtom*         mfra,
                                AP4_ByteStream&            input, 
                                AP4_ByteStream&            output)
{
    // FIXME: this only works for non-changing moofs 
 
    for (AP4_List<AP4_MoofLocator>::Item* item = moofs.FirstItem();
                                          item;
                                          item = item->GetNext()) {
        AP4_MoofLocator*   locator     = item->GetData();
        AP4_ContainerAtom* moof        = locator->m_Moof;
        AP4_UI64           moof_offset = locator->m_Offset;
        AP4_UI64           mdat_payload_offset = moof_offset+moof->GetSize()+8;
        AP4_MovieFragment* fragment    = new AP4_MovieFragment(moof);
        AP4_Sample         sample;
        AP4_DataBuffer     sample_data_in;
        AP4_DataBuffer     sample_data_out;
        AP4_Result         result;
    
        // process all the traf atoms
        AP4_Array<AP4_Processor::FragmentHandler*> handlers;
        for (;AP4_Atom* atom = moof->GetChild(AP4_ATOM_TYPE_TRAF, handlers.ItemCount());) {
            AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
            AP4_Processor::FragmentHandler* handler = CreateFragmentHandler(traf);
            if (handler) result = handler->ProcessFragment();
            handlers.Append(handler);
        }
             
        // write the moof
        AP4_UI64 moof_out_start = 0;
        output.Tell(moof_out_start);
        bool moof_has_changed = false;
        moof->Write(output);
            
        // process all track runs
        for (unsigned int i=0; i<handlers.ItemCount(); i++) {
            AP4_FragmentSampleTable* sample_table = NULL;
            AP4_Processor::FragmentHandler* handler = handlers[i];

            // get the track ID
            AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof->GetChild(AP4_ATOM_TYPE_TRAF, i));
            AP4_TfhdAtom* tfhd      = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD, i));
            
            // create a sample table object so we can read the sample data
            result = fragment->CreateSampleTable(moov, tfhd->GetTrackId(), &input, moof_offset, mdat_payload_offset, sample_table);
            if (AP4_FAILED(result)) return result;

            // compute the mdat size
            AP4_UI64 mdat_size = 0;
            for (unsigned int j=0; j<sample_table->GetSampleCount(); j++) {
                result = sample_table->GetSample(j, sample);
                if (AP4_FAILED(result)) return result;
                mdat_size += sample.GetSize();
            }
            
            // write an mdat header
            if (mdat_size > 0xFFFFFFFF-8) {
                // we don't support large mdat fragments
                return AP4_ERROR_OUT_OF_RANGE;
            }
            if (mdat_size) {
                output.WriteUI32((AP4_UI32)(8+mdat_size));
                output.WriteUI32(AP4_ATOM_TYPE_MDAT);
            }
            
#if defined(AP4_DEBUG)
            AP4_Position before;
            output.Tell(before);
#endif
            
            // write the mdat
            for (unsigned int j=0; j<sample_table->GetSampleCount(); j++) {
                result = sample_table->GetSample(j, sample);
                if (AP4_FAILED(result)) return result;
                sample.ReadData(sample_data_in);
                
                // process the sample data
                if (handler) {
                    result = handler->ProcessSample(sample_data_in, sample_data_out);
                    if (AP4_FAILED(result)) return result;

                    // write the sample data
                    result = output.Write(sample_data_out.GetData(), sample_data_out.GetDataSize());
                    if (AP4_FAILED(result)) return result;

                    // give the handler a chance to update the atoms
                    result = handler->FinishFragment();
                    if (AP4_SUCCEEDED(result)) moof_has_changed = true;
                } else {
                    // write the sample data (unmodified)
                    result = output.Write(sample_data_in.GetData(), sample_data_in.GetDataSize());
                    if (AP4_FAILED(result)) return result;
                }
            }
            
#if defined(AP4_DEBUG)
            AP4_Position after;
            output.Tell(after);
            AP4_ASSERT(after-before == mdat_size);
#endif
            delete sample_table;
        }
        
        // update the moof if needed
        AP4_UI64 mdat_out_end = 0;
        output.Tell(mdat_out_end);
        if (moof_has_changed) {
            output.Seek(moof_out_start);
            moof->Write(output);
            output.Seek(mdat_out_end);
        }
                
        // update the mfra if we have one
        if (mfra) {
            for (AP4_List<AP4_Atom>::Item* mfra_item = mfra->GetChildren().FirstItem();
                                           mfra_item;
                                           mfra_item = mfra_item->GetNext()) {
                if (mfra_item->GetData()->GetType() != AP4_ATOM_TYPE_TFRA) continue;
                AP4_TfraAtom* tfra = AP4_DYNAMIC_CAST(AP4_TfraAtom, mfra_item->GetData());
                if (tfra == NULL) continue;
                AP4_Array<AP4_TfraAtom::Entry>& entries     = tfra->GetEntries();
                AP4_Cardinal                    entry_count = entries.ItemCount();
                for (unsigned int i=0; i<entry_count; i++) {
                    if (entries[i].m_MoofOffset == locator->m_Offset) {
                        entries[i].m_MoofOffset = moof_out_start;
                    }
                }
            }
        }

        delete fragment;
    }
        
    return AP4_SUCCESS;
}
Exemple #9
0
/*----------------------------------------------------------------------
|       AP4_Processor::Process
+---------------------------------------------------------------------*/
AP4_Result
AP4_Processor::Process(AP4_ByteStream&  input, 
                       AP4_ByteStream&  output,
                       AP4_AtomFactory& atom_factory)
{
    // read all atoms 
    AP4_AtomParent top_level;
    AP4_Atom* atom;
    while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom))) {
        top_level.AddChild(atom);
    }

    // remove the [mdat] and [free] atoms, keep a ref to [moov]
    AP4_MoovAtom* moov = NULL;
    AP4_List<AP4_Atom>::Item* atom_item = top_level.GetChildren().FirstItem();
    while (atom_item) {
        atom = atom_item->GetData();
        AP4_List<AP4_Atom>::Item* next = atom_item->GetNext();
        if (//atom->GetType() == AP4_ATOM_TYPE_FREE ||
            atom->GetType() == AP4_ATOM_TYPE_MDAT) {
            atom->Detach();
            delete atom;
        } else if (atom->GetType() == AP4_ATOM_TYPE_MOOV) {
            moov = (AP4_MoovAtom*)atom;
        }
        atom_item = next;
    }

    // check that we have a moov atom
    if (moov == NULL) return AP4_FAILURE;

    // initialize the processor
    AP4_Result result = Initialize(top_level);
    if (AP4_FAILED(result)) return result;

    // build an array of track sample cursors
    AP4_List<AP4_TrakAtom>& trak_atoms = moov->GetTrakAtoms();
    AP4_Cardinal track_count = trak_atoms.ItemCount();
    AP4_SampleCursor* cursors = new AP4_SampleCursor[track_count];
    TrackHandler** handlers = new TrackHandler*[track_count];
    AP4_List<AP4_TrakAtom>::Item* item = trak_atoms.FirstItem();
    unsigned int index = 0;
    while (item) {
        // create the track handler    // find the stsd atom
        AP4_ContainerAtom* stbl = dynamic_cast<AP4_ContainerAtom*>(
            item->GetData()->FindChild("mdia/minf/stbl"));
        if (stbl == NULL) continue;
        handlers[index] = CreateTrackHandler(item->GetData());
        cursors[index].m_Locator.m_TrakIndex = index;
        cursors[index].m_Locator.m_SampleTable = new AP4_AtomSampleTable(stbl, input);
        cursors[index].m_Locator.m_SampleIndex = 0;
        cursors[index].m_Locator.m_SampleTable->GetSample(0, cursors[index].m_Locator.m_Sample);
        cursors[index].m_Locator.m_Chunk = 1;
        index++;
        item = item->GetNext();
    }

    // figure out the layout of the chunks
    AP4_Array<AP4_SampleLocator> locators;
    for (;;) {
        // see which is the next sample to write
        unsigned int min_offset = 0xFFFFFFFF;
        int cursor = -1;
        for (unsigned int i=0; i<track_count; i++) {
            if (cursors[i].m_Locator.m_SampleTable &&
                cursors[i].m_Locator.m_Sample.GetOffset() <= min_offset) {
                    min_offset = cursors[i].m_Locator.m_Sample.GetOffset();
                    cursor = i;
            }
        }

        // stop if all cursors are exhausted
        if (cursor == -1) break;

        // append this locator to the layout list
        AP4_SampleLocator& locator = cursors[cursor].m_Locator;
        locators.Append(locator);
        //AP4_Debug("NEXT: track %d, sample %d:%d: offset=%d, size=%d\n",
        //    locator.m_TrakIndex, 
        //    locator.m_Chunk,
        //    locator.m_SampleIndex,
        //    locator.m_Sample.GetOffset(),
        //    locator.m_Sample.GetSize());

        // move the cursor to the next sample
        locator.m_SampleIndex++;
        if (locator.m_SampleIndex == locator.m_SampleTable->GetSampleCount()) {
            // mark this track as completed
            locator.m_SampleTable = NULL;
        } else {
            // get the next sample info
            locator.m_SampleTable->GetSample(locator.m_SampleIndex, 
                locator.m_Sample);
            AP4_Ordinal skip, sdesc;
            locator.m_SampleTable->GetChunkForSample(locator.m_SampleIndex+1, // the internal API is 1-based
                locator.m_Chunk,
                skip, sdesc);
        }
    }

    // update the stbl atoms and compute the mdat size
    AP4_Size mdat_size = 0;
    int current_track  = -1;
    int current_chunk  = -1;
    AP4_Offset current_chunk_offset = 0;
    AP4_Size current_chunk_size = 0;
    for (AP4_Ordinal i=0; i<locators.ItemCount(); i++) {
        AP4_SampleLocator& locator = locators[i];
        if ((int)locator.m_TrakIndex != current_track ||
            (int)locator.m_Chunk     != current_chunk) {
            // start a new chunk for this track
            current_chunk_offset += current_chunk_size;
            current_chunk_size = 0;
            current_track = locator.m_TrakIndex;
            current_chunk = locator.m_Chunk;
            locator.m_SampleTable->SetChunkOffset(locator.m_Chunk, 
                current_chunk_offset);
        } 
        AP4_Size sample_size;
        TrackHandler* handler = handlers[locator.m_TrakIndex];
        if (handler) {
            sample_size = handler->GetProcessedSampleSize(locator.m_Sample);
            locator.m_SampleTable->SetSampleSize(locator.m_SampleIndex+1, sample_size);
        } else {
            sample_size = locator.m_Sample.GetSize();
        }
        current_chunk_size += sample_size;
        mdat_size += sample_size;
    }

    // process the tracks (ex: sample descriptions processing)
    for (AP4_Ordinal i=0; i<track_count; i++) {
        TrackHandler* handler = handlers[i];
        if (handler) handler->ProcessTrack();
    }

    // initialize the processor
    Finalize(top_level);

    // calculate the size of all atoms combined
    AP4_Size atoms_size = 0;
    top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size));

    // adjust the chunk offsets
    for (AP4_Ordinal i=0; i<track_count; i++) {
        AP4_TrakAtom* trak;
        trak_atoms.Get(i, trak);
        trak->AdjustChunkOffsets(atoms_size+AP4_ATOM_HEADER_SIZE);
    }

    // write all atoms
    top_level.GetChildren().Apply(AP4_AtomListWriter(output));

    // write mdat header
    output.WriteUI32(mdat_size+AP4_ATOM_HEADER_SIZE);
    output.WriteUI32(AP4_ATOM_TYPE_MDAT);

#if defined(AP4_DEBUG)
    AP4_Offset before;
    output.Tell(before);
#endif

    // write the samples
    AP4_Sample sample;
    AP4_DataBuffer data_in;
    AP4_DataBuffer data_out;
    for (unsigned int i=0; i<locators.ItemCount(); i++) {
        AP4_SampleLocator& locator = locators[i];
        locator.m_Sample.ReadData(data_in);
        TrackHandler* handler = handlers[locator.m_TrakIndex];
        if (handler) {
            handler->ProcessSample(data_in, data_out);
            output.Write(data_out.GetData(), data_out.GetDataSize());
        } else {
            output.Write(data_in.GetData(), data_in.GetDataSize());            
        }
    }

#if defined(AP4_DEBUG)
    AP4_Offset after;
    output.Tell(after);
    AP4_ASSERT(after-before == mdat_size);
#endif

    // cleanup
    delete[] cursors;
    for (unsigned int i=0; i<track_count; i++) {
        delete handlers[i];
    }
    delete[] handlers;

    return AP4_SUCCESS;
}