Esempio n. 1
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;
}
Esempio n. 2
0
/*----------------------------------------------------------------------
|   AP4_MarlinIpmpEncryptingProcessor::Initialize
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpEncryptingProcessor::Initialize(
    AP4_AtomParent&                  top_level,
    AP4_ByteStream&                  /*stream*/,
    AP4_Processor::ProgressListener* /*listener = NULL*/)
{
    // get the moov atom
    AP4_MoovAtom* moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV));
    if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
    
    // deal with the file type
    AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
    if (ftyp) {
        // remove the atom, it will be replaced with a new one
        top_level.RemoveChild(ftyp);
        
        // keep the existing brand and compatible brands
        AP4_Array<AP4_UI32> compatible_brands;
        compatible_brands.EnsureCapacity(ftyp->GetCompatibleBrands().ItemCount()+1);
        for (unsigned int i=0; i<ftyp->GetCompatibleBrands().ItemCount(); i++) {
            compatible_brands.Append(ftyp->GetCompatibleBrands()[i]);
        }
        
        // add the MGSV compatible brand if it is not already there
        if (!ftyp->HasCompatibleBrand(AP4_MARLIN_BRAND_MGSV)) {
            compatible_brands.Append(AP4_MARLIN_BRAND_MGSV);
        }

        // create a replacement for the major brand
        AP4_FtypAtom* new_ftyp = new AP4_FtypAtom(AP4_MARLIN_BRAND_MGSV,
                                                  0x13c078c, //AP4_MARLIN_BRAND_MGSV_MAJOR_VERSION,
                                                  &compatible_brands[0],
                                                  compatible_brands.ItemCount());
        delete ftyp;
        ftyp = new_ftyp;
    } else {
        AP4_UI32 isom = AP4_FTYP_BRAND_ISOM;
        ftyp = new AP4_FtypAtom(AP4_MARLIN_BRAND_MGSV, 0, &isom, 1);
    }
    
    // insert the ftyp atom as the first child
    top_level.AddChild(ftyp, 0);

    // create and 'mpod' track reference atom
    AP4_TrefTypeAtom* mpod = new AP4_TrefTypeAtom(AP4_ATOM_TYPE_MPOD);
    
    // look for an available track ID, starting at 1
    unsigned int od_track_id       = 0;
    unsigned int od_track_position = 0;
    AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
    while (trak_item) {
        AP4_TrakAtom* trak = trak_item->GetData();
        if (trak) {
            od_track_position++;
            if (trak->GetId() >= od_track_id) {
                od_track_id = trak->GetId()+1;
            }
            
            // if the track is encrypted, reference it in the mpod
            if (m_KeyMap.GetKey(trak->GetId())) {
                mpod->AddTrackId(trak->GetId());
            }
            
            //m_SinfEntries.Add(new SinfEntry(trak->GetId(), NULL));
        }
        trak_item = trak_item->GetNext();
    }
    
    // check that there was at least one track in the file
    if (od_track_id == 0) return AP4_ERROR_INVALID_FORMAT;
    
    // create an initial object descriptor
    AP4_InitialObjectDescriptor* iod = 
        // FIXME: get real values from the property map
        new AP4_InitialObjectDescriptor(AP4_DESCRIPTOR_TAG_MP4_IOD,
                                        1022, // object descriptor id
                                        false, 
                                        0xFE,    // OD profile level (0xFE = No OD profile specified)
                                        0xFF,    // scene profile level
                                        0xFE,    // audio profile level
                                        0xFE,    // visual profile level
                                        0xFF);   // graphics profile

    // create an ES_ID_Inc subdescriptor and add it to the initial object descriptor
    AP4_EsIdIncDescriptor* es_id_inc = new AP4_EsIdIncDescriptor(od_track_id);
    iod->AddSubDescriptor(es_id_inc);
    
    // create an iods atom to hold the initial object descriptor
    AP4_IodsAtom* iods = new AP4_IodsAtom(iod);
    
    // add the iods atom to the moov atom (try to put it just after mvhd)
    int iods_position = 0;
    int item_position = 0;
    for (AP4_List<AP4_Atom>::Item* item = moov->GetChildren().FirstItem();
         item;
         ++item) {
         ++item_position;
         if (item->GetData()->GetType() == AP4_ATOM_TYPE_MVHD) {
            iods_position = item_position;
            break;
         }
    }
    AP4_Result result = moov->AddChild(iods, iods_position);
    if (AP4_FAILED(result)) {
        delete iods;
        return result;
    }
    
    // create a sample table for the OD track
    AP4_SyntheticSampleTable* od_sample_table = new AP4_SyntheticSampleTable();
    
    // create the sample description for the OD track 
    AP4_MpegSystemSampleDescription* od_sample_description;
    od_sample_description = new AP4_MpegSystemSampleDescription(AP4_STREAM_TYPE_OD,
                                                                AP4_OTI_MPEG4_SYSTEM,
                                                                NULL,
                                                                32768, // buffer size
                                                                1024,  // max bitrate
                                                                512);  // avg bitrate
    od_sample_table->AddSampleDescription(od_sample_description, true);
    
    // create the OD descriptor update 
    AP4_DescriptorUpdateCommand od_update(AP4_COMMAND_TAG_OBJECT_DESCRIPTOR_UPDATE);
    for (unsigned int i=0; i<mpod->GetTrackIds().ItemCount(); i++) {
        AP4_ObjectDescriptor* od = new AP4_ObjectDescriptor(AP4_DESCRIPTOR_TAG_MP4_OD, 256+i); // descriptor id = 256+i
        od->AddSubDescriptor(new AP4_EsIdRefDescriptor(i+1));     // index into mpod (1-based)
        od->AddSubDescriptor(new AP4_IpmpDescriptorPointer(i+1)); // descriptor id = i+1
        od_update.AddDescriptor(od);
    }
    
    // create the IPMP descriptor update 
    AP4_DescriptorUpdateCommand ipmp_update(AP4_COMMAND_TAG_IPMP_DESCRIPTOR_UPDATE);
    for (unsigned int i=0; i<mpod->GetTrackIds().ItemCount(); i++) {
        // create the ipmp descriptor
        AP4_IpmpDescriptor* ipmp_descriptor = new AP4_IpmpDescriptor(i+1, AP4_MARLIN_IPMPS_TYPE_MGSV);

        // create the sinf container
        AP4_ContainerAtom* sinf = new AP4_ContainerAtom(AP4_ATOM_TYPE_SINF);

        // add the scheme type atom
        sinf->AddChild(new AP4_SchmAtom(AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC, 0x0100, NULL, true));

        // setup the scheme info atom
        const char* content_id = m_PropertyMap.GetProperty(mpod->GetTrackIds()[i], "ContentId");
        if (content_id) {
            AP4_ContainerAtom* schi = new AP4_ContainerAtom(AP4_ATOM_TYPE_SCHI);
            schi->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_8ID_, content_id));
            sinf->AddChild(schi);
        }
         
        // serialize the sinf atom to a buffer and set it as the ipmp data
        AP4_MemoryByteStream* sinf_data = new AP4_MemoryByteStream((AP4_Size)sinf->GetSize());
        sinf->Write(*sinf_data);
        ipmp_descriptor->SetData(sinf_data->GetData(), sinf_data->GetDataSize());
        sinf_data->Release();
        
        ipmp_update.AddDescriptor(ipmp_descriptor);
    }
    
    // add the sample with the descriptors and updates
    AP4_MemoryByteStream* sample_data = new AP4_MemoryByteStream();
    od_update.Write(*sample_data);
    ipmp_update.Write(*sample_data);
    od_sample_table->AddSample(*sample_data, 0, sample_data->GetDataSize(), 0, 0, 0, 0, true);
    
    // create the OD track
    AP4_TrakAtom* od_track = new AP4_TrakAtom(od_sample_table,
                                              AP4_HANDLER_TYPE_ODSM,
                                              "Bento4 Marlin OD Handler",
                                              od_track_id,
                                              0, 0,
                                              1, 1000, 1, 0, "und",
                                              0, 0);
    
    // add an entry in the processor's stream table to indicate that the 
    // media data for the OD track is not in the file stream, but in our
    // memory stream.
    m_ExternalTrackData.Add(new ExternalTrackData(od_track_id, sample_data));
    sample_data->Release();
    
    // add a tref track reference atom
    AP4_ContainerAtom* tref = new AP4_ContainerAtom(AP4_ATOM_TYPE_TREF);
    tref->AddChild(mpod);
    od_track->AddChild(tref, 1); // add after 'tkhd'
    
    // add the track to the moov atoms (just after the last track)
    moov->AddChild(od_track, od_track_position);
    
    return AP4_SUCCESS;
}
Esempio n. 3
0
/*----------------------------------------------------------------------
|   AP4_MarlinIpmpEncryptingProcessor::Initialize
+---------------------------------------------------------------------*/
AP4_Result 
AP4_MarlinIpmpEncryptingProcessor::Initialize(
    AP4_AtomParent&                  top_level,
    AP4_ByteStream&                  /*stream*/,
    AP4_Processor::ProgressListener* /*listener = NULL*/)
{
    // get the moov atom
    AP4_MoovAtom* moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, top_level.GetChild(AP4_ATOM_TYPE_MOOV));
    if (moov == NULL) return AP4_ERROR_INVALID_FORMAT;
    
    // deal with the file type
    AP4_FtypAtom* ftyp = AP4_DYNAMIC_CAST(AP4_FtypAtom, top_level.GetChild(AP4_ATOM_TYPE_FTYP));
    if (ftyp) {
        // remove the atom, it will be replaced with a new one
        top_level.RemoveChild(ftyp);
        
        // keep the existing brand and compatible brands
        AP4_Array<AP4_UI32> compatible_brands;
        compatible_brands.EnsureCapacity(ftyp->GetCompatibleBrands().ItemCount()+1);
        for (unsigned int i=0; i<ftyp->GetCompatibleBrands().ItemCount(); i++) {
            compatible_brands.Append(ftyp->GetCompatibleBrands()[i]);
        }
        
        // add the MGSV compatible brand if it is not already there
        if (!ftyp->HasCompatibleBrand(AP4_MARLIN_BRAND_MGSV)) {
            compatible_brands.Append(AP4_MARLIN_BRAND_MGSV);
        }

        // create a replacement for the major brand
        AP4_FtypAtom* new_ftyp = new AP4_FtypAtom(AP4_MARLIN_BRAND_MGSV,
                                                  0x13c078c, //AP4_MARLIN_BRAND_MGSV_MAJOR_VERSION,
                                                  &compatible_brands[0],
                                                  compatible_brands.ItemCount());
        delete ftyp;
        ftyp = new_ftyp;
    } else {
        AP4_UI32 isom = AP4_FTYP_BRAND_ISOM;
        ftyp = new AP4_FtypAtom(AP4_MARLIN_BRAND_MGSV, 0, &isom, 1);
    }
    
    // insert the ftyp atom as the first child
    top_level.AddChild(ftyp, 0);

    // create and 'mpod' track reference atom
    AP4_TrefTypeAtom* mpod = new AP4_TrefTypeAtom(AP4_ATOM_TYPE_MPOD);
    
    // look for an available track ID, starting at 1
    unsigned int od_track_id       = 0;
    unsigned int od_track_position = 0;
    for (AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
                                       trak_item;
                                       trak_item = trak_item->GetNext()) {
        AP4_TrakAtom* trak = trak_item->GetData();
        if (trak) {
            od_track_position++;
            if (trak->GetId() >= od_track_id) {
                od_track_id = trak->GetId()+1;
            }
            
            // if the track is encrypted, reference it in the mpod
            if (m_KeyMap.GetKey(trak->GetId())) {
                mpod->AddTrackId(trak->GetId());
            }
            
            //m_SinfEntries.Add(new SinfEntry(trak->GetId(), NULL));
        }   
    }
    
    // check that there was at least one track in the file
    if (od_track_id == 0) return AP4_ERROR_INVALID_FORMAT;
    
    // create an initial object descriptor
    AP4_InitialObjectDescriptor* iod = 
        // FIXME: get real values from the property map
        new AP4_InitialObjectDescriptor(AP4_DESCRIPTOR_TAG_MP4_IOD,
                                        1022, // object descriptor id
                                        false, 
                                        0xFE,    // OD profile level (0xFE = No OD profile specified)
                                        0xFF,    // scene profile level
                                        0xFE,    // audio profile level
                                        0xFE,    // visual profile level
                                        0xFF);   // graphics profile

    // create an ES_ID_Inc subdescriptor and add it to the initial object descriptor
    AP4_EsIdIncDescriptor* es_id_inc = new AP4_EsIdIncDescriptor(od_track_id);
    iod->AddSubDescriptor(es_id_inc);
    
    // create an iods atom to hold the initial object descriptor
    AP4_IodsAtom* iods = new AP4_IodsAtom(iod);
    
    // add the iods atom to the moov atom (try to put it just after mvhd)
    int iods_position = 0;
    int item_position = 0;
    for (AP4_List<AP4_Atom>::Item* moov_item = moov->GetChildren().FirstItem();
                                   moov_item;
                                   moov_item = moov_item->GetNext()) {
         ++item_position;
         if (moov_item->GetData()->GetType() == AP4_ATOM_TYPE_MVHD) {
            iods_position = item_position;
            break;
         }
    }
    AP4_Result result = moov->AddChild(iods, iods_position);
    if (AP4_FAILED(result)) {
        delete iods;
        return result;
    }
    
    // create a sample table for the OD track
    AP4_SyntheticSampleTable* od_sample_table = new AP4_SyntheticSampleTable();
    
    // create the sample description for the OD track 
    AP4_MpegSystemSampleDescription* od_sample_description;
    od_sample_description = new AP4_MpegSystemSampleDescription(AP4_STREAM_TYPE_OD,
                                                                AP4_OTI_MPEG4_SYSTEM,
                                                                NULL,
                                                                32768, // buffer size
                                                                1024,  // max bitrate
                                                                512);  // avg bitrate
    od_sample_table->AddSampleDescription(od_sample_description, true);
    
    // create the OD descriptor update 
    AP4_DescriptorUpdateCommand od_update(AP4_COMMAND_TAG_OBJECT_DESCRIPTOR_UPDATE);
    for (unsigned int i=0; i<mpod->GetTrackIds().ItemCount(); i++) {
        AP4_ObjectDescriptor* od = new AP4_ObjectDescriptor(AP4_DESCRIPTOR_TAG_MP4_OD, 256+i); // descriptor id = 256+i
        od->AddSubDescriptor(new AP4_EsIdRefDescriptor(i+1));     // index into mpod (1-based)
        od->AddSubDescriptor(new AP4_IpmpDescriptorPointer(i+1)); // descriptor id = i+1
        od_update.AddDescriptor(od);
    }
    
    // create the IPMP descriptor update 
    AP4_DescriptorUpdateCommand ipmp_update(AP4_COMMAND_TAG_IPMP_DESCRIPTOR_UPDATE);
    for (unsigned int i=0; i<mpod->GetTrackIds().ItemCount(); i++) {
        // create the ipmp descriptor
        AP4_IpmpDescriptor* ipmp_descriptor = new AP4_IpmpDescriptor(i+1, AP4_MARLIN_IPMPS_TYPE_MGSV);

        // create the sinf container
        AP4_ContainerAtom* sinf = new AP4_ContainerAtom(AP4_ATOM_TYPE_SINF);

        // add the scheme type atom
        sinf->AddChild(new AP4_SchmAtom(m_UseGroupKey?
                                        AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACGK:
                                        AP4_PROTECTION_SCHEME_TYPE_MARLIN_ACBC, 
                                        0x0100, NULL, true));

        // create the 'schi' container
        AP4_ContainerAtom* schi = new AP4_ContainerAtom(AP4_ATOM_TYPE_SCHI);

        // add the content ID 
        const char* content_id = m_PropertyMap.GetProperty(mpod->GetTrackIds()[i], "ContentId");
        if (content_id) {
            // add the content ID (8id_)
            schi->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_8ID_, content_id));
        }
                
        // find what the track type is (necessary for the next step) and the key
        const AP4_DataBuffer* key = NULL;
        AP4_Track::Type track_type = AP4_Track::TYPE_UNKNOWN;
        for (AP4_List<AP4_TrakAtom>::Item* trak_item = moov->GetTrakAtoms().FirstItem();
                                           trak_item;
                                           trak_item = trak_item->GetNext()) {
            AP4_TrakAtom* trak = trak_item->GetData();
            if (trak->GetId() == mpod->GetTrackIds()[i]) {
                // find the handler type
                AP4_Atom* sub = trak->FindChild("mdia/hdlr");
                if (sub) {
                    AP4_HdlrAtom* hdlr = AP4_DYNAMIC_CAST(AP4_HdlrAtom, sub);
                    if (hdlr) {
                        AP4_UI32 type = hdlr->GetHandlerType();
                        if (type == AP4_HANDLER_TYPE_SOUN) {
                            track_type = AP4_Track::TYPE_AUDIO;
                        } else if (type == AP4_HANDLER_TYPE_VIDE) {
                            track_type = AP4_Track::TYPE_VIDEO;
                        }
                    }
                }
                
                // find the key
                key = m_KeyMap.GetKey(trak->GetId());
                break;
            }
        }
        
        // group key
        if (m_UseGroupKey && key) {
            // find the group key
            const AP4_DataBuffer* group_key = m_KeyMap.GetKey(0);
            if (group_key) {
                AP4_DataBuffer wrapped_key;
                result = AP4_AesKeyWrap(group_key->GetData(), key->GetData(), key->GetDataSize(), wrapped_key);
                if (AP4_FAILED(result)) return result;
                AP4_UnknownAtom* gkey = new AP4_UnknownAtom(AP4_ATOM_TYPE_GKEY, 
                                                            wrapped_key.GetData(), 
                                                            wrapped_key.GetDataSize());
                schi->AddChild(gkey);
            }
        }
                
        // create and add the security attributes (satr)
        if (track_type != AP4_Track::TYPE_UNKNOWN && key != NULL && key != NULL) {
            AP4_ContainerAtom* satr = new AP4_ContainerAtom(AP4_ATOM_TYPE_SATR);
            switch (track_type) {
                case AP4_Track::TYPE_AUDIO:
                    satr->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_STYP, AP4_MARLIN_IPMP_STYP_AUDIO));
                    break;
                case AP4_Track::TYPE_VIDEO:
                    satr->AddChild(new AP4_NullTerminatedStringAtom(AP4_ATOM_TYPE_STYP, AP4_MARLIN_IPMP_STYP_VIDEO));
                    break;
                default:
                    break;
            }
            
            // add the signed attributes, if any
            const char* signed_attributes = m_PropertyMap.GetProperty(mpod->GetTrackIds()[i], "SignedAttributes");
            if (signed_attributes) {
                // decode the hex-encoded data
                unsigned int size = (unsigned int)AP4_StringLength(signed_attributes)/2;
                AP4_DataBuffer attributes_atoms;
                attributes_atoms.SetDataSize(size);
                if (AP4_SUCCEEDED(AP4_ParseHex(signed_attributes, attributes_atoms.UseData(), size))) {
                    // parse all the atoms encoded in the data and add them to the 'schi' container
                    AP4_MemoryByteStream* mbs = new AP4_MemoryByteStream(attributes_atoms.GetData(), 
                                                                         attributes_atoms.GetDataSize());
                    do {
                        AP4_Atom* atom = NULL;
                        result = AP4_DefaultAtomFactory::Instance.CreateAtomFromStream(*mbs, atom);
                        if (AP4_SUCCEEDED(result) && atom) {
                            satr->AddChild(atom);
                        }
                    } while (AP4_SUCCEEDED(result));
                    mbs->Release();
                }
            }

            // compute the hmac
            AP4_MemoryByteStream* mbs = new AP4_MemoryByteStream();
            satr->Write(*mbs);
            AP4_Hmac* digester = NULL;
            AP4_Hmac::Create(AP4_Hmac::SHA256, key->GetData(), key->GetDataSize(), digester);
            digester->Update(mbs->GetData(), mbs->GetDataSize());
            AP4_DataBuffer hmac_value;
            digester->Final(hmac_value);
            AP4_Atom* hmac = new AP4_UnknownAtom(AP4_ATOM_TYPE_HMAC, hmac_value.GetData(), hmac_value.GetDataSize());
            
            schi->AddChild(satr);
            schi->AddChild(hmac);
            
            mbs->Release();
        }
            
        sinf->AddChild(schi);
         
        // serialize the sinf atom to a buffer and set it as the ipmp data
        AP4_MemoryByteStream* sinf_data = new AP4_MemoryByteStream((AP4_Size)sinf->GetSize());
        sinf->Write(*sinf_data);
        ipmp_descriptor->SetData(sinf_data->GetData(), sinf_data->GetDataSize());
        sinf_data->Release();
        
        ipmp_update.AddDescriptor(ipmp_descriptor);
    }
    
    // add the sample with the descriptors and updates
    AP4_MemoryByteStream* sample_data = new AP4_MemoryByteStream();
    od_update.Write(*sample_data);
    ipmp_update.Write(*sample_data);
    od_sample_table->AddSample(*sample_data, 0, sample_data->GetDataSize(), 0, 0, 0, 0, true);
    
    // create the OD track
    AP4_TrakAtom* od_track = new AP4_TrakAtom(od_sample_table,
                                              AP4_HANDLER_TYPE_ODSM,
                                              "Bento4 Marlin OD Handler",
                                              od_track_id,
                                              0, 0,
                                              1, 1000, 1, 0, "und",
                                              0, 0);
    
    // add an entry in the processor's stream table to indicate that the 
    // media data for the OD track is not in the file stream, but in our
    // memory stream.
    m_ExternalTrackData.Add(new ExternalTrackData(od_track_id, sample_data));
    sample_data->Release();
    
    // add a tref track reference atom
    AP4_ContainerAtom* tref = new AP4_ContainerAtom(AP4_ATOM_TYPE_TREF);
    tref->AddChild(mpod);
    od_track->AddChild(tref, 1); // add after 'tkhd'
    
    // add the track to the moov atoms (just after the last track)
    moov->AddChild(od_track, od_track_position);
    
    return AP4_SUCCESS;
}
Esempio n. 4
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::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;
}