Switching tracks within an input file

Updated: April 19, 2023

An input file can contain multiple audio, video, and subtitle streams. Here, we call each stream a track, which is different than an input media file or a playlist entry referenced by the input. During playback, mm-renderer allows you to switch tracks within the input file to play a different audio or video stream or display different subtitles.

Note: Switching tracks isn't supported in the shipped version of mm-renderer. If you need this capability, contact QNX Customer Support.

The sample program below takes a dictionary received in a METADATA event and reads properties of individual audio streams in the input. Then, the application displays information on all audio tracks to the user, who then selects a particular track. In response, the application sets the appropriate input parameter to switch tracks.

/* This code would likely be in an event-processing loop, just after
   a call to mmr_event_wait() */
if ( eventInfoPtr = mmr_event_get( my_context ) ) {
    switch (eventInfoPtr->type) {
    ...
    case MMR_EVENT_METADATA: 
        displayAudioTrackProperties( eventInfoPtr->data );
        break;
    ...
    }
}

void displayAudioTrackProperties( const strm_dict_t* metadata_dict )
{
    strm_dict_t* trackInfo;

    for (int ndx = 0; (trackInfo = 
            mmr_metadata_split( metadata_dict, "audio", ndx) ) != NULL;
         ++ndx) 
    {
        /* Now we search the dictionary for audio track properties and
           display them so the user can select a track for playback */
        strm_dict_find_value( trackInfo, "sample_rate" );
        ...
        strm_dict_destroy( trackInfo );
    }
}

After calling mmr_event_get() and finding the type of the latest event to be MMR_EVENT_METADATA, we can extract the metadata properties from the data dictionary in the returned structure. Each audio, video, and subtitle track in a media file is described by its own attribute in this dictionary. The attribute's value is a string containg a list of properties. To avoid manually parsing this string for every audio track, we call mmr_metadata_split() in a loop, to have the mm-renderer API parse the string.

In each call, we pass in the data dictionary handle, the audio string (as the track type), and the loop index, which indicates the track for which we're retrieving properties. The function returns a new dictionary containing these properties; for lists of the properties for audio and other types, see the mmr_metadata_split() reference. We read properties from the dictionary and when we're finished displaying them (and hence, using them), we destroy the dictionary for that audio track. We stay in the loop until mmr_metadata_split() returns a NULL pointer, meaning no more audio tracks are found.

The code to display the properties or handle the UI event triggered by the user's selection of a new audio track isn't shown in our example; instead, we provide the code for setting the right input parameter to enable the new track for playback:
int enableAudioTrack( int idx )
{
    // Convert the integer value containing the index to a string
    char idx_str[20];
    itoa( idx, idx_str, 10 );

    // Store the index of the new audio track to play
    if ( strm_dict_set( dict_input_params, 
                        "audio_index", 
                        idx_str ) < 0 ) {
        /* Error-handling code goes here */
        return EXIT_FAILURE;
    }
    else if ( mmr_input_parameters( ctxt_audioplay,
                                    dict_input_params ) < 0 ) {
        /* Error-handling code goes here */
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

If the type of the attached input is track, we use mmr_input_parameters() to set the audio_index parameter. If the input were of type playlist, we would use mmr_track_parameters(), which requires us to pass in the playlist index of the entry (i.e., media file) in which we're switching audio tracks.