/*----------------------------------------------------------------------
|   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();
    }
}
Exemple #2
0
/*----------------------------------------------------------------------
|   CheckWarning
+---------------------------------------------------------------------*/
static bool
CheckWarning(AP4_ByteStream& stream, AP4_ProtectionKeyMap& key_map, Method method)
{
    AP4_File file(stream,
                  AP4_DefaultAtomFactory::Instance,
                  true);
    AP4_Movie* movie = file.GetMovie();
    if (!movie) {
        if (method != METHOD_MPEG_CENC && method != METHOD_PIFF_CBC && method != METHOD_PIFF_CTR) {
            fprintf(stderr, "WARNING: no movie atom found in input file\n");
            return false;
        }
    }

    bool warning = false;
    switch (method) {
        case METHOD_MPEG_CENC:
        case METHOD_PIFF_CBC:
        case METHOD_PIFF_CTR:
            if (movie && !movie->HasFragments()) {
                fprintf(stderr, "WARNING: MPEG-CENC method only applies to fragmented files\n");
                warning = true;
            }
            break;
        default:
            break;
    }

    if (movie) {
        for (unsigned int i=0; i<movie->GetTracks().ItemCount(); i++) {
            AP4_Track* track;
            AP4_Result result = movie->GetTracks().Get(i, track);
            if (AP4_FAILED(result)) return false;
            const AP4_DataBuffer* key = key_map.GetKey(track->GetId());
            if (key == NULL) {
                fprintf(stderr, "WARNING: track ID %d will not be encrypted\n", track->GetId());
                warning = true;
            }
        }
    }
    
    stream.Seek(0);
    return warning;
}
Exemple #3
0
/*----------------------------------------------------------------------
|       DumpTrackData
+---------------------------------------------------------------------*/
void
DumpTrackData(AP4_File* mp4_file, AP4_UI32 track_id, AP4_ByteStream* track_data)
{
    // get the track
    AP4_Track* track = NULL;
    AP4_Result result = mp4_file->GetTrack(track_id, track);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "track not found (id = %d)", track_id);
        return;
    }

    // write the data
    AP4_Sample sample;
    AP4_DataBuffer sample_data;
    AP4_Ordinal index = 0;
    while (AP4_SUCCEEDED(track->ReadSample(index, sample, sample_data))) {
        track_data->Write(sample_data.GetData(), sample_data.GetDataSize());
        index++;
    }
}
/*----------------------------------------------------------------------
|   AP4_HintTrackReader::AP4_HintTrackReader
+---------------------------------------------------------------------*/
AP4_HintTrackReader::AP4_HintTrackReader(AP4_Track& hint_track, 
                                         AP4_Movie& movie,
                                         AP4_UI32   ssrc) :
    m_HintTrack(hint_track),
    m_MediaTrack(NULL),
    m_MediaTimeScale(0),
    m_RtpSampleData(NULL),
    m_Ssrc(ssrc),
    m_SampleIndex(0),
    m_PacketIndex(0),
    m_RtpSequenceStart(0),
    m_RtpTimeStampStart(0),
    m_RtpTimeScale(0)
{
    // get the media track
    AP4_TrakAtom* hint_trak_atom = hint_track.UseTrakAtom();
    AP4_Atom* atom = hint_trak_atom->FindChild("tref/hint");
    if (atom != NULL) {
        AP4_UI32 media_track_id = AP4_DYNAMIC_CAST(AP4_TrefTypeAtom, atom)->GetTrackIds()[0];
        m_MediaTrack = movie.GetTrack(media_track_id);

        // get the media time scale
        m_MediaTimeScale = m_MediaTrack->GetMediaTimeScale();
    }

    // initiate random generator
    srand((int)time(NULL));

    // rtp sequence start init TODO!!
    m_RtpSequenceStart = (AP4_UI16)(rand()&0xFFFF);

    // rtp timestamp start init TODO!!
    m_RtpTimeStampStart = rand();

    // rtp time scale
    atom = hint_trak_atom->FindChild("mdia/minf/stbl/rtp /tims");
    if (atom) {
        AP4_TimsAtom* tims = AP4_DYNAMIC_CAST(AP4_TimsAtom, atom);
        m_RtpTimeScale = tims->GetTimeScale();
    }

    // generate a random ssrc if = 0
    if (m_Ssrc == 0) {
        m_Ssrc = rand();
    }

    // get the first sample
    GetRtpSample(0);
}
/*----------------------------------------------------------------------
|   AP4_HintTrackReader::Create
+---------------------------------------------------------------------*/
AP4_Result
AP4_HintTrackReader::Create(AP4_Track&            hint_track, 
                            AP4_Movie&            movie, 
                            AP4_UI32              ssrc,
                            AP4_HintTrackReader*& reader)
{
    // default value
    reader = NULL;
    
    // check the type
    if (hint_track.GetType() != AP4_Track::TYPE_HINT) {
        return AP4_ERROR_INVALID_TRACK_TYPE;
    }
    
    // create a new object
    reader = new AP4_HintTrackReader(hint_track, movie, ssrc);
    
    return AP4_SUCCESS;
}
/*----------------------------------------------------------------------
|   ReadSample
+---------------------------------------------------------------------*/
static AP4_Result
ReadSample(SampleReader&   reader, 
           AP4_Track&      track,
           AP4_Sample&     sample,
           AP4_DataBuffer& sample_data, 
           double&         ts,
           bool&           eos)
{
    AP4_Result result = reader.ReadSample(sample, sample_data);
    if (AP4_FAILED(result)) {
        if (result == AP4_ERROR_EOS) {
            eos = true;
        } else {
            return result;
        }
    }
    ts = (double)sample.GetDts()/(double)track.GetMediaTimeScale();
    
    return AP4_SUCCESS;
}
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc < 2) {
        PrintUsageAndExit();
    }

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

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

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

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

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

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

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

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

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

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

    return 0;
}
/*----------------------------------------------------------------------
|   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;
}
/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc < 2) {
        PrintUsageAndExit();
    }
    
    // default options
    Options.verbose                = false;
    Options.input                  = NULL;
    Options.init_segment_name      = AP4_SPLIT_DEFAULT_INIT_SEGMENT_NAME;
    Options.media_segment_name     = AP4_SPLIT_DEFAULT_MEDIA_SEGMENT_NAME;
    Options.pattern_params         = AP4_SPLIT_DEFAULT_PATTERN_PARAMS;
    Options.start_number           = 1;
    Options.track_id               = 0;
    Options.audio_only             = false;
    Options.video_only             = false;
    Options.init_only              = false;
    Options.track_filter           = 0;
    
    // parse command line
    AP4_Result result;
    char** args = argv+1;
    while (const char* arg = *args++) {
        if (!strcmp(arg, "--verbose")) {
            Options.verbose = true;
        } else if (!strcmp(arg, "--init-segment")) {
            if (*args == NULL) {
                fprintf(stderr, "ERROR: missing argument after --init-segment option\n");
                return 1;
            }
            Options.init_segment_name = *args++;
        } else if (!strcmp(arg, "--media-segment")) {
            if (*args == NULL) {
                fprintf(stderr, "ERROR: missing argument after --media-segment option\n");
                return 1;
            }
            Options.media_segment_name = *args++;
        } else if (!strcmp(arg, "--pattern-parameters")) {
            if (*args == NULL) {
                fprintf(stderr, "ERROR: missing argument after --pattern-params option\n");
                return 1;
            }
            Options.pattern_params = *args++;
        } else if (!strcmp(arg, "--track-id")) {
            Options.track_id = strtoul(*args++, NULL, 10);
        } else if (!strcmp(arg, "--start-number")) {
            Options.start_number = strtoul(*args++, NULL, 10);
        } else if (!strcmp(arg, "--init-only")) {
            Options.init_only = true;
        } else if (!strcmp(arg, "--audio")) {
            Options.audio_only = true;
        } else if (!strcmp(arg, "--video")) {
            Options.video_only = true;
        } else if (Options.input == NULL) {
            Options.input = arg;
        } else {
            fprintf(stderr, "ERROR: unexpected argument\n");
            return 1;
        }
    }

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

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

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

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

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

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

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

						AP4_FragmentSampleTable* sampleTable = track->GetFragmentSampleTable();

						AP4_Cardinal sample_count = 0;
						for (AP4_List<AP4_Atom>::Item* child_item = traf->GetChildren().FirstItem();
													   child_item;
													   child_item = child_item->GetNext()) {
							AP4_Atom* child_atom = child_item->GetData();
							if (child_atom->GetType() == AP4_ATOM_TYPE_TRUN) {
								AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, child_atom);
								if (trun) {
									sample_count += trun->GetEntries().ItemCount();
								}
							}
						}

						if (!sample_count) {
							return;
						}

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

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

						AP4_UI64 dts_origin = tfdt ? tfdt->GetBaseMediaDecodeTime() : 0;
						for (AP4_List<AP4_Atom>::Item* child_item = traf->GetChildren().FirstItem();
													   child_item;
													   child_item = child_item->GetNext()) {
							AP4_Atom* child_atom = child_item->GetData();
							if (child_atom->GetType() == AP4_ATOM_TYPE_TRUN) {
								AP4_TrunAtom* trun = AP4_DYNAMIC_CAST(AP4_TrunAtom, child_atom);
								if (trun) {
									sampleTable->AddTrun(trun, tfhd, trex, stream, dts_origin, moof_offset, mdat_payload_offset);
								}
							}
						}
					}
				}
			}
		}
	}
}
Exemple #11
0
/*----------------------------------------------------------------------
|   Fragment
+---------------------------------------------------------------------*/
static void
Fragment(AP4_File&       input_file,
         AP4_ByteStream& output_stream,
         unsigned int    fragment_duration,
         AP4_UI32        timescale)
{
    AP4_Result result;
    
    AP4_Movie* input_movie = input_file.GetMovie();
    if (input_movie == NULL) {
        fprintf(stderr, "ERROR: no moov found in the input file\n");
        return;
    }

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

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

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

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

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

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

                break;
            }
            if (cursor->m_Sample.IsSync()) {
                if (cursor->m_Sample.GetDts() >= cursor->m_EndDts) {
                    break; // done with this segment
                }
            }
        }
        if (Options.verbosity) {
            printf(" %d samples\n", sample_count);
        }
                
        // update moof and children
        trun->SetEntries(trun_entries);
        trun->SetDataOffset((AP4_UI32)moof->GetSize()+AP4_ATOM_HEADER_SIZE);
        
        // write moof
        moof->Write(output_stream);
        
        // write mdat
        output_stream.WriteUI32(mdat_size);
        output_stream.WriteUI32(AP4_ATOM_TYPE_MDAT);
        AP4_Sample     sample;
        AP4_DataBuffer sample_data;
        for (unsigned int i=0; i<sample_indexes.ItemCount(); i++) {
            result = cursor->m_Track->ReadSample(sample_indexes[i], sample, sample_data);
            if (AP4_FAILED(result)) {
                fprintf(stderr, "ERROR: failed to read sample %d (%d)\n", sample_indexes[i], result);
                return;
            }
            result = output_stream.Write(sample_data.GetData(), sample_data.GetDataSize());
            if (AP4_FAILED(result)) {
                fprintf(stderr, "ERROR: failed to write sample data (%d)\n", result);
                return;
            }
        }
        
        // cleanup
        delete moof;
    }
    
    // create an mfra container and write out the index
    AP4_ContainerAtom mfra(AP4_ATOM_TYPE_MFRA);
    for (unsigned int i=0; i<cursors.ItemCount(); i++) {
        mfra.AddChild(cursors[i]->m_Tfra);
        cursors[i]->m_Tfra = NULL;
    }
    AP4_MfroAtom* mfro = new AP4_MfroAtom((AP4_UI32)mfra.GetSize()+16);
    mfra.AddChild(mfro);
    result = mfra.Write(output_stream);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: failed to write 'mfra' (%d)\n", result);
        return;
    }
    
    // cleanup
    for (unsigned int i=0; i<cursors.ItemCount(); i++) {
        delete cursors[i];
    }
    delete output_movie;
}