/*----------------------------------------------------------------------
|   AP4_FragmentSampleTable::AP4_FragmentSampleTable
+---------------------------------------------------------------------*/
AP4_FragmentSampleTable::AP4_FragmentSampleTable(AP4_ContainerAtom* traf,
        AP4_TrexAtom*      trex,
        AP4_Cardinal       internal_track_id,
        AP4_ByteStream*    sample_stream,
        AP4_Position       moof_offset,
        AP4_Position       mdat_payload_offset,
        AP4_UI64           dts_origin)
    :m_InternalTrackId(internal_track_id)
{
    AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD));
    if (tfhd == NULL) return;

    // count all the samples and reserve space for them
    unsigned int sample_count = 0;
    for (AP4_List<AP4_Atom>::Item* item = traf->GetChildren().FirstItem();
            item;
            item = item->GetNext()) {
        AP4_Atom* atom = item->GetData();
        if (atom->GetType() == AP4_ATOM_TYPE_TRUN) {
            AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, atom);
            if (trun) sample_count += trun->GetEntries().ItemCount();
        }
    }
    m_Samples.EnsureCapacity(sample_count);

    // check if we have a timecode base
    AP4_TfdtAtom* tfdt = AP4_DYNAMIC_CAST(AP4_TfdtAtom, traf->GetChild(AP4_ATOM_TYPE_TFDT));
    if (tfdt) {
        dts_origin = tfdt->GetBaseMediaDecodeTime();
    }

    // process all the trun atoms
    for (AP4_List<AP4_Atom>::Item* item = traf->GetChildren().FirstItem();
            item;
            item = item->GetNext()) {
        AP4_Atom* atom = item->GetData();
        if (atom->GetType() == AP4_ATOM_TYPE_TRUN) {
            AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, atom);
            if (trun) {
                AP4_Result result = AddTrun(trun,
                                            tfhd,
                                            trex,
                                            sample_stream,
                                            moof_offset,
                                            mdat_payload_offset,
                                            dts_origin);
                if (AP4_FAILED(result)) return;
            }
        }
    }
}
Exemple #2
0
/*----------------------------------------------------------------------
|   AP4_File::AP4_File
+---------------------------------------------------------------------*/
AP4_File::AP4_File(AP4_ByteStream&  stream, 
                   AP4_AtomFactory& atom_factory,
                   bool             moov_only) :
    m_Movie(NULL),
    m_FileType(NULL),
    m_MetaData(NULL),
    m_MoovIsBeforeMdat(true)
{
    // parse top-level atoms
    AP4_Atom*    atom;
    AP4_Position stream_position;
    bool         keep_parsing = true;
    while (keep_parsing &&
           AP4_SUCCEEDED(stream.Tell(stream_position)) && 
           AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(stream, atom))) {
        AddChild(atom);
        switch (atom->GetType()) {
            case AP4_ATOM_TYPE_MOOV:
                m_Movie = new AP4_Movie(AP4_DYNAMIC_CAST(AP4_MoovAtom, atom), stream, false);
                if (moov_only) keep_parsing = false;
                break;

            case AP4_ATOM_TYPE_FTYP:
                m_FileType = AP4_DYNAMIC_CAST(AP4_FtypAtom, atom);
                break;

            case AP4_ATOM_TYPE_MDAT:
                // see if we are before the moov atom
                if (m_Movie == NULL) m_MoovIsBeforeMdat = false;
                break;
        }
    }
}
Exemple #3
0
/*----------------------------------------------------------------------
|       AP4_File::AP4_File
+---------------------------------------------------------------------*/
AP4_File::AP4_File(AP4_ByteStream& stream, AP4_AtomFactory& atom_factory) :
    m_Movie(NULL)
{
    // get all atoms
    AP4_Atom* atom;
    while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(stream, atom))) {
        switch (atom->GetType()) {
        case AP4_ATOM_TYPE_MOOV:
            m_Movie = new AP4_Movie(dynamic_cast<AP4_MoovAtom*>(atom),
                                    stream);
            break;
        case AP4_ATOM_TYPE_MOOF:
            if (m_Movie) {
                m_Movie->ProcessMoof(AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom),
                                     stream);
            }
            delete atom;
            break;
        case AP4_ATOM_TYPE_FTYP:
            //m_Movie = new AP4_Movie(dynamic_cast<AP4_FtypAtom*>(atom),  stream);
            m_FileType = dynamic_cast<AP4_FtypAtom*>(atom);
        default:
            m_OtherAtoms.Add(atom);
        }
    }
}
Exemple #4
0
/*----------------------------------------------------------------------
|   AP4_OmaDcfAtomDecrypter::DecryptAtoms
+---------------------------------------------------------------------*/
AP4_Result
AP4_OmaDcfAtomDecrypter::DecryptAtoms(AP4_AtomParent&                  atoms,
                                      AP4_Processor::ProgressListener* /*listener*/,
                                      AP4_BlockCipherFactory*          block_cipher_factory,
                                      AP4_ProtectionKeyMap&            key_map)
{
    // default factory
    if (block_cipher_factory == NULL) {
        block_cipher_factory = &AP4_DefaultBlockCipherFactory::Instance;
    }

    unsigned int index = 1;
    for (AP4_List<AP4_Atom>::Item* item = atoms.GetChildren().FirstItem();
            item;
            item = item->GetNext()) {
        AP4_Atom* atom = item->GetData();
        if (atom->GetType() != AP4_ATOM_TYPE_ODRM) continue;

        // check that we have the key
        const AP4_DataBuffer* key = key_map.GetKey(index++);
        if (key == NULL) return AP4_ERROR_INVALID_PARAMETERS;

        // check that we have all the atoms we need
        AP4_ContainerAtom* odrm = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
        if (odrm == NULL) continue; // not enough info
        AP4_OdheAtom* odhe = AP4_DYNAMIC_CAST(AP4_OdheAtom, odrm->GetChild(AP4_ATOM_TYPE_ODHE));
        if (odhe == NULL) continue; // not enough info
        AP4_OddaAtom* odda = AP4_DYNAMIC_CAST(AP4_OddaAtom, odrm->GetChild(AP4_ATOM_TYPE_ODDA));;
        if (odda == NULL) continue; // not enough info
        AP4_OhdrAtom* ohdr = AP4_DYNAMIC_CAST(AP4_OhdrAtom, odhe->GetChild(AP4_ATOM_TYPE_OHDR));
        if (ohdr == NULL) continue; // not enough info

        // do nothing if the atom is not encrypted
        if (ohdr->GetEncryptionMethod() == AP4_OMA_DCF_ENCRYPTION_METHOD_NULL) {
            continue;
        }

        // create the byte stream
        AP4_ByteStream* cipher_stream = NULL;
        AP4_Result result = CreateDecryptingStream(*odrm,
                            key->GetData(),
                            key->GetDataSize(),
                            block_cipher_factory,
                            cipher_stream);
        if (AP4_SUCCEEDED(result)) {
            // replace the odda atom's payload with the decrypted stream
            odda->SetEncryptedPayload(*cipher_stream, ohdr->GetPlaintextLength());
            cipher_stream->Release();

            // the atom will now be in the clear
            ohdr->SetEncryptionMethod(AP4_OMA_DCF_ENCRYPTION_METHOD_NULL);
            ohdr->SetPaddingScheme(AP4_OMA_DCF_PADDING_SCHEME_NONE);
        }
    }

    return AP4_SUCCESS;
}
Exemple #5
0
/*----------------------------------------------------------------------
|   AP4_LinearReader::AdvanceFragment
+---------------------------------------------------------------------*/
AP4_Result
AP4_LinearReader::AdvanceFragment()
{
    AP4_Result result;
     
    // go the the start of the next fragment
    result = m_FragmentStream->Seek(m_NextFragmentPosition);
    if (AP4_FAILED(result)) return result;

    // read atoms until we find a moof
    assert(m_HasFragments);
    if (!m_FragmentStream) return AP4_ERROR_INVALID_STATE;
    do {
        AP4_Atom* atom = NULL;
        result = AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(*m_FragmentStream, atom);
        if (AP4_SUCCEEDED(result)) {
            if (atom->GetType() == AP4_ATOM_TYPE_MOOF) {
                AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
                if (moof) {
                    // remember where we are in the stream
                    AP4_Position position = 0;
                    m_FragmentStream->Tell(position);
        
                    // process the movie fragment
                    result = ProcessMoof(moof, position-atom->GetSize(), position+8);
                    if (AP4_FAILED(result)) return result;

                    // compute where the next fragment will be
                    AP4_UI32 size;
                    AP4_UI32 type;
                    m_FragmentStream->Tell(position);
                    result = m_FragmentStream->ReadUI32(size);
                    if (AP4_FAILED(result)) return AP4_SUCCESS; // can't read more
                    result = m_FragmentStream->ReadUI32(type);
                    if (AP4_FAILED(result)) return AP4_SUCCESS; // can't read more
                    if (size == 0) {
                        m_NextFragmentPosition = 0;
                    } else if (size == 1) {
                        AP4_UI64 size_64 = 0;
                        result = m_FragmentStream->ReadUI64(size_64);
                        if (AP4_FAILED(result)) return AP4_SUCCESS; // can't read more
                        m_NextFragmentPosition = position+size_64;
                    } else {
                        m_NextFragmentPosition = position+size;
                    }
                    return AP4_SUCCESS;
                } else {
                    delete atom;
                }
            } else {
                delete atom;
            }            
        }
    } while (AP4_SUCCEEDED(result));
        
    return AP4_ERROR_EOS;
}
/*----------------------------------------------------------------------
|   AutoDetectAudioFragmentDuration
+---------------------------------------------------------------------*/
static unsigned int 
AutoDetectAudioFragmentDuration(AP4_ByteStream& stream, TrackCursor* cursor)
{
    // remember where we are in the stream
    AP4_Position where = 0;
    stream.Tell(where);
    AP4_LargeSize stream_size = 0;
    stream.GetSize(stream_size);
    AP4_LargeSize bytes_available = stream_size-where;
    
    AP4_UI64  fragment_count = 0;
    AP4_UI32  last_fragment_size = 0;
    AP4_Atom* atom = NULL;
    while (AP4_SUCCEEDED(AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(stream, bytes_available, atom))) {
        if (atom && atom->GetType() == AP4_ATOM_TYPE_MOOF) {
            AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
            AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, moof->FindChild("traf/tfhd"));
            if (tfhd && tfhd->GetTrackId() == cursor->m_Track->GetId()) {
                ++fragment_count;
                AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, moof->FindChild("traf/trun"));
                if (trun) {
                    last_fragment_size = trun->GetEntries().ItemCount();
                }
            }
        }
        delete atom;
        atom = NULL;
    }
    
    // restore the stream to its original position
    stream.Seek(where);
    
    // decide if we can infer an fragment size
    if (fragment_count == 0 || cursor->m_Samples->GetSampleCount() == 0) {
        return 0;
    }
    // don't count the last fragment if we have more than one
    if (fragment_count > 1 && last_fragment_size) {
        --fragment_count;
    }
    if (fragment_count <= 1 || cursor->m_Samples->GetSampleCount() < last_fragment_size) {
        last_fragment_size = 0;
    }
    AP4_Sample sample;
    AP4_UI64 total_duration = 0;
    for (unsigned int i=0; i<cursor->m_Samples->GetSampleCount()-last_fragment_size; i++) {
        cursor->m_Samples->GetSample(i, sample);
        total_duration += sample.GetDuration();
    }
    return (unsigned int)AP4_ConvertTime(total_duration/fragment_count, cursor->m_Track->GetMediaTimeScale(), 1000);
}
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc != 2) {
        PrintUsageAndExit();
    }
    const char* input_filename  = argv[1];
    
    // open the input
    AP4_ByteStream* input = NULL;
    AP4_Result result = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open input file (%s)\n", input_filename);
        return 1;
    }
        
    // get the movie
    AP4_File* file = new AP4_File(*input, AP4_DefaultAtomFactory::Instance, true);
    AP4_Movie* movie = file->GetMovie();
    
    AP4_Atom* atom = NULL;
    do {
        // process the next atom
        result = AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(*input, atom);
        if (AP4_SUCCEEDED(result)) {
            printf("atom size=%lld\n", atom->GetSize());
            if (atom->GetType() == AP4_ATOM_TYPE_MOOF) {
                AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
                if (moof) {
                    // remember where we are in the stream
                    AP4_Position position = 0;
                    input->Tell(position);
        
                    // process the movie fragment
                    ProcessMoof(movie, moof, input, position-atom->GetSize(), position+8);

                    // go back to where we were before processing the fragment
                    input->Seek(position);
                }
            } else {
                delete atom;
            }            
        }
    } while (AP4_SUCCEEDED(result));
    
    // cleanup
    delete file;
    input->Release();

    return 0;                                            
}
Exemple #8
0
/*----------------------------------------------------------------------
|       AP4_File::AP4_File
+---------------------------------------------------------------------*/
AP4_File::AP4_File(AP4_ByteStream& stream, AP4_AtomFactory& atom_factory) :
    m_Movie(NULL)
{
    // get all atoms
    AP4_Atom* atom;
    while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(stream, atom))) {
        switch (atom->GetType()) {
            case AP4_ATOM_TYPE_MOOV:
			case AP4_ATOM_TYPE_3DVF:
                m_Movie = DNew AP4_Movie(dynamic_cast<AP4_MoovAtom*>(atom),
                                        stream);
                break;
            case AP4_ATOM_TYPE_FTYP:
                //m_Movie = DNew AP4_Movie(dynamic_cast<AP4_FtypAtom*>(atom),  stream);
                m_FileType = dynamic_cast<AP4_FtypAtom*>(atom);
            default:
                m_OtherAtoms.Add(atom);
        }
    }
}
Exemple #9
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;
}
Exemple #10
0
/*----------------------------------------------------------------------
|   AP4_MarlinIpmpParser:Parse
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpParser::Parse(AP4_AtomParent&      top_level, 
                            AP4_ByteStream&      stream,
                            AP4_List<SinfEntry>& sinf_entries,
                            bool                 remove_od_data)
{
    // check the file type
    AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
    if (ftyp == NULL ||
        (ftyp->GetMajorBrand() != AP4_MARLIN_BRAND_MGSV && !ftyp->HasCompatibleBrand(AP4_MARLIN_BRAND_MGSV))) {
        return AP4_ERROR_INVALID_FORMAT;
    }
    
    // check the initial object descriptor and get the OD Track ID
    AP4_IodsAtom* iods = AP4_DYNAMIC_CAST(AP4_IodsAtom, top_level.FindChild("moov/iods"));
    AP4_UI32      od_track_id = 0;
    if (iods == NULL) return AP4_ERROR_INVALID_FORMAT;
    const AP4_ObjectDescriptor* od = iods->GetObjectDescriptor();
    if (od == NULL) return AP4_ERROR_INVALID_FORMAT;
    AP4_EsIdIncDescriptor* es_id_inc = AP4_DYNAMIC_CAST(AP4_EsIdIncDescriptor, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_ES_ID_INC));
    if (es_id_inc == NULL) return AP4_ERROR_INVALID_FORMAT;
    od_track_id = es_id_inc->GetTrackId();
    
    // find the track pointed to by the descriptor
    AP4_MoovAtom* moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV));
    if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
    AP4_TrakAtom* od_trak = NULL;
    AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
    while (trak_item) {
        AP4_TrakAtom* trak = trak_item->GetData();
        if (trak) {
            if (trak->GetId() == od_track_id) {
                od_trak = trak;
            } else {
                sinf_entries.Add(new SinfEntry(trak->GetId(), NULL));
            }
        }
        trak_item = trak_item->GetNext();
    }

    // check that we have found the OD track 
    if (od_trak == NULL) return AP4_ERROR_INVALID_FORMAT;

    // look for the 'mpod' trak references
    AP4_TrefTypeAtom* track_references;
    track_references = AP4_DYNAMIC_CAST(AP4_TrefTypeAtom, od_trak->FindChild("tref/mpod"));
    if (track_references == NULL) return AP4_ERROR_INVALID_FORMAT;

    // create an AP4_Track object from the trak atom and check that it has samples
    AP4_Track* od_track = new AP4_Track(*od_trak, stream, 0);
    if (od_track->GetSampleCount() < 1) {
        delete od_track;
        return AP4_ERROR_INVALID_FORMAT;
    }
    
    // get the first sample (in this version, we only look at a single OD command)
    AP4_Sample od_sample;
    AP4_Result result = od_track->GetSample(0, od_sample);
    if (AP4_FAILED(result)) {
        delete od_track;
        return AP4_ERROR_INVALID_FORMAT;
    }
    
    // adapt the sample data into a byte stream for parsing
    AP4_DataBuffer sample_data;
    od_sample.ReadData(sample_data);
    AP4_MemoryByteStream* sample_stream = new AP4_MemoryByteStream(sample_data);
    
    // look for one ObjectDescriptorUpdate command and 
    // one IPMP_DescriptorUpdate command
    AP4_DescriptorUpdateCommand* od_update = NULL;
    AP4_DescriptorUpdateCommand* ipmp_update = NULL;
    do {
        AP4_Command* command = NULL;
        result = AP4_CommandFactory::CreateCommandFromStream(*sample_stream, command);
        if (AP4_SUCCEEDED(result)) {
            // found a command in the sample, check the type
            switch (command->GetTag()) {
              case AP4_COMMAND_TAG_OBJECT_DESCRIPTOR_UPDATE:
                if (od_update == NULL) {
                    od_update = AP4_DYNAMIC_CAST(AP4_DescriptorUpdateCommand, command);
                }
                break;
                
              case AP4_COMMAND_TAG_IPMP_DESCRIPTOR_UPDATE:
                if (ipmp_update == NULL) {
                    ipmp_update = AP4_DYNAMIC_CAST(AP4_DescriptorUpdateCommand, command);
                }
                break;

              default:
                break;
            }
        }
    } while (AP4_SUCCEEDED(result));
    sample_stream->Release();
    sample_stream = NULL;
    
    // check that we have what we need
    if (od_update == NULL || ipmp_update == NULL) {
        delete od_track;
        return AP4_ERROR_INVALID_FORMAT;
    }
        
    // process all the object descriptors in the od update
    for (AP4_List<AP4_Descriptor>::Item* od_item = od_update->GetDescriptors().FirstItem();
         od_item;
         od_item = od_item->GetNext()) {
        od = AP4_DYNAMIC_CAST(AP4_ObjectDescriptor, od_item->GetData());
        if (od == NULL) continue;

        // find which track this od references
        AP4_EsIdRefDescriptor* es_id_ref;
        es_id_ref = AP4_DYNAMIC_CAST(AP4_EsIdRefDescriptor, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_ES_ID_REF));
        if (es_id_ref == NULL || 
            es_id_ref->GetRefIndex() > track_references->GetTrackIds().ItemCount() ||
            es_id_ref->GetRefIndex() == 0) {
            continue;
        }
        AP4_UI32 track_id = track_references->GetTrackIds()[es_id_ref->GetRefIndex()-1];
        SinfEntry* sinf_entry = NULL;
        for (AP4_List<SinfEntry>::Item* sinf_entry_item = sinf_entries.FirstItem();
             sinf_entry_item;
             sinf_entry_item = sinf_entry_item->GetNext()) {
            sinf_entry = sinf_entry_item->GetData();
            if (sinf_entry->m_TrackId == track_id) {
                break; // match
            } else {
                sinf_entry = NULL; // no match
            }
        }
        if (sinf_entry == NULL) continue; // no matching entry
        if (sinf_entry->m_Sinf != NULL) continue; // entry already populated
        
        // see what ipmp descriptor this od points to
        AP4_IpmpDescriptorPointer* ipmpd_pointer;
        ipmpd_pointer = AP4_DYNAMIC_CAST(AP4_IpmpDescriptorPointer, od->FindSubDescriptor(AP4_DESCRIPTOR_TAG_IPMP_DESCRIPTOR_POINTER));
        if (ipmpd_pointer == NULL) continue; // no pointer

        // find the ipmp descriptor referenced by the pointer
        AP4_IpmpDescriptor* ipmpd = NULL;
        for (AP4_List<AP4_Descriptor>::Item* ipmpd_item = ipmp_update->GetDescriptors().FirstItem();
             ipmpd_item;
             ipmpd_item = ipmpd_item->GetNext()) {
            // check that this descriptor is of the right type
            ipmpd = AP4_DYNAMIC_CAST(AP4_IpmpDescriptor, ipmpd_item->GetData());
            if (ipmpd == NULL || ipmpd->GetIpmpsType() != AP4_MARLIN_IPMPS_TYPE_MGSV) continue;
            
            // check the descriptor id
            if (ipmpd->GetDescriptorId() == ipmpd_pointer->GetDescriptorId()) {
                break; // match
            } else {
                ipmpd = NULL; // no match
            }
        }
        if (ipmpd == NULL) continue; // no matching entry
        
        // parse the ipmp data into one or more 'sinf' atoms, and keep the one with the
        // right type
        AP4_MemoryByteStream* data = new AP4_MemoryByteStream(ipmpd->GetData().GetData(),
                                                              ipmpd->GetData().GetDataSize());
        AP4_LargeSize bytes_available = ipmpd->GetData().GetDataSize();
        do {
            AP4_Atom* atom = NULL;
            
            // setup the factory with a context so we can instantiate an 'schm'
            // atom with a slightly different format than the standard 'schm'
            AP4_AtomFactory* factory = &AP4_MarlinIpmpAtomFactory::Instance;
            factory->PushContext(AP4_ATOM_TYPE('m','r','l','n'));
            
            // parse the next atom in the stream 
            result = factory->CreateAtomFromStream(*data, bytes_available, atom);
            factory->PopContext();
            if (AP4_FAILED(result) || atom == NULL) break;
            
            // check that what we have parsed is indeed an 'sinf' of the right type
            if (atom->GetType() == AP4_ATOM_TYPE_SINF) {
                AP4_ContainerAtom* sinf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
                AP4_SchmAtom* schm = AP4_DYNAMIC_CAST(AP4_SchmAtom, sinf->FindChild("schm"));
                if (schm->GetSchemeType()    == AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC && 
                    schm->GetSchemeVersion() == 0x0100) {
                    // store the sinf in the entry for that track
                    sinf_entry->m_Sinf = sinf;
                    break;
                }
            }
            delete atom;
        } while (AP4_SUCCEEDED(result));
        data->Release();        
    }
    
    // remove the iods atom and the OD track if required
    if (remove_od_data) {
        od_trak->Detach();
        delete od_trak;
        iods->Detach();
        delete iods;
    }
    
    // cleanup
    delete od_track;
    
    return AP4_SUCCESS;
}
/*----------------------------------------------------------------------
|   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;
}
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc < 2) {
        PrintUsageAndExit();
    }
    
    // default options
    Options.verbose                = false;
    Options.input                  = NULL;
    Options.init_segment_name      = AP4_SPLIT_DEFAULT_INIT_SEGMENT_NAME;
    Options.media_segment_name     = AP4_SPLIT_DEFAULT_MEDIA_SEGMENT_NAME;
    Options.pattern_params         = AP4_SPLIT_DEFAULT_PATTERN_PARAMS;
    Options.start_number           = 1;
    Options.track_id               = 0;
    Options.audio_only             = false;
    Options.video_only             = false;
    Options.init_only              = false;
    Options.track_filter           = 0;
    
    // parse command line
    AP4_Result result;
    char** args = argv+1;
    while (const char* arg = *args++) {
        if (!strcmp(arg, "--verbose")) {
            Options.verbose = true;
        } else if (!strcmp(arg, "--init-segment")) {
            if (*args == NULL) {
                fprintf(stderr, "ERROR: missing argument after --init-segment option\n");
                return 1;
            }
            Options.init_segment_name = *args++;
        } else if (!strcmp(arg, "--media-segment")) {
            if (*args == NULL) {
                fprintf(stderr, "ERROR: missing argument after --media-segment option\n");
                return 1;
            }
            Options.media_segment_name = *args++;
        } else if (!strcmp(arg, "--pattern-parameters")) {
            if (*args == NULL) {
                fprintf(stderr, "ERROR: missing argument after --pattern-params option\n");
                return 1;
            }
            Options.pattern_params = *args++;
        } else if (!strcmp(arg, "--track-id")) {
            Options.track_id = strtoul(*args++, NULL, 10);
        } else if (!strcmp(arg, "--start-number")) {
            Options.start_number = strtoul(*args++, NULL, 10);
        } else if (!strcmp(arg, "--init-only")) {
            Options.init_only = true;
        } else if (!strcmp(arg, "--audio")) {
            Options.audio_only = true;
        } else if (!strcmp(arg, "--video")) {
            Options.video_only = true;
        } else if (Options.input == NULL) {
            Options.input = arg;
        } else {
            fprintf(stderr, "ERROR: unexpected argument\n");
            return 1;
        }
    }

    // check args
    if (Options.input == NULL) {
        fprintf(stderr, "ERROR: missing input file name\n");
        return 1;
    }
    if ((Options.audio_only && (Options.video_only || Options.track_id)) ||
        (Options.video_only && (Options.audio_only || Options.track_id)) ||
        (Options.track_id   && (Options.audio_only || Options.video_only))) {
        fprintf(stderr, "ERROR: --audio, --video and --track-id options are mutualy exclusive\n");
        return 1;
    }
    if (strlen(Options.pattern_params) < 1) {
        fprintf(stderr, "ERROR: --pattern-params argument is too short\n");
        return 1;
    }
    if (strlen(Options.pattern_params) > 2) {
        fprintf(stderr, "ERROR: --pattern-params argument is too long\n");
        return 1;
    }
    const char* cursor = Options.pattern_params;
    while (*cursor) {
        if (*cursor != 'I' && *cursor != 'N') {
            fprintf(stderr, "ERROR: invalid pattern parameter '%c'\n", *cursor);
            return 1;
        }
        ++cursor;
    }
    
	// create the input stream
    AP4_ByteStream* input = NULL;
    result = AP4_FileByteStream::Create(Options.input, AP4_FileByteStream::STREAM_MODE_READ, input);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open input (%d)\n", result);
        return 1;
    }
    
    // get the movie
    AP4_File* file = new AP4_File(*input, AP4_DefaultAtomFactory::Instance, true);
    AP4_Movie* movie = file->GetMovie();
    if (movie == NULL) {
        fprintf(stderr, "no movie found in file\n");
        return 1;
    }
    
    // filter tracks if required
    if (Options.audio_only) {
        AP4_Track* track = movie->GetTrack(AP4_Track::TYPE_AUDIO);
        if (track == NULL) {
            fprintf(stderr, "--audio option specified, but no audio track found\n");
            return 1;
        }
        Options.track_filter = track->GetId();
    } else if (Options.video_only) {
        AP4_Track* track = movie->GetTrack(AP4_Track::TYPE_VIDEO);
        if (track == NULL) {
            fprintf(stderr, "--video option specified, but no video track found\n");
            return 1;
        }
        Options.track_filter = track->GetId();
    } else if (Options.track_id) {
        AP4_Track* track = movie->GetTrack(Options.track_id);
        if (track == NULL) {
            fprintf(stderr, "--track-id option specified, but no such track found\n");
            return 1;
        }
        Options.track_filter = track->GetId();
    }
    
    // save the init segment
    AP4_ByteStream* output = NULL;
    result = AP4_FileByteStream::Create(Options.init_segment_name, AP4_FileByteStream::STREAM_MODE_WRITE, output);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open output file (%d)\n", result);
        return 1;
    }
    AP4_FtypAtom* ftyp = file->GetFileType(); 
    if (ftyp) {
        result = ftyp->Write(*output);
        if (AP4_FAILED(result)) {
            fprintf(stderr, "ERROR: cannot write ftyp segment (%d)\n", result);
            return 1;
        }
    }
    if (Options.track_filter) {
        AP4_MoovAtom* moov = movie->GetMoovAtom();
        
        // only keep the 'trak' atom that we need
        AP4_List<AP4_Atom>::Item* child = moov->GetChildren().FirstItem();
        while (child) {
            AP4_Atom* atom = child->GetData();
            child = child->GetNext();
            if (atom->GetType() == AP4_ATOM_TYPE_TRAK) {
                AP4_TrakAtom* trak = (AP4_TrakAtom*)atom;
                AP4_TkhdAtom* tkhd = (AP4_TkhdAtom*)trak->GetChild(AP4_ATOM_TYPE_TKHD);
                if (tkhd && tkhd->GetTrackId() != Options.track_filter) {
                    atom->Detach();
                    delete atom;
                }
            }
        }

        // only keep the 'trex' atom that we need
        AP4_ContainerAtom* mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov->GetChild(AP4_ATOM_TYPE_MVEX));
        if (mvex) {
            child = mvex->GetChildren().FirstItem();
            while (child) {
                AP4_Atom* atom = child->GetData();
                child = child->GetNext();
                if (atom->GetType() == AP4_ATOM_TYPE_TREX) {
                    AP4_TrexAtom* trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, atom);
                    if (trex && trex->GetTrackId() != Options.track_filter) {
                        atom->Detach();
                        delete atom;
                    }
                }
            }
        }
    }
    result = movie->GetMoovAtom()->Write(*output);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot write init segment (%d)\n", result);
        return 1;
    }
        
    AP4_Atom* atom = NULL;
    unsigned int track_id = 0;
    for (;!Options.init_only;) {
        // process the next atom
        result = AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(*input, atom);
        if (AP4_FAILED(result)) break;
        
        if (atom->GetType() == AP4_ATOM_TYPE_MOOF) {
            AP4_ContainerAtom* moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);

            unsigned int traf_count = 0;
            AP4_ContainerAtom* traf = NULL;
            do {
                traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof->GetChild(AP4_ATOM_TYPE_TRAF, traf_count));
                if (traf == NULL) break;
                AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD));
                if (tfhd == NULL) {
                    fprintf(stderr, "ERROR: invalid media format\n");
                    return 1;                    
                }
                track_id = tfhd->GetTrackId();
                traf_count++;
            } while (traf);
    
            // check if this fragment has more than one traf
            if (traf_count > 1) {
                if (Options.audio_only) {
                    fprintf(stderr, "ERROR: --audio option incompatible with multi-track fragments");
                    return 1;
                }
                if (Options.video_only) {
                    fprintf(stderr, "ERROR: --video option incompatible with multi-track fragments");
                    return 1;
                }
                track_id = 0;
            }
            
            // open a new file for this fragment
            if (output) {
                output->Release();
                output = NULL;
            }
            char segment_name[4096];
            if (Options.track_filter == 0 || Options.track_filter == track_id) {
                AP4_UI64 p[2] = {0,0};
                unsigned int params_len = strlen(Options.pattern_params);
                for (unsigned int i=0; i<params_len; i++) {
                    if (Options.pattern_params[i] == 'I') {
                        p[i] = track_id;
                    } else if (Options.pattern_params[i] == 'N') {
                        p[i] = NextFragmentIndex(track_id)+Options.start_number;
                    }
                }
                switch (params_len) {
                    case 1:
                        sprintf(segment_name, Options.media_segment_name, p[0]);
                        break;
                    case 2:
                        sprintf(segment_name, Options.media_segment_name, p[0], p[1]);
                        break;
                    default:
                        segment_name[0] = 0;
                        break;
                }
                result = AP4_FileByteStream::Create(segment_name, AP4_FileByteStream::STREAM_MODE_WRITE, output);
                if (AP4_FAILED(result)) {
                    fprintf(stderr, "ERROR: cannot open output file (%d)\n", result);
                    return 1;
                }
            }
        }
        
        // write the atom
        if (output && atom->GetType() != AP4_ATOM_TYPE_MFRA) {
            atom->Write(*output);
        }
        delete atom;
    }

    // cleanup
    delete file;
    if (input) input->Release();
    if (output) output->Release();
    
    return 0;
}
Exemple #14
0
/*----------------------------------------------------------------------
|   AP4_Movie::ProcessMoof
+---------------------------------------------------------------------*/
void
AP4_Movie::ProcessMoof(AP4_ContainerAtom* moof, AP4_ByteStream& stream)
{
	if (moof) {
		AP4_Offset offset = 0;
		stream.Tell(offset);
		AP4_Offset moof_offset = offset - moof->GetSize();
		AP4_Offset mdat_payload_offset = offset + 8;

		AP4_MfhdAtom* mfhd = AP4_DYNAMIC_CAST(AP4_MfhdAtom, moof->GetChild(AP4_ATOM_TYPE_MFHD));
		if (mfhd) {
			for (AP4_List<AP4_Atom>::Item* item = moof->GetChildren().FirstItem();
										   item;
										   item = item->GetNext()) {
				AP4_Atom* atom = item->GetData();
				if (atom->GetType() == AP4_ATOM_TYPE_TRAF) {
					AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom);
					if (traf) {
						AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD));
						if (!tfhd) {
							continue;
						}
						AP4_Track* track = GetTrack(tfhd->GetTrackId());
						if (!track) {
							continue;
						}

						AP4_TfdtAtom* tfdt = AP4_DYNAMIC_CAST(AP4_TfdtAtom, traf->GetChild(AP4_ATOM_TYPE_TFDT));

						AP4_TrexAtom*      trex = NULL;
						AP4_ContainerAtom* mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, m_MoovAtom->GetChild(AP4_ATOM_TYPE_MVEX));
						if (mvex) {
							for (AP4_List<AP4_Atom>::Item* child_item = mvex->GetChildren().FirstItem();
														   child_item;
														   child_item = child_item->GetNext()) {
								AP4_Atom* child_atom = child_item->GetData();
								if (child_atom->GetType() == AP4_ATOM_TYPE_TREX) {
									trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, child_atom);
									if (trex && trex->GetTrackId() == tfhd->GetTrackId()) break;
									trex = NULL;
								}
							}
						}

						AP4_FragmentSampleTable* sampleTable = track->GetFragmentSampleTable();

						AP4_Cardinal sample_count = 0;
						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);
								if (trun) {
									sample_count += trun->GetEntries().ItemCount();
								}
							}
						}

						if (!sample_count) {
							return;
						}

						if (sampleTable->GetSampleCount() == 0) {
							track->CreateFragmentFromStdSamples();
						}

						if (AP4_FAILED(sampleTable->EnsureCapacity(sample_count + sampleTable->GetSampleCount()))) {
							return;
						}

						AP4_UI64 dts_origin = tfdt ? tfdt->GetBaseMediaDecodeTime() : 0;
						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);
								if (trun) {
									sampleTable->AddTrun(trun, tfhd, trex, stream, dts_origin, moof_offset, mdat_payload_offset);
								}
							}
						}
					}
				}
			}
		}
	}
}
Exemple #15
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;
}