/*---------------------------------------------------------------------- | AP4_TrakAtom::GetChunkOffsets +---------------------------------------------------------------------*/ AP4_Result AP4_TrakAtom::GetChunkOffsets(AP4_Array<AP4_UI64>& chunk_offsets) { AP4_Atom* atom; if ((atom = FindChild("mdia/minf/stbl/stco"))) { AP4_StcoAtom* stco = AP4_DYNAMIC_CAST(AP4_StcoAtom, atom); if (stco == NULL) return AP4_ERROR_INTERNAL; AP4_Cardinal stco_chunk_count = stco->GetChunkCount(); const AP4_UI32* stco_chunk_offsets = stco->GetChunkOffsets(); chunk_offsets.SetItemCount(stco_chunk_count); for (unsigned int i=0; i<stco_chunk_count; i++) { chunk_offsets[i] = stco_chunk_offsets[i]; } return AP4_SUCCESS; } else if ((atom = FindChild("mdia/minf/stbl/co64"))) { AP4_Co64Atom* co64 = AP4_DYNAMIC_CAST(AP4_Co64Atom, atom); if (co64 == NULL) return AP4_ERROR_INTERNAL; AP4_Cardinal co64_chunk_count = co64->GetChunkCount(); const AP4_UI64* co64_chunk_offsets = co64->GetChunkOffsets(); chunk_offsets.SetItemCount(co64_chunk_count); for (unsigned int i=0; i<co64_chunk_count; i++) { chunk_offsets[i] = co64_chunk_offsets[i]; } return AP4_SUCCESS; } else { return AP4_ERROR_INVALID_STATE; } }
/*---------------------------------------------------------------------- | AP4_TrakAtom::SetChunkOffsets +---------------------------------------------------------------------*/ AP4_Result AP4_TrakAtom::SetChunkOffsets(const AP4_Array<AP4_UI64>& chunk_offsets) { AP4_Atom* atom; if ((atom = FindChild("mdia/minf/stbl/stco"))) { AP4_StcoAtom* stco = dynamic_cast<AP4_StcoAtom*>(atom); if (stco == NULL) return AP4_ERROR_INTERNAL; AP4_Cardinal stco_chunk_count = stco->GetChunkCount(); AP4_UI32* stco_chunk_offsets = stco->GetChunkOffsets(); if (stco_chunk_count > chunk_offsets.ItemCount()) { return AP4_ERROR_OUT_OF_RANGE; } for (unsigned int i=0; i<stco_chunk_count; i++) { stco_chunk_offsets[i] = (AP4_UI32)chunk_offsets[i]; } return AP4_SUCCESS; } else if ((atom = FindChild("mdia/minf/stbl/co64"))) { AP4_Co64Atom* co64 = dynamic_cast<AP4_Co64Atom*>(atom); if (co64 == NULL) return AP4_ERROR_INTERNAL; AP4_Cardinal co64_chunk_count = co64->GetChunkCount(); AP4_UI64* co64_chunk_offsets = co64->GetChunkOffsets(); if (co64_chunk_count > chunk_offsets.ItemCount()) { return AP4_ERROR_OUT_OF_RANGE; } for (unsigned int i=0; i<co64_chunk_count; i++) { co64_chunk_offsets[i] = chunk_offsets[i]; } return AP4_SUCCESS; } else { return AP4_ERROR_INVALID_STATE; } }
/*---------------------------------------------------------------------- | AP4_AvccAtom::AP4_AvccAtom +---------------------------------------------------------------------*/ AP4_AvccAtom::AP4_AvccAtom(AP4_UI08 config_version, AP4_UI08 profile, AP4_UI08 level, AP4_UI08 profile_compatibility, AP4_UI08 length_size, const AP4_Array<AP4_DataBuffer>& sequence_parameters, const AP4_Array<AP4_DataBuffer>& picture_parameters) : AP4_Atom(AP4_ATOM_TYPE_AVCC, AP4_ATOM_HEADER_SIZE+7), m_ConfigurationVersion(config_version), m_Profile(profile), m_Level(level), m_ProfileCompatibility(profile_compatibility), m_NaluLengthSize(length_size) { // deep copy of the parameters unsigned int i = 0; for (i=0; i<sequence_parameters.ItemCount(); i++) { m_SequenceParameters.Append(sequence_parameters[i]); m_Size32 += 2+sequence_parameters[i].GetDataSize(); } for (i=0; i<picture_parameters.ItemCount(); i++) { m_PictureParameters.Append(picture_parameters[i]); m_Size32 += 2+picture_parameters[i].GetDataSize(); } }
/*---------------------------------------------------------------------- | AP4_AvccAtom::AP4_AvccAtom +---------------------------------------------------------------------*/ AP4_AvccAtom::AP4_AvccAtom(AP4_UI08 profile, AP4_UI08 level, AP4_UI08 profile_compatibility, AP4_UI08 length_size, const AP4_Array<AP4_DataBuffer>& sequence_parameters, const AP4_Array<AP4_DataBuffer>& picture_parameters) : AP4_Atom(AP4_ATOM_TYPE_AVCC, AP4_ATOM_HEADER_SIZE), m_ConfigurationVersion(1), m_Profile(profile), m_Level(level), m_ProfileCompatibility(profile_compatibility), m_NaluLengthSize(length_size) { // deep copy of the parameters unsigned int i = 0; for (i=0; i<sequence_parameters.ItemCount(); i++) { m_SequenceParameters.Append(sequence_parameters[i]); } for (i=0; i<picture_parameters.ItemCount(); i++) { m_PictureParameters.Append(picture_parameters[i]); } // compute the raw bytes UpdateRawBytes(); // update the size m_Size32 += m_RawBytes.GetDataSize(); }
static unsigned int NextFragmentIndex(unsigned int track_id) { int track_index = -1; for (unsigned int i=0; i<TrackIds.ItemCount(); i++) { if (TrackIds[i] == track_id) { track_index = i; break; } } if (track_index == -1) { track_index = TrackCounters.ItemCount(); TrackIds.Append(track_id); TrackCounters.Append(0); } return TrackCounters[track_index]++; }
/*---------------------------------------------------------------------- | ProcessMoof +---------------------------------------------------------------------*/ static int ProcessMoof(AP4_Movie* movie, AP4_ContainerAtom* moof, AP4_ByteStream* sample_stream, AP4_Position moof_offset, AP4_Position mdat_payload_offset) { AP4_Result result; AP4_MovieFragment* fragment = new AP4_MovieFragment(moof); printf("fragment sequence number=%d\n", fragment->GetSequenceNumber()); AP4_FragmentSampleTable* sample_table = NULL; // get all track IDs in this fragment AP4_Array<AP4_UI32> ids; fragment->GetTrackIds(ids); printf("Found %d tracks in fragment: ", ids.ItemCount()); for (unsigned int i=0; i<ids.ItemCount(); i++) { printf("%d ", ids[i]); } printf("\n"); for (unsigned int i=0; i<ids.ItemCount(); i++) { AP4_Track* track = NULL; if (movie) { track = movie->GetTrack(ids[i]); } AP4_ContainerAtom* traf = NULL; fragment->GetTrafAtom(ids[i], traf); printf("processing moof for track id %d\n", ids[i]); result = fragment->CreateSampleTable(movie, ids[i], sample_stream, moof_offset, mdat_payload_offset, sample_table); CHECK(result == AP4_SUCCESS || result == AP4_ERROR_NO_SUCH_ITEM); if (AP4_SUCCEEDED(result) ) { ProcessSamples(track, traf, sample_table); delete sample_table; } else { printf("no sample table for this track\n"); } } delete fragment; return 0; }
/*---------------------------------------------------------------------- | AP4_LinearReader::ProcessMoof +---------------------------------------------------------------------*/ AP4_Result AP4_LinearReader::ProcessMoof(AP4_ContainerAtom* moof, AP4_Position moof_offset, AP4_Position mdat_payload_offset) { AP4_Result result; // create a new fragment delete m_Fragment; m_Fragment = new AP4_MovieFragment(moof); // update the trackers AP4_Array<AP4_UI32> ids; m_Fragment->GetTrackIds(ids); for (unsigned int i=0; i<m_Trackers.ItemCount(); i++) { Tracker* tracker = m_Trackers[i]; if (tracker->m_SampleTableIsOwned) { delete tracker->m_SampleTable; } tracker->m_SampleTable = NULL; tracker->m_NextSampleIndex = 0; for (unsigned int j=0; j<ids.ItemCount(); j++) { if (ids[j] == tracker->m_Track->GetId()) { AP4_FragmentSampleTable* sample_table = NULL; result = m_Fragment->CreateSampleTable(&m_Movie, ids[j], m_FragmentStream, moof_offset, mdat_payload_offset, tracker->m_NextDts, sample_table); if (AP4_FAILED(result)) return result; tracker->m_SampleTable = sample_table; tracker->m_SampleTableIsOwned = true; tracker->m_Eos = false; break; } } } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | DumpTrackData +---------------------------------------------------------------------*/ void DumpTrackData(const char* mp4_filename, AP4_File& mp4_file, const AP4_Array<AP4_Ordinal>& tracks_to_dump, const AP4_ProtectionKeyMap& key_map) { // dump all the tracks that need to be dumped AP4_Cardinal tracks_to_dump_count = tracks_to_dump.ItemCount(); for (AP4_Ordinal i=0; i<tracks_to_dump_count; i++) { // get the track AP4_Ordinal track_id = tracks_to_dump[i]; AP4_Track* track = mp4_file.GetMovie()->GetTrack(track_id); if (track == NULL) { fprintf(stderr, "track not found (id = %d)", track_id); return; } // get the sample description AP4_SampleDescription* sample_description = track->GetSampleDescription(0); if (sample_description == NULL) { fprintf(stderr, "WARNING: unable to parse sample description\n"); } // get the dump data byte stream AP4_ByteStream* dump = CreateTrackDumpByteStream(mp4_filename, track_id); if (dump == NULL) return; printf("\nDumping data for track %d:\n", track_id); switch(sample_description ? sample_description->GetType() : AP4_SampleDescription::TYPE_UNKNOWN) { case AP4_SampleDescription::TYPE_PROTECTED: { const AP4_DataBuffer* key = key_map.GetKey(track_id); if (key == NULL) { fprintf(stderr, "WARNING: No key found for encrypted track %d... " "dumping encrypted samples\n", track_id); DumpSamples(track, dump); } else { DecryptAndDumpSamples(track, sample_description, key->GetData(), key->GetDataSize(), dump); } } break; default: DumpSamples(track, dump); } dump->Release(); } }
/*---------------------------------------------------------------------- | AP4_TrakAtom::SetChunkOffsets +---------------------------------------------------------------------*/ AP4_Result AP4_TrakAtom::SetChunkOffsets(const AP4_Array<AP4_UI64>& chunk_offsets) { AP4_Atom* atom; if ((atom = FindChild("mdia/minf/stbl/stco"))) { AP4_StcoAtom* stco = AP4_DYNAMIC_CAST(AP4_StcoAtom, atom); if (stco == NULL) return AP4_ERROR_INTERNAL; AP4_Cardinal stco_chunk_count = stco->GetChunkCount(); AP4_UI32* stco_chunk_offsets = stco->GetChunkOffsets(); if (stco_chunk_count > chunk_offsets.ItemCount()) { return AP4_ERROR_OUT_OF_RANGE; } for (unsigned int i=0; i<stco_chunk_count; i++) { AP4_ASSERT(!(chunk_offsets[i] >> 32)); stco_chunk_offsets[i] = (AP4_UI32)chunk_offsets[i]; } return AP4_SUCCESS; } else if ((atom = FindChild("mdia/minf/stbl/co64"))) {
/*---------------------------------------------------------------------- | main +---------------------------------------------------------------------*/ int main(int argc, char** argv) { if (argc < 2) { PrintUsageAndExit(); } // init the variables const char* input_filename = NULL; const char* output_filename = NULL; const char* track_selector = NULL; AP4_UI32 selected_track_id = 0; unsigned int fragment_duration = 0; bool auto_detect_fragment_duration = true; bool create_segment_index = false; bool quiet = false; AP4_UI32 timescale = 0; AP4_Result result; Options.verbosity = 1; Options.debug = false; Options.trim = false; Options.no_tfdt = false; Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE; // parse the command line argv++; char* arg; while ((arg = *argv++)) { if (!strcmp(arg, "--verbosity")) { arg = *argv++; if (arg == NULL) { fprintf(stderr, "ERROR: missing argument after --verbosity option\n"); return 1; } Options.verbosity = strtoul(arg, NULL, 10); } else if (!strcmp(arg, "--debug")) { Options.debug = true; } else if (!strcmp(arg, "--index")) { create_segment_index = true; } else if (!strcmp(arg, "--quiet")) { quiet = true; } else if (!strcmp(arg, "--trim")) { Options.trim = true; } else if (!strcmp(arg, "--no-tfdt")) { Options.no_tfdt = true; } else if (!strcmp(arg, "--force-i-frame-sync")) { arg = *argv++; if (arg == NULL) { fprintf(stderr, "ERROR: missing argument after --fragment-duration option\n"); return 1; } if (!strcmp(arg, "all")) { Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_ALL; } else if (!strcmp(arg, "auto")) { Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_AUTO; } else { fprintf(stderr, "ERROR: unknown mode for --force-i-frame-sync\n"); return 1; } } else if (!strcmp(arg, "--fragment-duration")) { arg = *argv++; if (arg == NULL) { fprintf(stderr, "ERROR: missing argument after --fragment-duration option\n"); return 1; } fragment_duration = strtoul(arg, NULL, 10); auto_detect_fragment_duration = false; } else if (!strcmp(arg, "--timescale")) { arg = *argv++; if (arg == NULL) { fprintf(stderr, "ERROR: missing argument after --timescale option\n"); return 1; } timescale = strtoul(arg, NULL, 10); } else if (!strcmp(arg, "--track")) { track_selector = *argv++; if (track_selector == NULL) { fprintf(stderr, "ERROR: missing argument after --track option\n"); return 1; } } else { if (input_filename == NULL) { input_filename = arg; } else if (output_filename == NULL) { output_filename = arg; } else { fprintf(stderr, "ERROR: unexpected argument '%s'\n", arg); return 1; } } } if (Options.debug && Options.verbosity == 0) { Options.verbosity = 1; } if (input_filename == NULL) { fprintf(stderr, "ERROR: no input specified\n"); return 1; } AP4_ByteStream* input_stream = NULL; result = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input_stream); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot open input (%d)\n", result); return 1; } if (output_filename == NULL) { fprintf(stderr, "ERROR: no output specified\n"); return 1; } AP4_ByteStream* output_stream = NULL; result = AP4_FileByteStream::Create(output_filename, AP4_FileByteStream::STREAM_MODE_WRITE, output_stream); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: cannot create/open output (%d)\n", result); return 1; } // parse the input MP4 file (moov only) AP4_File input_file(*input_stream, AP4_DefaultAtomFactory::Instance, true); // check the file for basic properties if (input_file.GetMovie() == NULL) { fprintf(stderr, "ERROR: no movie found in the file\n"); return 1; } if (!quiet && input_file.GetMovie()->HasFragments()) { fprintf(stderr, "NOTICE: file is already fragmented, it will be re-fragmented\n"); } // create a cusor list to keep track of the tracks we will read from AP4_Array<TrackCursor*> cursors; // iterate over all tracks TrackCursor* video_track = NULL; TrackCursor* audio_track = NULL; TrackCursor* subtitles_track = NULL; unsigned int video_track_count = 0; unsigned int audio_track_count = 0; unsigned int subtitles_track_count = 0; for (AP4_List<AP4_Track>::Item* track_item = input_file.GetMovie()->GetTracks().FirstItem(); track_item; track_item = track_item->GetNext()) { AP4_Track* track = track_item->GetData(); // sanity check if (track->GetSampleCount() == 0 && !input_file.GetMovie()->HasFragments()) { fprintf(stderr, "WARNING: track %d has no samples, it will be skipped\n", track->GetId()); continue; } // create a sample array for this track SampleArray* sample_array; if (input_file.GetMovie()->HasFragments()) { sample_array = new CachedSampleArray(track); } else { sample_array = new SampleArray(track); } // create a cursor for the track TrackCursor* cursor = new TrackCursor(track, sample_array); cursor->m_Tfra->SetTrackId(track->GetId()); cursors.Append(cursor); if (track->GetType() == AP4_Track::TYPE_VIDEO) { if (video_track) { fprintf(stderr, "WARNING: more than one video track found\n"); } else { video_track = cursor; } video_track_count++; } else if (track->GetType() == AP4_Track::TYPE_AUDIO) { if (audio_track == NULL) { audio_track = cursor; } audio_track_count++; } else if (track->GetType() == AP4_Track::TYPE_SUBTITLES) { if (subtitles_track == NULL) { subtitles_track = cursor; } subtitles_track_count++; } } if (cursors.ItemCount() == 0) { fprintf(stderr, "ERROR: no valid track found\n"); return 1; } if (track_selector) { if (!strncmp("audio", track_selector, 5)) { if (audio_track) { selected_track_id = audio_track->m_Track->GetId(); } else { fprintf(stderr, "ERROR: no audio track found\n"); return 1; } } else if (!strncmp("video", track_selector, 5)) { if (video_track) { selected_track_id = video_track->m_Track->GetId(); } else { fprintf(stderr, "ERROR: no video track found\n"); return 1; } } else if (!strncmp("subtitles", track_selector, 9)) { if (subtitles_track) { selected_track_id = subtitles_track->m_Track->GetId(); } else { fprintf(stderr, "ERROR: no subtitles track found\n"); return 1; } } else { selected_track_id = (AP4_UI32)strtol(track_selector, NULL, 10); bool found = false; for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (cursors[i]->m_Track->GetId() == selected_track_id) { found = true; break; } } if (!found) { fprintf(stderr, "ERROR: track not found\n"); return 1; } } } if (video_track_count == 0 && audio_track_count == 0 && subtitles_track_count == 0) { fprintf(stderr, "ERROR: no audio, video, or subtitles track in the file\n"); return 1; } AP4_AvcSampleDescription* avc_desc = NULL; if (video_track && (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE)) { // that feature is only supported for AVC AP4_SampleDescription* sdesc = video_track->m_Track->GetSampleDescription(0); if (sdesc) { avc_desc = AP4_DYNAMIC_CAST(AP4_AvcSampleDescription, sdesc); } if (avc_desc == NULL) { fprintf(stderr, "--force-i-frame-sync can only be used with AVC/H.264 video\n"); return 1; } } // remember where the stream was AP4_Position position; input_stream->Tell(position); // for fragmented input files, we need to populate the sample arrays if (input_file.GetMovie()->HasFragments()) { AP4_LinearReader reader(*input_file.GetMovie(), input_stream); for (unsigned int i=0; i<cursors.ItemCount(); i++) { reader.EnableTrack(cursors[i]->m_Track->GetId()); } AP4_UI32 track_id; AP4_Sample sample; do { result = reader.GetNextSample(sample, track_id); if (AP4_SUCCEEDED(result)) { for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (cursors[i]->m_Track->GetId() == track_id) { cursors[i]->m_Samples->AddSample(sample); break; } } } } while (AP4_SUCCEEDED(result)); } else if (video_track && (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE)) { AP4_Sample sample; if (Options.force_i_frame_sync == AP4_FRAGMENTER_FORCE_SYNC_MODE_AUTO) { // detect if this looks like an open-gop source for (unsigned int i=1; i<video_track->m_Samples->GetSampleCount(); i++) { if (AP4_SUCCEEDED(video_track->m_Samples->GetSample(i, sample))) { if (sample.IsSync()) { // we found a sync i-frame, assume this is *not* an open-gop source Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE; if (Options.debug) { printf("this does not look like an open-gop source, not forcing i-frame sync flags\n"); } break; } } } } if (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE) { for (unsigned int i=0; i<video_track->m_Samples->GetSampleCount(); i++) { if (AP4_SUCCEEDED(video_track->m_Samples->GetSample(i, sample))) { if (IsIFrame(sample, avc_desc)) { video_track->m_Samples->ForceSync(i); } } } } } // return the stream to its original position input_stream->Seek(position); // auto-detect the fragment duration if needed if (auto_detect_fragment_duration) { if (video_track) { fragment_duration = AutoDetectFragmentDuration(video_track); } else if (audio_track && input_file.GetMovie()->HasFragments()) { fragment_duration = AutoDetectAudioFragmentDuration(*input_stream, audio_track); } if (fragment_duration == 0) { if (Options.verbosity > 0) { fprintf(stderr, "unable to autodetect fragment duration, using default\n"); } fragment_duration = AP4_FRAGMENTER_DEFAULT_FRAGMENT_DURATION; } else if (fragment_duration > AP4_FRAGMENTER_MAX_AUTO_FRAGMENT_DURATION) { if (Options.verbosity > 0) { fprintf(stderr, "auto-detected fragment duration too large, using default\n"); } fragment_duration = AP4_FRAGMENTER_DEFAULT_FRAGMENT_DURATION; } } // fragment the file Fragment(input_file, *output_stream, cursors, fragment_duration, timescale, selected_track_id, create_segment_index); // cleanup and exit if (input_stream) input_stream->Release(); if (output_stream) output_stream->Release(); return 0; }
/*---------------------------------------------------------------------- | Fragment +---------------------------------------------------------------------*/ static void Fragment(AP4_File& input_file, AP4_ByteStream& output_stream, AP4_Array<TrackCursor*>& cursors, unsigned int fragment_duration, AP4_UI32 timescale, AP4_UI32 track_id, bool create_segment_index) { AP4_List<FragmentInfo> fragments; TrackCursor* index_cursor = NULL; AP4_Result result; AP4_Movie* input_movie = input_file.GetMovie(); if (input_movie == NULL) { fprintf(stderr, "ERROR: no moov found in the input file\n"); return; } // create the output file object AP4_Movie* output_movie = new AP4_Movie(1000); // create an mvex container AP4_ContainerAtom* mvex = new AP4_ContainerAtom(AP4_ATOM_TYPE_MVEX); AP4_MehdAtom* mehd = new AP4_MehdAtom(0); mvex->AddChild(mehd); // add an output track for each track in the input file for (unsigned int i=0; i<cursors.ItemCount(); i++) { AP4_Track* track = cursors[i]->m_Track; // skip non matching tracks if we have a selector if (track_id && track->GetId() != track_id) { continue; } result = cursors[i]->Init(); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to init sample cursor (%d), skipping track %d\n", result, track->GetId()); return; } // create a sample table (with no samples) to hold the sample description AP4_SyntheticSampleTable* sample_table = new AP4_SyntheticSampleTable(); for (unsigned int j=0; j<track->GetSampleDescriptionCount(); j++) { AP4_SampleDescription* sample_description = track->GetSampleDescription(j); sample_table->AddSampleDescription(sample_description, false); } // create the track AP4_Track* output_track = new AP4_Track(sample_table, track->GetId(), timescale?timescale:1000, AP4_ConvertTime(track->GetDuration(), input_movie->GetTimeScale(), timescale?timescale:1000), timescale?timescale:track->GetMediaTimeScale(), 0,//track->GetMediaDuration(), track); output_movie->AddTrack(output_track); // add a trex entry to the mvex container AP4_TrexAtom* trex = new AP4_TrexAtom(track->GetId(), 1, 0, 0, 0); mvex->AddChild(trex); } // select the anchor cursor TrackCursor* anchor_cursor = NULL; for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (cursors[i]->m_Track->GetId() == track_id) { anchor_cursor = cursors[i]; } } if (anchor_cursor == NULL) { for (unsigned int i=0; i<cursors.ItemCount(); i++) { // use this as the anchor track if it is the first video track if (cursors[i]->m_Track->GetType() == AP4_Track::TYPE_VIDEO) { anchor_cursor = cursors[i]; break; } } } if (anchor_cursor == NULL) { // no video track to anchor with, pick the first audio track for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (cursors[i]->m_Track->GetType() == AP4_Track::TYPE_AUDIO) { anchor_cursor = cursors[i]; break; } } // no audio track to anchor with, pick the first subtitles track for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (cursors[i]->m_Track->GetType() == AP4_Track::TYPE_SUBTITLES) { anchor_cursor = cursors[i]; break; } } } if (anchor_cursor == NULL) { // this shoudl never happen fprintf(stderr, "ERROR: no anchor track\n"); return; } if (create_segment_index) { index_cursor = anchor_cursor; } if (Options.debug) { printf("Using track ID %d as anchor\n", anchor_cursor->m_Track->GetId()); } // update the mehd duration mehd->SetDuration(output_movie->GetDuration()); // add the mvex container to the moov container output_movie->GetMoovAtom()->AddChild(mvex); // compute all the fragments unsigned int sequence_number = 1; for(;;) { TrackCursor* cursor = NULL; // pick the first track with a fragment index lower than the anchor's for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (track_id && cursors[i]->m_Track->GetId() != track_id) continue; if (cursors[i]->m_Eos) continue; if (cursors[i]->m_FragmentIndex < anchor_cursor->m_FragmentIndex) { cursor = cursors[i]; break; } } // check if we found a non-anchor cursor to use if (cursor == NULL) { // the anchor should be used in this round, check if we can use it if (anchor_cursor->m_Eos) { // the anchor is done, pick a new anchor unless we need to trim anchor_cursor = NULL; if (!Options.trim) { for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (track_id && cursors[i]->m_Track->GetId() != track_id) continue; if (cursors[i]->m_Eos) continue; if (anchor_cursor == NULL || cursors[i]->m_Track->GetType() == AP4_Track::TYPE_VIDEO || cursors[i]->m_Track->GetType() == AP4_Track::TYPE_AUDIO) { anchor_cursor = cursors[i]; if (Options.debug) { printf("+++ New anchor: Track ID %d\n", anchor_cursor->m_Track->GetId()); } } } } } cursor = anchor_cursor; } if (cursor == NULL) break; // all done // decide how many samples go into this fragment AP4_UI64 target_dts; if (cursor == anchor_cursor) { // compute the current dts in milliseconds AP4_UI64 anchor_dts_ms = AP4_ConvertTime(cursor->m_Sample.GetDts(), cursor->m_Track->GetMediaTimeScale(), 1000); // round to the nearest multiple of fragment_duration AP4_UI64 anchor_position = (anchor_dts_ms + (fragment_duration/2))/fragment_duration; // pick the next fragment_duration multiple at our target target_dts = AP4_ConvertTime(fragment_duration*(anchor_position+1), 1000, cursor->m_Track->GetMediaTimeScale()); } else { target_dts = AP4_ConvertTime(anchor_cursor->m_Sample.GetDts(), anchor_cursor->m_Track->GetMediaTimeScale(), cursor->m_Track->GetMediaTimeScale()); if (target_dts <= cursor->m_Sample.GetDts()) { // we must be at the end, past the last anchor sample, just use the target duration target_dts = AP4_ConvertTime(fragment_duration*(cursor->m_FragmentIndex+1), 1000, cursor->m_Track->GetMediaTimeScale()); if (target_dts <= cursor->m_Sample.GetDts()) { // we're still behind, there may have been an alignment/rounding error, just advance by one segment duration target_dts = cursor->m_Sample.GetDts()+AP4_ConvertTime(fragment_duration, 1000, cursor->m_Track->GetMediaTimeScale()); } } } unsigned int end_sample_index = cursor->m_Samples->GetSampleCount(); AP4_UI64 smallest_diff = (AP4_UI64)(0xFFFFFFFFFFFFFFFFULL); AP4_Sample sample; for (unsigned int i=cursor->m_SampleIndex+1; i<=cursor->m_Samples->GetSampleCount(); i++) { AP4_UI64 dts; if (i < cursor->m_Samples->GetSampleCount()) { result = cursor->m_Samples->GetSample(i, sample); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to get sample %d (%d)\n", i, result); return; } if (!sample.IsSync()) continue; // only look for sync samples dts = sample.GetDts(); } else { result = cursor->m_Samples->GetSample(i-1, sample); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to get sample %d (%d)\n", i-1, result); return; } dts = sample.GetDts()+sample.GetDuration(); } AP4_SI64 diff = dts-target_dts; AP4_UI64 abs_diff = diff<0?-diff:diff; if (abs_diff < smallest_diff) { // this sample is the closest to the target so far end_sample_index = i; smallest_diff = abs_diff; } if (diff >= 0) { // this sample is past the target, it is not going to get any better, stop looking break; } } if (cursor->m_Eos) continue; if (Options.debug) { if (cursor == anchor_cursor) { printf("===="); } else { printf("----"); } printf(" Track ID %d - dts=%lld, target=%lld, start=%d, end=%d/%d\n", cursor->m_Track->GetId(), cursor->m_Sample.GetDts(), target_dts, cursor->m_SampleIndex, end_sample_index, cursor->m_Track->GetSampleCount()); } // emit a fragment for the selected track if (Options.verbosity > 1) { printf("fragment: track ID %d ", cursor->m_Track->GetId()); } // decide which sample description index to use // (this is not very sophisticated, we only look at the sample description // index of the first sample in the group, which may not be correct. This // should be fixed later) unsigned int sample_desc_index = cursor->m_Sample.GetDescriptionIndex(); unsigned int tfhd_flags = AP4_TFHD_FLAG_DEFAULT_BASE_IS_MOOF; if (sample_desc_index > 0) { tfhd_flags |= AP4_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT; } if (cursor->m_Track->GetType() == AP4_Track::TYPE_VIDEO) { tfhd_flags |= AP4_TFHD_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT; } // setup the moof structure AP4_ContainerAtom* moof = new AP4_ContainerAtom(AP4_ATOM_TYPE_MOOF); AP4_MfhdAtom* mfhd = new AP4_MfhdAtom(sequence_number++); moof->AddChild(mfhd); AP4_ContainerAtom* traf = new AP4_ContainerAtom(AP4_ATOM_TYPE_TRAF); AP4_TfhdAtom* tfhd = new AP4_TfhdAtom(tfhd_flags, cursor->m_Track->GetId(), 0, sample_desc_index+1, 0, 0, 0); if (tfhd_flags & AP4_TFHD_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT) { tfhd->SetDefaultSampleFlags(0x1010000); // sample_is_non_sync_sample=1, sample_depends_on=1 (not I frame) } traf->AddChild(tfhd); if (!Options.no_tfdt) { AP4_TfdtAtom* tfdt = new AP4_TfdtAtom(1, cursor->m_Timestamp); traf->AddChild(tfdt); } AP4_UI32 trun_flags = AP4_TRUN_FLAG_DATA_OFFSET_PRESENT | AP4_TRUN_FLAG_SAMPLE_DURATION_PRESENT | AP4_TRUN_FLAG_SAMPLE_SIZE_PRESENT; AP4_UI32 first_sample_flags = 0; if (cursor->m_Track->GetType() == AP4_Track::TYPE_VIDEO) { trun_flags |= AP4_TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT; first_sample_flags = 0x2000000; // sample_depends_on=2 (I frame) } AP4_TrunAtom* trun = new AP4_TrunAtom(trun_flags, 0, first_sample_flags); traf->AddChild(trun); moof->AddChild(traf); // create a new FragmentInfo object to store the fragment details FragmentInfo* fragment = new FragmentInfo(cursor->m_Samples, cursor->m_Tfra, cursor->m_Timestamp, moof); fragments.Add(fragment); // add samples to the fragment unsigned int sample_count = 0; AP4_Array<AP4_TrunAtom::Entry> trun_entries; fragment->m_MdatSize = AP4_ATOM_HEADER_SIZE; for (;;) { // if we have one non-zero CTS delta, we'll need to express it if (cursor->m_Sample.GetCtsDelta()) { trun->SetFlags(trun->GetFlags() | AP4_TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT); } // add one sample trun_entries.SetItemCount(sample_count+1); AP4_TrunAtom::Entry& trun_entry = trun_entries[sample_count]; trun_entry.sample_duration = timescale? (AP4_UI32)AP4_ConvertTime(cursor->m_Sample.GetDuration(), cursor->m_Track->GetMediaTimeScale(), timescale): cursor->m_Sample.GetDuration(); trun_entry.sample_size = cursor->m_Sample.GetSize(); trun_entry.sample_composition_time_offset = timescale? (AP4_UI32)AP4_ConvertTime(cursor->m_Sample.GetCtsDelta(), cursor->m_Track->GetMediaTimeScale(), timescale): cursor->m_Sample.GetCtsDelta(); fragment->m_SampleIndexes.SetItemCount(sample_count+1); fragment->m_SampleIndexes[sample_count] = cursor->m_SampleIndex; fragment->m_MdatSize += trun_entry.sample_size; fragment->m_Duration += trun_entry.sample_duration; // next sample cursor->m_Timestamp += trun_entry.sample_duration; result = cursor->SetSampleIndex(cursor->m_SampleIndex+1); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to get sample %d (%d)\n", cursor->m_SampleIndex+1, result); return; } sample_count++; if (cursor->m_Eos) { if (Options.debug) { printf("[Track ID %d has reached the end]\n", cursor->m_Track->GetId()); } break; } if (cursor->m_SampleIndex >= end_sample_index) { break; // done with this fragment } } if (Options.verbosity > 1) { printf(" %d samples\n", sample_count); } // update moof and children trun->SetEntries(trun_entries); trun->SetDataOffset((AP4_UI32)moof->GetSize()+AP4_ATOM_HEADER_SIZE); // advance the cursor's fragment index ++cursor->m_FragmentIndex; } // write the ftyp atom AP4_FtypAtom* ftyp = input_file.GetFileType(); if (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 compatible brand if it is not already there if (!ftyp->HasCompatibleBrand(AP4_FILE_BRAND_ISO5)) { compatible_brands.Append(AP4_FILE_BRAND_ISO5); } // create a replacement AP4_FtypAtom* new_ftyp = new AP4_FtypAtom(ftyp->GetMajorBrand(), ftyp->GetMinorVersion(), &compatible_brands[0], compatible_brands.ItemCount()); ftyp = new_ftyp; } else { AP4_UI32 compat = AP4_FILE_BRAND_ISO5; ftyp = new AP4_FtypAtom(AP4_FTYP_BRAND_MP42, 0, &compat, 1); } ftyp->Write(output_stream); delete ftyp; // write the moov atom output_movie->GetMoovAtom()->Write(output_stream); // write the (not-yet fully computed) index if needed AP4_SidxAtom* sidx = NULL; AP4_Position sidx_position = 0; output_stream.Tell(sidx_position); if (create_segment_index) { sidx = new AP4_SidxAtom(index_cursor->m_Track->GetId(), index_cursor->m_Track->GetMediaTimeScale(), 0, 0); // reserve space for the entries now, but they will be computed and updated later sidx->SetReferenceCount(fragments.ItemCount()); sidx->Write(output_stream); } // write all fragments for (AP4_List<FragmentInfo>::Item* item = fragments.FirstItem(); item; item = item->GetNext()) { FragmentInfo* fragment = item->GetData(); // remember the time and position of this fragment output_stream.Tell(fragment->m_MoofPosition); fragment->m_Tfra->AddEntry(fragment->m_Timestamp, fragment->m_MoofPosition); // write the moof fragment->m_Moof->Write(output_stream); // write mdat output_stream.WriteUI32(fragment->m_MdatSize); output_stream.WriteUI32(AP4_ATOM_TYPE_MDAT); AP4_DataBuffer sample_data; AP4_Sample sample; for (unsigned int i=0; i<fragment->m_SampleIndexes.ItemCount(); i++) { // get the sample result = fragment->m_Samples->GetSample(fragment->m_SampleIndexes[i], sample); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to get sample %d (%d)\n", fragment->m_SampleIndexes[i], result); return; } // read the sample data result = sample.ReadData(sample_data); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to read sample data for sample %d (%d)\n", fragment->m_SampleIndexes[i], result); return; } // write the sample data result = output_stream.Write(sample_data.GetData(), sample_data.GetDataSize()); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to write sample data (%d)\n", result); return; } } } // update the index and re-write it if needed if (create_segment_index) { unsigned int segment_index = 0; AP4_SidxAtom::Reference reference; for (AP4_List<FragmentInfo>::Item* item = fragments.FirstItem(); item; item = item->GetNext()) { FragmentInfo* fragment = item->GetData(); reference.m_ReferencedSize = (AP4_UI32)(fragment->m_Moof->GetSize()+fragment->m_MdatSize); reference.m_SubsegmentDuration = fragment->m_Duration; reference.m_StartsWithSap = true; sidx->SetReference(segment_index++, reference); } AP4_Position here = 0; output_stream.Tell(here); output_stream.Seek(sidx_position); sidx->Write(output_stream); output_stream.Seek(here); delete sidx; } // create an mfra container and write out the index AP4_ContainerAtom mfra(AP4_ATOM_TYPE_MFRA); for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (track_id && cursors[i]->m_Track->GetId() != track_id) { continue; } mfra.AddChild(cursors[i]->m_Tfra); cursors[i]->m_Tfra = NULL; } AP4_MfroAtom* mfro = new AP4_MfroAtom((AP4_UI32)mfra.GetSize()+16); mfra.AddChild(mfro); result = mfra.Write(output_stream); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to write 'mfra' (%d)\n", result); return; } // cleanup fragments.DeleteReferences(); for (unsigned int i=0; i<cursors.ItemCount(); i++) { delete cursors[i]; } for (AP4_List<FragmentInfo>::Item* item = fragments.FirstItem(); item; item = item->GetNext()) { FragmentInfo* fragment = item->GetData(); delete fragment->m_Moof; } delete output_movie; }
/*---------------------------------------------------------------------- | AP4_Processor::MuxStream +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::MuxStream( AP4_Array<AP4_ByteStream *> &input, AP4_ByteStream& output, AP4_UI08 partitions, AP4_AtomFactory& atom_factory) { AP4_Result result; AP4_UI64 stream_offset = 0; if (partitions & 1) { // read all atoms. // keep all atoms except [mdat] // keep a ref to [moov] // put [moof] atoms in a separate list AP4_AtomParent top_level; AP4_Array<AP4_MoovAtom*> moov; AP4_Size track_count(0); for(AP4_Size streamid(0); streamid < input.ItemCount(); ++streamid) { for (AP4_Atom* atom = NULL; AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*input[streamid], atom)); input[streamid]->Tell(stream_offset)) { if (atom->GetType() == AP4_ATOM_TYPE_MFRA) { delete atom; continue; } else if (atom->GetType() == AP4_ATOM_TYPE_SIDX) { delete atom; continue; } else if (atom->GetType() == AP4_ATOM_TYPE_SSIX) { delete atom; continue; } if (streamid == 0) top_level.AddChild(atom); else if (atom->GetType() != AP4_ATOM_TYPE_MOOV) delete atom; if (atom->GetType() == AP4_ATOM_TYPE_MOOV) { moov.Append(AP4_DYNAMIC_CAST(AP4_MoovAtom,atom)); break; } } if (moov.ItemCount() == streamid) return AP4_ERROR_INVALID_FORMAT; while (AP4_SUCCEEDED(moov[streamid]->DeleteChild(AP4_ATOM_TYPE_PSSH, 0))); // Remove tracks we cannot handle for (AP4_List<AP4_TrakAtom>::Item *item(moov[streamid]->GetTrakAtoms().FirstItem()); item;) if (!item->GetData()->FindChild("mdia/minf/stbl")) moov[streamid]->GetTrakAtoms().Remove(item); else item = item->GetNext(); track_count += moov[streamid]->GetTrakAtoms().ItemCount(); } // initialize the processor if (AP4_FAILED(result = Initialize(top_level, *input[0]))) return result; // process the tracks if we have a moov atom m_TrackData.SetItemCount(track_count); m_StreamData.SetItemCount(input.ItemCount()); //NormalizeTREX(mvex, 0, m_TrackCounts[0], m_TrackCounts[1]); AP4_Cardinal internal_index(0); AP4_ContainerAtom *mvex_base(0); AP4_List<AP4_TrakAtom>::Item *item = NULL; for (AP4_Size streamid(0); streamid < input.ItemCount(); ++streamid) { m_StreamData[streamid].trackStart = internal_index; m_StreamData[streamid].stream = input[streamid]; if (streamid) moov[0]->AddTrakAtoms(moov[streamid]->GetTrakAtoms(), item); else item = moov[streamid]->GetTrakAtoms().FirstItem(); for (; item; item = item->GetNext()) { PERTRACK &track_data(m_TrackData[internal_index]); track_data.original_id = item->GetData()->GetId(); item->GetData()->SetId(track_data.new_id = internal_index + 1); if (AP4_MdhdAtom* mdhd = AP4_DYNAMIC_CAST(AP4_MdhdAtom, item->GetData()->FindChild("mdia/mdhd"))) track_data.timescale = mdhd->GetTimeScale(); else track_data.timescale = 1; AP4_ContainerAtom *mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov[streamid]->GetChild(AP4_ATOM_TYPE_MVEX, 0)); if (!mvex) return AP4_ERROR_INVALID_FORMAT; if (!item->GetData()->GetDuration()) { AP4_MehdAtom *mehd(AP4_DYNAMIC_CAST(AP4_MehdAtom, mvex->GetChild(AP4_ATOM_TYPE_MEHD, 0))); item->GetData()->SetDuration(mehd? mehd->GetDuration():0); } AP4_TrexAtom *trex(NULL); unsigned int index(0); for (; !trex && (trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, mvex->GetChild(AP4_ATOM_TYPE_TREX, index++)));) if(trex->GetTrackId() != track_data.original_id) trex = NULL; if (!trex) return AP4_ERROR_INVALID_FORMAT; if (mvex_base) { trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, trex->Clone()); mvex_base->AddChild(trex); } else mvex_base = mvex; trex->SetTrackId(track_data.new_id); track_data.track_handler = CreateTrackHandler(item->GetData(), trex); track_data.track_handler->ProcessTrack(); track_data.streamId = streamid; ++m_StreamData[streamid].trackCount; ++internal_index; } } // We don't need the other moovs anymore..... moov.SetItemCount(1); AP4_MvhdAtom *mvhd(AP4_DYNAMIC_CAST(AP4_MvhdAtom, moov[0]->GetChild(AP4_ATOM_TYPE_MVHD, 0))); if (!mvhd->GetDuration()) { AP4_MehdAtom *mehd(AP4_DYNAMIC_CAST(AP4_MehdAtom, mvex_base->GetChild(AP4_ATOM_TYPE_MEHD, 0))); mvhd->SetDuration(mehd ? mehd->GetDuration() : 0); } // finalize the processor Finalize(top_level); // calculate the size of all atoms combined AP4_UI64 atoms_size = 0; top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size)); // write all atoms top_level.GetChildren().Apply(AP4_AtomListWriter(output)); m_MoovAtom = moov[0]; m_MoovAtom->Detach(); } if (partitions & 2) { // process the fragments, if any result = AP4_SUCCESS; AP4_Array<AP4_UI64> moof_positions, mdat_positions; moof_positions.SetItemCount(input.ItemCount()); mdat_positions.SetItemCount(input.ItemCount()); for (;;) { AP4_ContainerAtom *moof = NULL; AP4_UI32 track_index(0); #if 0 for (AP4_Cardinal streamid(0); streamid < input.ItemCount(); ++streamid) { AP4_Atom* atom = NULL; if (AP4_SUCCEEDED(input[streamid]->Tell(stream_offset)) && AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*input[streamid], atom))) { if (atom->GetType() != AP4_ATOM_TYPE_MOOF) return AP4_ERROR_INVALID_FORMAT; moof_positions[streamid] = stream_offset; mdat_positions[streamid] = stream_offset + atom->GetSize() + +AP4_ATOM_HEADER_SIZE; if (moof) { int index(0); for (; AP4_Atom* child = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom)->GetChild(AP4_ATOM_TYPE_TRAF, index++);) moof->AddChild(child->Clone()); delete atom; } else moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); NormalizeTRAF(AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof), m_StreamData[streamid].trackStart, m_StreamData[streamid].trackStart + m_StreamData[streamid].trackCount, track_index); } else delete atom; } #else double mindts(9999999999.0); AP4_Cardinal nextStream(~0); for (AP4_Cardinal track(0); track < m_TrackData.ItemCount(); ++track) if ((double)m_TrackData[track].dts / m_TrackData[track].timescale < mindts) { mindts = (double)m_TrackData[track].dts / m_TrackData[track].timescale; nextStream = m_TrackData[track].streamId; } AP4_Atom* atom = NULL; if (AP4_SUCCEEDED(result = input[nextStream]->Tell(stream_offset)) && AP4_SUCCEEDED(result = atom_factory.CreateAtomFromStream(*input[nextStream], atom))) { if (atom->GetType() != AP4_ATOM_TYPE_MOOF) return AP4_ERROR_INVALID_FORMAT; } else if (atom) return result; moof_positions[nextStream] = stream_offset; mdat_positions[nextStream] = stream_offset + atom->GetSize() + +AP4_ATOM_HEADER_SIZE; moof = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); NormalizeTRAF(AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof), m_StreamData[nextStream].trackStart, m_StreamData[nextStream].trackStart + m_StreamData[nextStream].trackCount, track_index); #endif if (!moof) break; if (AP4_FAILED(result = ProcessFragment(moof, NULL, 0, output, moof_positions, mdat_positions))) return result; delete moof; moof = NULL; } // cleanup m_TrackData.Clear(); m_StreamData.Clear(); } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_Processor::Process +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::Process(AP4_ByteStream& input, AP4_ByteStream& output, AP4_ByteStream* fragments, ProgressListener* listener, AP4_AtomFactory& atom_factory) { // read all atoms. // keep all atoms except [mdat] // keep a ref to [moov] // put [moof] atoms in a separate list AP4_AtomParent top_level; AP4_MoovAtom* moov = NULL; AP4_ContainerAtom* mfra = NULL; AP4_SidxAtom* sidx = NULL; AP4_List<AP4_AtomLocator> frags; AP4_UI64 stream_offset = 0; bool in_fragments = false; unsigned int sidx_count = 0; for (AP4_Atom* atom = NULL; AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom)); input.Tell(stream_offset)) { if (atom->GetType() == AP4_ATOM_TYPE_MDAT) { delete atom; continue; } else if (atom->GetType() == AP4_ATOM_TYPE_MOOV) { moov = AP4_DYNAMIC_CAST(AP4_MoovAtom, atom); if (fragments) break; } else if (atom->GetType() == AP4_ATOM_TYPE_MFRA) { mfra = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); continue; } else if (atom->GetType() == AP4_ATOM_TYPE_SIDX) { // don't keep the index, it is likely to be invalidated, we will recompute it later ++sidx_count; if (sidx == NULL) { sidx = AP4_DYNAMIC_CAST(AP4_SidxAtom, atom); } else { delete atom; continue; } } else if (atom->GetType() == AP4_ATOM_TYPE_SSIX) { // don't keep the index, it is likely to be invalidated delete atom; continue; } else if (!fragments && (in_fragments || atom->GetType() == AP4_ATOM_TYPE_MOOF)) { in_fragments = true; frags.Add(new AP4_AtomLocator(atom, stream_offset)); break; } top_level.AddChild(atom); } // check that we have at most one sidx (we can't deal with multi-sidx streams here if (sidx_count > 1) { top_level.RemoveChild(sidx); delete sidx; sidx = NULL; } // if we have a fragments stream, get the fragment locators from there if (fragments) { stream_offset = 0; for (AP4_Atom* atom = NULL; AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(*fragments, atom)); fragments->Tell(stream_offset)) { if (atom->GetType() == AP4_ATOM_TYPE_MDAT) { delete atom; continue; } frags.Add(new AP4_AtomLocator(atom, stream_offset)); } } // initialize the processor AP4_Result result = Initialize(top_level, input); if (AP4_FAILED(result)) return result; // process the tracks if we have a moov atom AP4_Array<AP4_SampleLocator> locators; AP4_Cardinal track_count = 0; AP4_List<AP4_TrakAtom>* trak_atoms = NULL; AP4_LargeSize mdat_payload_size = 0; AP4_SampleCursor* cursors = NULL; if (moov) { // build an array of track sample locators trak_atoms = &moov->GetTrakAtoms(); track_count = trak_atoms->ItemCount(); cursors = new AP4_SampleCursor[track_count]; m_TrackData.SetItemCount(track_count); m_StreamData.SetItemCount(1); m_StreamData[0].stream = &input; unsigned int index = 0; for (AP4_List<AP4_TrakAtom>::Item* item = trak_atoms->FirstItem(); item; item=item->GetNext()) { AP4_TrakAtom* trak = item->GetData(); // find the stsd atom AP4_ContainerAtom* stbl = AP4_DYNAMIC_CAST(AP4_ContainerAtom, trak->FindChild("mdia/minf/stbl")); if (stbl == NULL) continue; // see if there's an external data source for this track AP4_ByteStream* trak_data_stream = &input; for (AP4_List<ExternalTrackData>::Item* ditem = m_ExternalTrackData.FirstItem(); ditem; ditem=ditem->GetNext()) { ExternalTrackData* tdata = ditem->GetData(); if (tdata->m_TrackId == trak->GetId()) { trak_data_stream = tdata->m_MediaData; break; } } AP4_ContainerAtom *mvex = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moov->GetChild(AP4_ATOM_TYPE_MVEX)); AP4_TrexAtom* trex = NULL; if (mvex) { for (AP4_List<AP4_Atom>::Item* item = mvex->GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_TREX) { trex = AP4_DYNAMIC_CAST(AP4_TrexAtom, atom); if (trex && trex->GetTrackId() == trak->GetId()) break; trex = NULL; } } } // create the track handler m_TrackData[index].track_handler = CreateTrackHandler(trak, trex); m_TrackData[index].new_id = trak->GetId(); cursors[index].m_Locator.m_TrakIndex = index; cursors[index].m_Locator.m_SampleTable = new AP4_AtomSampleTable(stbl, *trak_data_stream); cursors[index].m_Locator.m_SampleIndex = 0; cursors[index].m_Locator.m_ChunkIndex = 0; if (cursors[index].m_Locator.m_SampleTable->GetSampleCount()) { cursors[index].m_Locator.m_SampleTable->GetSample(0, cursors[index].m_Locator.m_Sample); } else { cursors[index].m_EndReached = true; } index++; } // figure out the layout of the chunks for (;;) { // see which is the next sample to write AP4_UI64 min_offset = (AP4_UI64)(-1); int cursor = -1; for (unsigned int i=0; i<track_count; i++) { if (!cursors[i].m_EndReached && cursors[i].m_Locator.m_Sample.GetOffset() <= min_offset) { min_offset = cursors[i].m_Locator.m_Sample.GetOffset(); cursor = i; } } // stop if all cursors are exhausted if (cursor == -1) break; // append this locator to the layout list AP4_SampleLocator& locator = cursors[cursor].m_Locator; locators.Append(locator); // move the cursor to the next sample locator.m_SampleIndex++; if (locator.m_SampleIndex == locator.m_SampleTable->GetSampleCount()) { // mark this track as completed cursors[cursor].m_EndReached = true; } else { // get the next sample info locator.m_SampleTable->GetSample(locator.m_SampleIndex, locator.m_Sample); AP4_Ordinal skip, sdesc; locator.m_SampleTable->GetChunkForSample(locator.m_SampleIndex, locator.m_ChunkIndex, skip, sdesc); } } // update the stbl atoms and compute the mdat size int current_track = -1; int current_chunk = -1; AP4_Position current_chunk_offset = 0; AP4_Size current_chunk_size = 0; for (AP4_Ordinal i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; if ((int)locator.m_TrakIndex != current_track || (int)locator.m_ChunkIndex != current_chunk) { // start a new chunk for this track current_chunk_offset += current_chunk_size; current_chunk_size = 0; current_track = locator.m_TrakIndex; current_chunk = locator.m_ChunkIndex; locator.m_SampleTable->SetChunkOffset(locator.m_ChunkIndex, current_chunk_offset); } AP4_Size sample_size; TrackHandler* handler = m_TrackData[locator.m_TrakIndex].track_handler; if (handler) { sample_size = handler->GetProcessedSampleSize(locator.m_Sample); locator.m_SampleTable->SetSampleSize(locator.m_SampleIndex, sample_size); } else { sample_size = locator.m_Sample.GetSize(); } current_chunk_size += sample_size; mdat_payload_size += sample_size; } // process the tracks (ex: sample descriptions processing) for (AP4_Ordinal i=0; i<track_count; i++) { TrackHandler* handler = m_TrackData[i].track_handler; if (handler) handler->ProcessTrack(); } } // finalize the processor Finalize(top_level); if (!fragments) { // calculate the size of all atoms combined AP4_UI64 atoms_size = 0; top_level.GetChildren().Apply(AP4_AtomSizeAdder(atoms_size)); // see if we need a 64-bit or 32-bit mdat AP4_Size mdat_header_size = AP4_ATOM_HEADER_SIZE; if (mdat_payload_size+mdat_header_size > 0xFFFFFFFF) { // we need a 64-bit size mdat_header_size += 8; } // adjust the chunk offsets for (AP4_Ordinal i=0; i<track_count; i++) { AP4_TrakAtom* trak; trak_atoms->Get(i, trak); trak->AdjustChunkOffsets(atoms_size+mdat_header_size); } // write all atoms top_level.GetChildren().Apply(AP4_AtomListWriter(output)); // write mdat header if (mdat_payload_size) { if (mdat_header_size == AP4_ATOM_HEADER_SIZE) { // 32-bit size output.WriteUI32((AP4_UI32)(mdat_header_size+mdat_payload_size)); output.WriteUI32(AP4_ATOM_TYPE_MDAT); } else { // 64-bit size output.WriteUI32(1); output.WriteUI32(AP4_ATOM_TYPE_MDAT); output.WriteUI64(mdat_header_size+mdat_payload_size); } } } // write the samples if (moov) { if (!fragments) { #if defined(AP4_DEBUG) AP4_Position before; output.Tell(before); #endif AP4_Sample sample; AP4_DataBuffer data_in; AP4_DataBuffer data_out; for (unsigned int i=0; i<locators.ItemCount(); i++) { AP4_SampleLocator& locator = locators[i]; locator.m_Sample.ReadData(data_in); TrackHandler* handler = m_TrackData[locator.m_TrakIndex].track_handler; if (handler) { result = handler->ProcessSample(data_in, data_out); if (AP4_FAILED(result)) return result; output.Write(data_out.GetData(), data_out.GetDataSize()); } else { output.Write(data_in.GetData(), data_in.GetDataSize()); } // notify the progress listener if (listener) { listener->OnProgress(i+1, locators.ItemCount()); } } #if defined(AP4_DEBUG) AP4_Position after; output.Tell(after); AP4_ASSERT(after-before == mdat_payload_size); #endif } else m_StreamData[0].stream = fragments; // find the position of the sidx atom AP4_Position sidx_position = 0; if (sidx) { for (AP4_List<AP4_Atom>::Item* item = top_level.GetChildren().FirstItem(); item; item = item->GetNext()) { AP4_Atom* atom = item->GetData(); if (atom->GetType() == AP4_ATOM_TYPE_SIDX) { break; } sidx_position += atom->GetSize(); } } // process the fragments, if any AP4_Array<AP4_Position> moof_offsets, mdat_offsets; moof_offsets.SetItemCount(1); mdat_offsets.SetItemCount(1); while (frags.ItemCount() > 0) { for (AP4_List<AP4_AtomLocator>::Item *locator(frags.FirstItem()); locator; locator = locator->GetNext()) { AP4_ContainerAtom *moof(AP4_DYNAMIC_CAST(AP4_ContainerAtom, locator->GetData()->m_Atom)); moof_offsets[0] = locator->GetData()->m_Offset; mdat_offsets[0] = moof_offsets[0] + moof->GetSize() + AP4_ATOM_HEADER_SIZE; result = ProcessFragment(moof, sidx, sidx_position, output, moof_offsets, mdat_offsets); if (AP4_FAILED(result)) return result; } frags.DeleteReferences(); AP4_Atom* atom = NULL; input.Tell(stream_offset); if (AP4_SUCCEEDED(atom_factory.CreateAtomFromStream(input, atom))) { if (atom->GetType() == AP4_ATOM_TYPE_MOOF) frags.Add(new AP4_AtomLocator(atom, stream_offset)); else delete atom; } } // update the mfra if we have one if (mfra) { for (AP4_List<AP4_Atom>::Item* mfra_item = mfra->GetChildren().FirstItem(); mfra_item; mfra_item = mfra_item->GetNext()) { if (mfra_item->GetData()->GetType() != AP4_ATOM_TYPE_TFRA) continue; AP4_TfraAtom* tfra = AP4_DYNAMIC_CAST(AP4_TfraAtom, mfra_item->GetData()); if (tfra == NULL) continue; AP4_Array<AP4_TfraAtom::Entry>& entries = tfra->GetEntries(); AP4_Cardinal entry_count = entries.ItemCount(); for (unsigned int i = 0; i<entry_count; i++) { entries[i].m_MoofOffset = FindFragmentMapEntry(entries[i].m_MoofOffset); } } } // update and re-write the sidx if we have one if (sidx && sidx_position) { AP4_Position where = 0; output.Tell(where); output.Seek(sidx_position); result = sidx->Write(output); if (AP4_FAILED(result)) return result; output.Seek(where); } if (!fragments) { // write the mfra atom at the end if we have one if (mfra) { mfra->Write(output); } } // cleanup for (AP4_Ordinal i=0; i<track_count; i++) delete cursors[i].m_Locator.m_SampleTable; m_TrackData.Clear(); delete[] cursors; } // cleanup frags.DeleteReferences(); delete mfra; return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | AP4_Processor::ProcessFragments +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::ProcessFragments(AP4_MoovAtom* moov, AP4_List<AP4_MoofLocator>& moofs, AP4_ContainerAtom* mfra, AP4_ByteStream& input, AP4_ByteStream& output) { // FIXME: this only works for non-changing moofs for (AP4_List<AP4_MoofLocator>::Item* item = moofs.FirstItem(); item; item = item->GetNext()) { AP4_MoofLocator* locator = item->GetData(); AP4_ContainerAtom* moof = locator->m_Moof; AP4_UI64 moof_offset = locator->m_Offset; AP4_UI64 mdat_payload_offset = moof_offset+moof->GetSize()+8; AP4_MovieFragment* fragment = new AP4_MovieFragment(moof); AP4_Sample sample; AP4_DataBuffer sample_data_in; AP4_DataBuffer sample_data_out; AP4_Result result; // process all the traf atoms AP4_Array<AP4_Processor::FragmentHandler*> handlers; for (;AP4_Atom* atom = moof->GetChild(AP4_ATOM_TYPE_TRAF, handlers.ItemCount());) { AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, atom); AP4_Processor::FragmentHandler* handler = CreateFragmentHandler(traf); if (handler) result = handler->ProcessFragment(); handlers.Append(handler); } // write the moof AP4_UI64 moof_out_start = 0; output.Tell(moof_out_start); bool moof_has_changed = false; moof->Write(output); // process all track runs for (unsigned int i=0; i<handlers.ItemCount(); i++) { AP4_FragmentSampleTable* sample_table = NULL; AP4_Processor::FragmentHandler* handler = handlers[i]; // get the track ID AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof->GetChild(AP4_ATOM_TYPE_TRAF, i)); AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD, i)); // create a sample table object so we can read the sample data result = fragment->CreateSampleTable(moov, tfhd->GetTrackId(), &input, moof_offset, mdat_payload_offset, sample_table); if (AP4_FAILED(result)) return result; // compute the mdat size AP4_UI64 mdat_size = 0; for (unsigned int j=0; j<sample_table->GetSampleCount(); j++) { result = sample_table->GetSample(j, sample); if (AP4_FAILED(result)) return result; mdat_size += sample.GetSize(); } // write an mdat header if (mdat_size > 0xFFFFFFFF-8) { // we don't support large mdat fragments return AP4_ERROR_OUT_OF_RANGE; } if (mdat_size) { output.WriteUI32((AP4_UI32)(8+mdat_size)); output.WriteUI32(AP4_ATOM_TYPE_MDAT); } #if defined(AP4_DEBUG) AP4_Position before; output.Tell(before); #endif // write the mdat for (unsigned int j=0; j<sample_table->GetSampleCount(); j++) { result = sample_table->GetSample(j, sample); if (AP4_FAILED(result)) return result; sample.ReadData(sample_data_in); // process the sample data if (handler) { result = handler->ProcessSample(sample_data_in, sample_data_out); if (AP4_FAILED(result)) return result; // write the sample data result = output.Write(sample_data_out.GetData(), sample_data_out.GetDataSize()); if (AP4_FAILED(result)) return result; // give the handler a chance to update the atoms result = handler->FinishFragment(); if (AP4_SUCCEEDED(result)) moof_has_changed = true; } else { // write the sample data (unmodified) result = output.Write(sample_data_in.GetData(), sample_data_in.GetDataSize()); if (AP4_FAILED(result)) return result; } } #if defined(AP4_DEBUG) AP4_Position after; output.Tell(after); AP4_ASSERT(after-before == mdat_size); #endif delete sample_table; } // update the moof if needed AP4_UI64 mdat_out_end = 0; output.Tell(mdat_out_end); if (moof_has_changed) { output.Seek(moof_out_start); moof->Write(output); output.Seek(mdat_out_end); } // update the mfra if we have one if (mfra) { for (AP4_List<AP4_Atom>::Item* mfra_item = mfra->GetChildren().FirstItem(); mfra_item; mfra_item = mfra_item->GetNext()) { if (mfra_item->GetData()->GetType() != AP4_ATOM_TYPE_TFRA) continue; AP4_TfraAtom* tfra = AP4_DYNAMIC_CAST(AP4_TfraAtom, mfra_item->GetData()); if (tfra == NULL) continue; AP4_Array<AP4_TfraAtom::Entry>& entries = tfra->GetEntries(); AP4_Cardinal entry_count = entries.ItemCount(); for (unsigned int i=0; i<entry_count; i++) { if (entries[i].m_MoofOffset == locator->m_Offset) { entries[i].m_MoofOffset = moof_out_start; } } } } delete fragment; } return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | Fragment +---------------------------------------------------------------------*/ static void Fragment(AP4_File& input_file, AP4_ByteStream& output_stream, unsigned int fragment_duration, AP4_UI32 timescale) { AP4_Result result; AP4_Movie* input_movie = input_file.GetMovie(); if (input_movie == NULL) { fprintf(stderr, "ERROR: no moov found in the input file\n"); return; } // create the output file object AP4_Movie* output_movie = new AP4_Movie(1000); // create an mvex container AP4_ContainerAtom* mvex = new AP4_ContainerAtom(AP4_ATOM_TYPE_MVEX); AP4_MehdAtom* mehd = new AP4_MehdAtom(0); mvex->AddChild(mehd); // create a cusor list to keep track of the tracks we will read from AP4_Array<TrackCursor*> cursors; // add an output track for each track in the input file for (AP4_List<AP4_Track>::Item* track_item = input_movie->GetTracks().FirstItem(); track_item; track_item = track_item->GetNext()) { AP4_Track* track = track_item->GetData(); TrackCursor* cursor = new TrackCursor(); cursor->m_TrackId = track->GetId(); cursor->m_Tfra->SetTrackId(track->GetId()); cursors.Append(cursor); // create a sample table (with no samples) to hold the sample description AP4_SyntheticSampleTable* sample_table = new AP4_SyntheticSampleTable(); for (unsigned int i=0; i<track->GetSampleDescriptionCount(); i++) { AP4_SampleDescription* sample_description = track->GetSampleDescription(i); sample_table->AddSampleDescription(sample_description, false); } // create the track AP4_Track* output_track = new AP4_Track(track->GetType(), sample_table, cursor->m_TrackId, timescale?timescale:1000, AP4_ConvertTime(track->GetDuration(), input_movie->GetTimeScale(), timescale?timescale:1000), timescale?timescale:track->GetMediaTimeScale(), 0,//track->GetMediaDuration(), track->GetTrackLanguage(), track->GetWidth(), track->GetHeight()); output_movie->AddTrack(output_track); result = cursor->SetTrack(track); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to read sample (%d)\n", result); return; } // add a trex entry to the mvex container AP4_TrexAtom* trex = new AP4_TrexAtom(cursor->m_TrackId, 1, 0, 0, 0); mvex->AddChild(trex); } if (cursors.ItemCount() == 0) { fprintf(stderr, "ERROR: no track found\n"); return; } for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (cursors[i]->m_Track->GetType() == AP4_Track::TYPE_VIDEO) { cursors[i]->m_TargetDuration = AP4_ConvertTime(fragment_duration>AP4_FRAGMENTER_FRAGMENT_DURATION_TOLERANCE ? fragment_duration-AP4_FRAGMENTER_FRAGMENT_DURATION_TOLERANCE : 0, 1000, cursors[i]->m_Track->GetMediaTimeScale()); } else { cursors[i]->m_TargetDuration = AP4_ConvertTime(fragment_duration, 1000, cursors[i]->m_Track->GetMediaTimeScale()); } } // update the mehd duration mehd->SetDuration(output_movie->GetDuration()); // the mvex container to the moov container output_movie->GetMoovAtom()->AddChild(mvex); // write the ftyp atom AP4_FtypAtom* ftyp = input_file.GetFileType(); if (ftyp) { ftyp->Write(output_stream); } // write the moov atom output_movie->GetMoovAtom()->Write(output_stream); // write all the fragments unsigned int sequence_number = 1; for(;;) { // select the next track to read from TrackCursor* cursor = NULL; AP4_UI64 min_dts = (AP4_UI64)(-1); for (unsigned int i=0; i<cursors.ItemCount(); i++) { if (cursors[i]->m_Eos) continue; AP4_UI64 dts = AP4_ConvertTime(cursors[i]->m_Sample.GetDts(), cursors[i]->m_Track->GetMediaTimeScale(), AP4_FRAGMENTER_BASE_TIMESCALE); if (dts < min_dts) { min_dts = dts; cursor = cursors[i]; } } if (cursor == NULL) break; // all done // compute the target end for the segment cursor->m_EndDts = cursor->m_Sample.GetDts()+cursor->m_TargetDuration; // emit a fragment for the selected track if (Options.verbosity > 0) { printf("fragment: track ID %d ", cursor->m_Track->GetId()); } // remember the time and position of this fragment AP4_Position moof_offset = 0; output_stream.Tell(moof_offset); cursor->m_Tfra->AddEntry(cursor->m_Timestamp, moof_offset); // decide which sample description index to use // (this is not very sophisticated, we only look at the sample description // index of the first sample in the group, which may not be correct. This // should be fixed later) unsigned int sample_desc_index = cursor->m_Sample.GetDescriptionIndex(); unsigned int tfhd_flags = AP4_TFHD_FLAG_DEFAULT_BASE_IS_MOOF; if (sample_desc_index > 0) { tfhd_flags |= AP4_TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT; } if (cursor->m_Track->GetType() == AP4_Track::TYPE_VIDEO) { tfhd_flags |= AP4_TFHD_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT; } // setup the moof structure AP4_ContainerAtom* moof = new AP4_ContainerAtom(AP4_ATOM_TYPE_MOOF); AP4_MfhdAtom* mfhd = new AP4_MfhdAtom(sequence_number++); moof->AddChild(mfhd); AP4_ContainerAtom* traf = new AP4_ContainerAtom(AP4_ATOM_TYPE_TRAF); AP4_TfhdAtom* tfhd = new AP4_TfhdAtom(tfhd_flags, cursor->m_TrackId, 0, sample_desc_index+1, 0, 0, 0); if (tfhd_flags & AP4_TFHD_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT) { tfhd->SetDefaultSampleFlags(0x1010000); // sample_is_non_sync_sample=1, sample_depends_on=1 (not I frame) } traf->AddChild(tfhd); AP4_TfdtAtom* tfdt = new AP4_TfdtAtom(1, cursor->m_Timestamp); traf->AddChild(tfdt); AP4_UI32 trun_flags = AP4_TRUN_FLAG_DATA_OFFSET_PRESENT | AP4_TRUN_FLAG_SAMPLE_DURATION_PRESENT | AP4_TRUN_FLAG_SAMPLE_SIZE_PRESENT; AP4_UI32 first_sample_flags = 0; if (cursor->m_Track->GetType() == AP4_Track::TYPE_VIDEO) { trun_flags |= AP4_TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT; first_sample_flags = 0x2000000; // sample_depends_on=2 (I frame) } AP4_TrunAtom* trun = new AP4_TrunAtom(trun_flags, 0, first_sample_flags); traf->AddChild(trun); moof->AddChild(traf); // decide which samples go in this fragment AP4_Array<AP4_UI32> sample_indexes; unsigned int sample_count = 0; AP4_Array<AP4_TrunAtom::Entry> trun_entries; AP4_UI32 mdat_size = AP4_ATOM_HEADER_SIZE; for (;;) { // if we have one non-zero CTS delta, we'll need to express it if (cursor->m_Sample.GetCtsDelta()) { trun->SetFlags(trun->GetFlags() | AP4_TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT); } // add one sample trun_entries.SetItemCount(sample_count+1); AP4_TrunAtom::Entry& trun_entry = trun_entries[sample_count]; trun_entry.sample_duration = timescale? (AP4_UI32)AP4_ConvertTime(cursor->m_Sample.GetDuration(), cursor->m_Track->GetMediaTimeScale(), timescale): cursor->m_Sample.GetDuration(); trun_entry.sample_size = cursor->m_Sample.GetSize(); trun_entry.sample_composition_time_offset = timescale? (AP4_UI32)AP4_ConvertTime(cursor->m_Sample.GetCtsDelta(), cursor->m_Track->GetMediaTimeScale(), timescale): cursor->m_Sample.GetCtsDelta(); sample_indexes.SetItemCount(sample_count+1); sample_indexes[sample_count] = cursor->m_SampleIndex; mdat_size += trun_entry.sample_size; // next sample cursor->m_Timestamp += trun_entry.sample_duration; cursor->m_SampleIndex++; sample_count++; if (cursor->m_SampleIndex >= cursor->m_Track->GetSampleCount()) { cursor->m_Eos = true; AP4_UI64 end_dts = cursor->m_Sample.GetDts()+cursor->m_Sample.GetDuration(); cursor->m_Sample.Reset(); cursor->m_Sample.SetDts(end_dts); break; } result = cursor->m_Track->GetSample(cursor->m_SampleIndex, cursor->m_Sample); if (AP4_FAILED(result)) { cursor->m_Eos = true; AP4_UI64 end_dts = cursor->m_Sample.GetDts()+cursor->m_Sample.GetDuration(); cursor->m_Sample.Reset(); cursor->m_Sample.SetDts(end_dts); break; } if (cursor->m_Sample.IsSync()) { if (cursor->m_Sample.GetDts() >= cursor->m_EndDts) { break; // done with this segment } } } if (Options.verbosity) { printf(" %d samples\n", sample_count); } // update moof and children trun->SetEntries(trun_entries); trun->SetDataOffset((AP4_UI32)moof->GetSize()+AP4_ATOM_HEADER_SIZE); // write moof moof->Write(output_stream); // write mdat output_stream.WriteUI32(mdat_size); output_stream.WriteUI32(AP4_ATOM_TYPE_MDAT); AP4_Sample sample; AP4_DataBuffer sample_data; for (unsigned int i=0; i<sample_indexes.ItemCount(); i++) { result = cursor->m_Track->ReadSample(sample_indexes[i], sample, sample_data); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to read sample %d (%d)\n", sample_indexes[i], result); return; } result = output_stream.Write(sample_data.GetData(), sample_data.GetDataSize()); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to write sample data (%d)\n", result); return; } } // cleanup delete moof; } // create an mfra container and write out the index AP4_ContainerAtom mfra(AP4_ATOM_TYPE_MFRA); for (unsigned int i=0; i<cursors.ItemCount(); i++) { mfra.AddChild(cursors[i]->m_Tfra); cursors[i]->m_Tfra = NULL; } AP4_MfroAtom* mfro = new AP4_MfroAtom((AP4_UI32)mfra.GetSize()+16); mfra.AddChild(mfro); result = mfra.Write(output_stream); if (AP4_FAILED(result)) { fprintf(stderr, "ERROR: failed to write 'mfra' (%d)\n", result); return; } // cleanup for (unsigned int i=0; i<cursors.ItemCount(); i++) { delete cursors[i]; } delete output_movie; }
/*---------------------------------------------------------------------- | AP4_Processor::ProcessFragments +---------------------------------------------------------------------*/ AP4_Result AP4_Processor::ProcessFragment( AP4_ContainerAtom* moof, AP4_SidxAtom* sidx, AP4_Position sidx_position, AP4_ByteStream& output, AP4_Array<AP4_Position>& moof_positions, AP4_Array<AP4_Position>& mdat_positions) { unsigned int fragment_index = 0; //AP4_UI64 mdat_payload_offset = atom_offset+atom->GetSize()+AP4_ATOM_HEADER_SIZE; AP4_Sample sample; AP4_DataBuffer sample_data_in; AP4_DataBuffer sample_data_out; AP4_Result result = AP4_SUCCESS; // parse the moof //AP4_MovieFragment* fragment = new AP4_MovieFragment(moof); // process all the traf atoms AP4_Array<AP4_Processor::FragmentHandler*> handlers; AP4_Array<AP4_FragmentSampleTable*> sample_tables; for (; AP4_Atom* child = moof->GetChild(AP4_ATOM_TYPE_TRAF, handlers.ItemCount());) { AP4_TrafAtom* traf = AP4_DYNAMIC_CAST(AP4_TrafAtom, child); PERTRACK &track_data(m_TrackData[traf->GetInternalTrackId()]); AP4_TrakAtom* trak = track_data.track_handler->GetTrakAtom(); AP4_TrexAtom* trex = track_data.track_handler->GetTrexAtom(); // create the handler for this traf AP4_Processor::FragmentHandler* handler = CreateFragmentHandler(trak, trex, traf, *(m_StreamData[track_data.streamId].stream), moof_positions[track_data.streamId]); if (handler) { result = handler->ProcessFragment(); if (AP4_FAILED(result)) return result; } handlers.Append(handler); // create a sample table object so we can read the sample data AP4_FragmentSampleTable* sample_table = new AP4_FragmentSampleTable( traf, trex, traf->GetInternalTrackId(), m_StreamData[track_data.streamId].stream, moof_positions[traf->GetInternalTrackId()], mdat_positions[traf->GetInternalTrackId()], 0); sample_tables.Append(sample_table); // let the handler look at the samples before we process them if (handler) result = handler->PrepareForSamples(sample_table); if (AP4_FAILED(result)) return result; } output.Buffer(); // write the moof AP4_UI64 moof_out_start = 0; output.Tell(moof_out_start); moof->Write(output); // remember the location of this fragment FragmentMapEntry map_entry = { moof_positions[0], moof_out_start }; fragment_map_.Append(map_entry); // write an mdat header AP4_Position mdat_out_start; AP4_UI64 mdat_size = AP4_ATOM_HEADER_SIZE; output.Tell(mdat_out_start); output.WriteUI32(0); output.WriteUI32(AP4_ATOM_TYPE_MDAT); // process all track runs for (unsigned int i=0; i<handlers.ItemCount(); i++) { AP4_Processor::FragmentHandler* handler = handlers[i]; // get the track ID AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof->GetChild(AP4_ATOM_TYPE_TRAF, i)); if (traf == NULL) continue; AP4_TfhdAtom* tfhd = AP4_DYNAMIC_CAST(AP4_TfhdAtom, traf->GetChild(AP4_ATOM_TYPE_TFHD)); // compute the base data offset AP4_UI64 base_data_offset; if (tfhd->GetFlags() & AP4_TFHD_FLAG_BASE_DATA_OFFSET_PRESENT) { base_data_offset = mdat_out_start+AP4_ATOM_HEADER_SIZE; } else { base_data_offset = moof_out_start; } // build a list of all trun atoms AP4_Array<AP4_TrunAtom*> truns; for (AP4_List<AP4_Atom>::Item* child_item = traf->GetChildren().FirstItem(); child_item; child_item = child_item->GetNext()) { AP4_Atom* child_atom = child_item->GetData(); if (child_atom->GetType() == AP4_ATOM_TYPE_TRUN) { AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, child_atom); truns.Append(trun); } } AP4_Ordinal trun_index = 0; AP4_Ordinal trun_sample_index = 0; AP4_TrunAtom* trun = truns[0]; trun->SetDataOffset((AP4_SI32)((mdat_out_start+mdat_size)-base_data_offset)); // write the mdat for (unsigned int j=0; j<sample_tables[i]->GetSampleCount(); j++, trun_sample_index++) { // advance the trun index if necessary if (trun_sample_index >= trun->GetEntries().ItemCount()) { trun = truns[++trun_index]; trun->SetDataOffset((AP4_SI32)((mdat_out_start+mdat_size)-base_data_offset)); trun_sample_index = 0; } // get the next sample result = sample_tables[i]->GetSample(j, sample); if (AP4_FAILED(result)) return result; sample.ReadData(sample_data_in); m_TrackData[sample_tables[i]->GetInteralTrackId()].dts = sample.GetDts(); // process the sample data if (handler) { result = handler->ProcessSample(sample_data_in, sample_data_out); if (AP4_FAILED(result)) return result; // write the sample data result = output.Write(sample_data_out.GetData(), sample_data_out.GetDataSize()); if (AP4_FAILED(result)) return result; // update the mdat size mdat_size += sample_data_out.GetDataSize(); // update the trun entry trun->UseEntries()[trun_sample_index].sample_size = sample_data_out.GetDataSize(); } else { // write the sample data (unmodified) result = output.Write(sample_data_in.GetData(), sample_data_in.GetDataSize()); if (AP4_FAILED(result)) return result; // update the mdat size mdat_size += sample_data_in.GetDataSize(); } } if (handler) { // update the tfhd header if (tfhd->GetFlags() & AP4_TFHD_FLAG_BASE_DATA_OFFSET_PRESENT) { tfhd->SetBaseDataOffset(mdat_out_start+AP4_ATOM_HEADER_SIZE); } if (tfhd->GetFlags() & AP4_TFHD_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT) { tfhd->SetDefaultSampleSize(trun->GetEntries()[0].sample_size); } // give the handler a chance to update the atoms handler->FinishFragment(); } } // update the mdat header AP4_Position mdat_out_end; output.Tell(mdat_out_end); #if defined(AP4_DEBUG) AP4_ASSERT(mdat_out_end-mdat_out_start == mdat_size); #endif if (AP4_FAILED(result = output.Seek(mdat_out_start))) return result; output.WriteUI32((AP4_UI32)mdat_size); output.Seek(mdat_out_end); // update the moof if needed if (AP4_FAILED(result = output.Seek(moof_out_start))) return result; moof->Write(output); output.Seek(mdat_out_end); // update the sidx if we have one if (sidx && fragment_index < sidx->GetReferences().ItemCount()) { if (fragment_index == 0) { sidx->SetFirstOffset(moof_out_start-(sidx_position+sidx->GetSize())); } AP4_LargeSize fragment_size = mdat_out_end-moof_out_start; AP4_SidxAtom::Reference& sidx_ref = sidx->UseReferences()[fragment_index]; sidx_ref.m_ReferencedSize = (AP4_UI32)fragment_size; } // cleanup //delete fragment; for (unsigned int i=0; i<handlers.ItemCount(); i++) { delete handlers[i]; } for (unsigned int i=0; i<sample_tables.ItemCount(); i++) { delete sample_tables[i]; } if (AP4_FAILED(result = output.Flush())) return result; return AP4_SUCCESS; }
/*---------------------------------------------------------------------- | 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; }
/*---------------------------------------------------------------------- | 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; }