/*---------------------------------------------------------------------- | AP4_Processor::Process +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::Process(AP4_ByteStream& input, AP4_ByteStream& output, ProgressListener* listener, AP4_AtomFactory& atom_factory) { // read all atoms AP4_AtomParent top_level; AP4_Atom* atom; while (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom))) { top_level.AddChild(atom); } // remove the [mdat] atom, keep a ref to [moov] AP4_MoovAtom* moov = NULL; AP4_List<AP4_Atom>::Item* atom_item = top_level.GetChildren().FirstItem(); while (atom_item) { atom = atom_item->GetData(); AP4_List<AP4_Atom>::Item* next = atom_item->GetNext(); if (atom->GetType() == AP4_ATOM_TYPE_MDAT) { atom->Detach(); delete atom; } else if (atom->GetType() == AP4_ATOM_TYPE_MOOV) { moov = (AP4_MoovAtom*)atom; } atom_item = next; } // initialize the processor AP4_Result result = Initialize(top_level, input); if (AP4_FAILED(result)) return result; // process the tracks if we have a moov atom AP4_Array<AP4_SampleLocator> locators; AP4_Cardinal track_count = 0; AP4_List<AP4_TrakAtom>* trak_atoms = NULL; AP4_LargeSize mdat_payload_size = 0; TrackHandler** handlers = NULL; AP4_SampleCursor* cursors = NULL; if (moov) { // build an array of track sample locators trak_atoms = &moov->GetTrakAtoms(); track_count = trak_atoms->ItemCount(); cursors = new AP4_SampleCursor[track_count]; handlers = new TrackHandler*[track_count]; for (AP4_Ordinal i=0; i<track_count; i++) { handlers[i] = NULL; } unsigned int index = 0; for (AP4_List<AP4_TrakAtom>::Item* item = trak_atoms->FirstItem(); item; item=item->GetNext()) { AP4_TrakAtom* trak = item->GetData(); // find the stsd atom AP4_ContainerAtom* stbl = AP4_DYNAMIC_CAST(AP4_ContainerAtom, trak->FindChild("mdia/minf/stbl")); if (stbl == NULL) continue; // see if there's an external data source for this track AP4_ByteStream* trak_data_stream = &input; for (AP4_List<ExternalTrackData>::Item* ditem = m_ExternalTrackData.FirstItem(); ditem; ditem=ditem->GetNext()) { ExternalTrackData* tdata = ditem->GetData(); if (tdata->m_TrackId == trak->GetId()) { trak_data_stream = tdata->m_MediaData; break; } } // create the track handler handlers[index] = CreateTrackHandler(trak); cursors[index].m_Locator.m_TrakIndex = index; cursors[index].m_Locator.m_SampleTable = new AP4_AtomSampleTable(stbl, *trak_data_stream); cursors[index].m_Locator.m_SampleIndex = 0; cursors[index].m_Locator.m_ChunkIndex = 0; cursors[index].m_Locator.m_SampleTable->GetSample(0, cursors[index].m_Locator.m_Sample); index++; } // figure out the layout of the chunks for (;;) { // see which is the next sample to write AP4_UI64 min_offset = (AP4_UI64)(-1); int cursor = -1; for (unsigned int i=0; i<track_count; i++) { if (!cursors[i].m_EndReached && cursors[i].m_Locator.m_Sample.GetOffset() <= min_offset) { min_offset = cursors[i].m_Locator.m_Sample.GetOffset(); cursor = i; } } // stop if all cursors are exhausted if (cursor == -1) break; // append this locator to the layout list AP4_SampleLocator& locator = cursors[cursor].m_Locator; locators.Append(locator); // move the cursor to the next sample locator.m_SampleIndex++; if (locator.m_SampleIndex == locator.m_SampleTable->GetSampleCount()) { // mark this track as completed cursors[cursor].m_EndReached = true; } else { // get the next sample info locator.m_SampleTable->GetSample(locator.m_SampleIndex, locator.m_Sample); AP4_Ordinal skip, sdesc; locator.m_SampleTable->GetChunkForSample(locator.m_SampleIndex, locator.m_ChunkIndex, skip, sdesc); } } // update the stbl atoms and compute the mdat size int current_track = -1; int current_chunk = -1; AP4_Position current_chunk_offset = 0; AP4_Size current_chunk_size = 0; for (AP4_Ordinal i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; if ((int)locator.m_TrakIndex != current_track || (int)locator.m_ChunkIndex != current_chunk) { // start a new chunk for this track current_chunk_offset += current_chunk_size; current_chunk_size = 0; current_track = locator.m_TrakIndex; current_chunk = locator.m_ChunkIndex; locator.m_SampleTable->SetChunkOffset(locator.m_ChunkIndex, current_chunk_offset); } AP4_Size sample_size; TrackHandler* handler = handlers[locator.m_TrakIndex]; if (handler) { sample_size = handler->GetProcessedSampleSize(locator.m_Sample); locator.m_SampleTable->SetSampleSize(locator.m_SampleIndex, sample_size); } else { sample_size = locator.m_Sample.GetSize(); } current_chunk_size += sample_size; mdat_payload_size += sample_size; } // process the tracks (ex: sample descriptions processing) for (AP4_Ordinal i=0; i<track_count; i++) { TrackHandler* handler = handlers[i]; if (handler) handler->ProcessTrack(); } } // initialize the processor Finalize(top_level); // calculate the size of all atoms combined AP4_UI64 atoms_size = 0; top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size)); // see if we need a 64-bit or 32-bit mdat AP4_Size mdat_header_size = AP4_ATOM_HEADER_SIZE; if (mdat_payload_size+mdat_header_size > 0xFFFFFFFF) { // we need a 64-bit size mdat_header_size += 8; } // adjust the chunk offsets for (AP4_Ordinal i=0; i<track_count; i++) { AP4_TrakAtom* trak; trak_atoms->Get(i, trak); trak->AdjustChunkOffsets(atoms_size+mdat_header_size); } // write all atoms top_level.GetChildren().Apply(AP4_AtomListWriter(output)); // write mdat header if (mdat_payload_size) { if (mdat_header_size == AP4_ATOM_HEADER_SIZE) { // 32-bit size output.WriteUI32((AP4_UI32)(mdat_header_size+mdat_payload_size)); output.WriteUI32(AP4_ATOM_TYPE_MDAT); } else { // 64-bit size output.WriteUI32(1); output.WriteUI32(AP4_ATOM_TYPE_MDAT); output.WriteUI64(mdat_header_size+mdat_payload_size); } } #if defined(AP4_DEBUG) AP4_Position before; output.Tell(before); #endif // write the samples if (moov) { AP4_Sample sample; AP4_DataBuffer data_in; AP4_DataBuffer data_out; for (unsigned int i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; locator.m_Sample.ReadData(data_in); TrackHandler* handler = handlers[locator.m_TrakIndex]; if (handler) { result = handler->ProcessSample(data_in, data_out); if (AP4_FAILED(result)) return result; output.Write(data_out.GetData(), data_out.GetDataSize()); } else { output.Write(data_in.GetData(), data_in.GetDataSize()); } // notify the progress listener if (listener) { listener->OnProgress(i+1, locators.ItemCount()); } } // cleanup for (AP4_Ordinal i=0; i<track_count; i++) { delete cursors[i].m_Locator.m_SampleTable; delete handlers[i]; } delete[] cursors; delete[] handlers; } #if defined(AP4_DEBUG) AP4_Position after; output.Tell(after); AP4_ASSERT(after-before == mdat_payload_size); #endif return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | main +---------------------------------------------------------------------*/ int main(int argc, char** argv) { if (argc < 2) { PrintUsageAndExit(); } // 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; }
/*---------------------------------------------------------------------- | AP4_MoovAtom::AP4_MoovAtom +---------------------------------------------------------------------*/ AP4_MoovAtom::AP4_MoovAtom(AP4_Size size, AP4_ByteStream& stream, AP4_AtomFactory& atom_factory) : AP4_ContainerAtom(AP4_ATOM_TYPE_MOOV, size, false, stream, atom_factory), m_TimeScale(0) { if(AP4_ContainerAtom* cmov = dynamic_cast<AP4_ContainerAtom*>(GetChild(AP4_ATOM_TYPE_CMOV))) { AP4_DcomAtom* dcom = dynamic_cast<AP4_DcomAtom*>(cmov->GetChild(AP4_ATOM_TYPE_DCOM)); AP4_CmvdAtom* cmvd = dynamic_cast<AP4_CmvdAtom*>(cmov->GetChild(AP4_ATOM_TYPE_CMVD)); if(dcom && dcom->GetCompressorSubType() == AP4_ATOM_TYPE('z','l','i','b') && cmvd) { const AP4_DataBuffer& data = cmvd->GetDataBuffer(); z_stream d_stream; d_stream.zalloc = (alloc_func)0; d_stream.zfree = (free_func)0; d_stream.opaque = (voidpf)0; int res; if(Z_OK == (res = inflateInit(&d_stream))) { d_stream.next_in = (Bytef*)data.GetData(); d_stream.avail_in = data.GetDataSize(); unsigned char* dst = NULL; int n = 0; do { dst = (unsigned char*)realloc(dst, ++n*1000); d_stream.next_out = &dst[(n-1)*1000]; d_stream.avail_out = 1000; if(Z_OK != (res = inflate(&d_stream, Z_NO_FLUSH)) && Z_STREAM_END != res) { free(dst); dst = NULL; break; } } while(0 == d_stream.avail_out && 0 != d_stream.avail_in && Z_STREAM_END != res); inflateEnd(&d_stream); if(dst) { AP4_ByteStream* s = new AP4_MemoryByteStream(dst, d_stream.total_out); ReadChildren(atom_factory, *s, d_stream.total_out); s->Release(); free(dst); } if(AP4_MoovAtom* moov = dynamic_cast<AP4_MoovAtom*>(GetChild(AP4_ATOM_TYPE_MOOV))) { AP4_List<AP4_Atom> Children; for(AP4_List<AP4_Atom>::Item* item = moov->GetChildren().FirstItem(); item; item = item->GetNext()) { Children.Add(item->GetData()); } for(AP4_List<AP4_Atom>::Item* item = Children.FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); atom->Detach(); atom->SetParent(this); m_Children.Add(atom); } moov->Detach(); delete moov; } } } } // collect all trak atoms m_Children.Apply(AP4_TrakAtomCollector(&m_TrakAtoms)); }
/*---------------------------------------------------------------------- | 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; }