Microsoft DirectX 8.0

Writing a DVD Application in C++

This article describes the main steps in creating a DVD application in C++. Most of the code examples presented here are from the DirectShow 8.0 DVD sample application, located in the sample directory in the DirectShow SDK. This article contains the following sections. For background information on basic general concepts in DVD, see DVD Basics.

Writing a DVD Application: The Basics

When writing a DVD application, you supply the code for the user interface, and call into the Microsoft® DirectShow® application programming interface (API) to issue all DVD playback and navigation commands. This involves calling the Component Object Model (COM) CoCreateInstance method on the DVDGraphBuilder object, which does the actual work of creating the DVD filter graph. After the filter graph is created, you can obtain pointers to the IDvdControl2 and IDvdInfo2 interfaces exposed by the DVD Navigator source filter. After you have the interface pointers, you can connect your user interface to the DVD navigation commands such as PlayTitle, Pause, and so on. This basic process is quite simple and is similar to the creation of other types of DirectShow applications.

DVD Filter Graph Configuration

Generally, application developers don't need to be concerned with the details of the DVD filter graph because it is created automatically. This section is provided for those who want an idea of what is going on behind the scenes. (See the Filter Reference section if you require more detail on the individual filters referred to in this article.)

The following illustration shows a typical DVD filter graph.

DVD Filter Graph

The DVD Navigator is the first filter in a DVD playback filter graph; it performs several tasks:

On the audio stream, the DVD Navigator connects downstream to an audio decoder, which connects to the Default DirectShow Device filter, the default audio renderer. On the video and subpicture streams, the downstream filters are the third-party video decoder, the Overlay Mixer, and the Video Renderer. If your application will handle line 21 closed-captioned data, you must add the DirectShow Line 21 Decoder filter to the graph. This involves a single method call; the filter will be connected automatically between the video decoder and the Overlay Mixer.

Your application communicates with and controls the DVD Navigator through the custom interfaces that the DVD Navigator exposes: IDvdControl2—the "set" methods—and IDvdInfo2—the "get" methods. It also must communicate with the filter graph manager (through IMediaControl) to stop, start, and otherwise control the graph. You might also need to control other individual filters, such as the Overlay Mixer filter for switching between windowed and full-screen display. For more information, see IMixerPinConfig2. The exact configuration of the graph will vary depending on what type of MPEG-2 decoder you have installed, whether you need to handle line 21 closed-captioned data, and other factors.

Building the DVD Filter Graph

The recommended way to build the DVD playback filter graph is to have a DvdGraphBuilder object do it for you automatically. This approach is demonstrated below and in the DVD sample application. If you need to build your DVD filter graph manually, you can do so by following the basic rules of graph building discussed elsewhere in the DirectShow documentation. Generally, you should not manually add, remove, connect, or disconnect individual filters in the graph created by the DvdGraphBuilder, because doing so might confuse the cleanup code.

After creating an instance of the DvdGraphBuilder object, an application can simply call its IDvdGraphBuilder::RenderDvdVideoVolume member function, which builds the filter graph from the available DirectShow filters and MPEG-2 decoders on the local system. For more information, see Building the Filter Graph.

After the filter graph is built, your application can obtain the pointers it needs to control the DVD Navigator, the Filter Graph Manager, and the video window. The basic steps, with error-checking and other code left out for simplicity, are illustrated in the following code example. The complete code is found in the DirectShow DVDSample application in the DirectX8 SDK DXF\samples\multimedia\dshow\dvd directory in the CDvdCore::BuildGraph() method.

	// Create an instance of the DVD Graph Builder object.
   HRESULT hr;
   hr = CoCreateInstance(CLSID_DvdGraphBuilder,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IDvdGraphBuilder,
                          reinterpret_cast<void**>(&m_pIDvdGB));

   // Build the DVD filter graph.
   AM_DVD_RENDERSTATUS	buildStatus;
   hr = m_pIDvdGB->RenderDvdVideoVolume(pszwDiscPath, m_dwRenderFlags, &buildStatus);

   // Get the pointers to the DVD Navigator interfaces.
   hr = m_pIDvdGB->GetDvdInterface(IID_IDvdInfo2, reinterpret_cast<void**>(&m_pIDvdI2));
   hr = m_pIDvdGB->GetDvdInterface(IID_IDvdControl2, reinterpret_cast<void**>(&m_pIDvdC2));
   ...	
   // Get a pointer to the filter graph manager.
   hr = m_pDvdGB->GetFiltergraph(&m_pGraph) ;
   ...   
   // Use the graph pointer to get a pointer to IMediaControl,
   // used for controlling the filter graph as a whole.
   hr = m_pGraph->QueryInterface(IID_IMediaControl, reinterpret_cast<void**>(&m_pIMC));
   ...   
   // Get a pointer to IMediaEventEx,
   // used for handling DVD and other filter graph events.
   hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (LPVOID *)&m_pME) ; 
   ...                
   // Use the graph builder pointer again to get the IVideoWindow interface,
   // used to set the window style and message-handling behavior of the video renderer filter.
   hr = m_pIDvdGB->GetDvdInterface(IID_IVideoWindow, reinterpret_cast<void**>(&m_pIVW));
  
   hr = m_pDvdGB->GetDvdInterface(IID_IAMLine21Decoder, (LPVOID *)&pL21Dec) ;

Handling DVD Event Notifications

The DVD Navigator sends notifications to an application-specified window when certain events take place, such as when the DVD domain changes, when a new parental management level is encountered, and when the DVD Navigator is about to enter an angle block. The event parameters can contain additional information related to the event. Error messages and warnings are also sent in this way. The application specifies the window that will handle the event notifications by using the IMediaEventEx pointer to call SetNotifyWindow, as follows:

	hr = m_pIME->SetNotifyWindow(reinterpret_cast<OAHWND>(m_hWnd), WM_DVD_EVENT, 0);

In the preceding example, m_hWnd is the HWND of the window to receive the messages and WM_DVD_EVENT is the application-defined message (greater than WM_USER) that will signal that a DVD event has taken place. The event itself is retrieved by the application through a call to IMediaEvent::GetEvent. Because more than one event might be in the event queue at any given time, the application must call GetEvent within a loop that repeats until all queued events have been retrieved, as shown in the following code example.

while (SUCCEEDED(m_pIME->GetEvent(&lEvent, &lParam1, &lParam2, lTimeOut)))
	{
        HRESULT hr;
		switch(lEvent)
        {

        case EC_DVD_CURRENT_HMSF_TIME:
        {
            DVD_HMSF_TIMECODE * pTC = reinterpret_cast<DVD_HMSF_TIMECODE *>(&lParam1);
            m_CurTime = *pTC;
            ...
        }
        break;
		...
	}

DVD events might contain additional information in the lParam1 or lParam2 parameters, as illustrated above where the current time is contained in lParam1. The preceding code example is from the DVD sample application in Dvdcore.cpp. For a complete list of all DVD events and their parameters, see DVD Event Notification Codes.

Working With DVD Menus

The logical structure of DVD menus is discussed in DVD Navigation Basics. An application can display a menu from the disc by calling IDvdControl2::ShowMenu and passing the index of the desired menu.

Keep in mind that selecting a button merely highlights its borders. To cause the associated command to be fired, the button must be activated. Some IDvdControl2 methods select a specified button, some activate a button, and SelectAndActivateButton does both. The selection and activation of menu buttons using the mouse is demonstrated in the DVD sample application in CDvdCore::OnMouseEvent.

The IDvdControl2 menu-related methods enable an application to control menus programmatically. An application can highlight or select a button based on a mouse movement or any other user action or some other program logic. An application can also implement and control a custom menu or bitmap representing the buttons on a DVD remote control. In the DVD sample application, the relative button menu commands are connected to the keyboard arrow keys (see CDvdCore::OnKeyEvent). Activating a button programmatically can be done in various ways, but the button must always be selected before it can be activated.

Audio and Subpicture Streams

A DVD-Video disc can have up to eight audio streams, numbered zero through seven, each with up to six discrete channels. (Note that audio and subpicture streams are numbered from zero, whereas titles, angles, and parental levels are numbered from one.) Only one of these streams can be selected at any given time. For subpictures, up to 32 streams are available, although only one stream can be activated at any given time. Discs are generally authored with default audio and subpicture streams, but an application can enable users to view a list of all the available streams, and select the one in the language they prefer. The basic steps in this process are the same for both audio and subpicture streams.

  1. Determine the number of streams available for a title.
  2. Iterate through the streams and retrieve the stream attributes for each.
  3. Retrieve the language code from the returned locale identifier (LCID) and create a human-readable string.
  4. Populate a list box or other user interface (UI) control to enable the user to select a preferred stream.

In the DVD sample application, the CAudioLangDlg::MakeAudioStreamList method in Dialogs.cpp demonstrates the basic steps.

Enforcing Parental Management Levels

Any title or portion of a title on a DVD-Video disc can be assigned a generic parental management level (PML) from 1 through 8. When the DVD Navigator is reading content that has a PML, it is said to be in a parental block. A parental block may consist of part of a chapter, multiple chapters, or multiple titles. A DVD application intended for an international market should not hard code a particular rating system into its parental management logic.

The DVD Navigator itself does not enforce the PMLs; it merely informs your application when it encounters PML information on the disc. By default, it ignores this information on the disc and plays the highest level content. To enforce the PMLs, your application must implement some form of password control logic that associates users with levels, instruct the DVD Navigator to send it PML event notifications (by calling the IDvdControl2::SetOption method on startup, with the parameters DVD_NotifyParentalLevelChange and TRUE), and respond to those events to allow or disallow access as appropriate.

A DVD title can have one overall PML, but disc authors can give certain sections of that title higher or more restrictive PMLs. These are called temporary PML commands; these commands always contain two branching instructions: one to follow if the temporary PML command is accepted by the player application, and the other to follow if the command is rejected. The sequence of events is as follows. The DVD Navigator is reading video content (DVD Title Domain) when it encounters a temporary PML command on the disc. It checks its internal flag to see whether the application has requested to be notified of this event. If the flag is not set, the DVD continues playing, following the "parental level change rejected" branch specified on the disc. If the flag is set, the DVD sends an EC_DVD_PARENTAL_LEVEL_CHANGE event to the application and waits in a paused state until it gets a response. When your application receives the event, it uses its own logic to determine whether to accept the command. It then calls IDvdControl2::AcceptParentalLevelChange with an argument of TRUE or FALSE. If TRUE, the DVD Navigator resumes playing, following the "parental level change accepted" branch specified on the disc. Otherwise it resumes playing and follow the "parental level change rejected" branch.

Saving and Restoring DvdState Objects

DvdState objects enable applications to save a snapshot of the user session, including information such as the current location on the disc, the parental level of the person who is viewing, the selected audio and subpicture streams, and so on. This means that users can save their place on a DVD-Video disc and watch it at a later time.

Applications can't create DvdState objects. These objects are created internally by the DVD Navigator when an application calls IDvdInfo2::GetState. DvdState objects expose the IDvdState interface to allow applications to query for certain information.

In the DVD sample application, the CDvdCore::RestoreBookmark and CDvdCore::SaveBookmark functions show how to save and retrieve DvdState objects.

Working with DVD Text Strings

DVD discs, especially karaoke discs, might contain a database of text information to supplement the video or audio content. Such text on karaoke discs can include song titles, artist names, record labels, and so on. Versions of this text can be present in more than one language. These strings are optional; discs are not required to have them. If present, they are organized in a way that closely mirrors the logical hierarchy of the DVD volume. Each string is preceded by a number that identifies what part of the disc structure the string belongs to or gives some clue as to the content of the string. A subset of the string types is defined in the DVD_TextStringType enumeration.

There are two basic types of text strings: structure identifiers and content strings. Types with a value of 0x01 through 0x20 are structure identifiers. They are empty strings; the numerical code is used to identify the logical structure to which any following content strings belong. This structure corresponds very closely to the logical structure of a DVD disc contents: volume, title, chapter, and so on. The remaining types identify content strings that hold information that may be displayed to the viewer in a user interface. The exact way in which content strings are used is not closely defined, so DVD authors can use them in various ways.

Historically, DVD text strings have been used almost exclusively on karaoke discs, and these discs mostly use the 0x01 and 0x02 structure identifiers and the 0x30 type content string. But it is reasonable to assume that, as time goes by, more types of DVD-Video discs will contain text strings and these strings will be organized in more complex ways to provide fuller descriptions of disc contents.

For information on DVD text strings, see the DVD Forum at www.dvdforum.org.

The CDvdCore::GetDvdText method in the DVDSample application demonstrates the basic steps in enumerating and displaying DVD text strings.

Playing Karaoke Audio Streams

The DVD Navigator can play DVD-Video discs with karaoke audio streams, but karaoke playback also requires a decoder that supports multichannel karaoke mixing. Specifically, the decoder must support the DVD Karaoke Property Set (AM_PROPERTY_DVDKARAOKE).

Karaoke discs are a type of DVD-Video disc and have the same navigation structure. Songs are generally formatted as titles, and titles can be grouped into title sets based on performer, musical style, or other criteria. The main difference between karaoke and other types of DVD-Videos is the audio stream. Karaoke discs all contain multichannel audio, usually Dolby AC-3. Channels 0 and 1 always contain the background instrumental music, while channels 2 through 5 can each contain any combination of guide vocals, guide melodies, and sound effects. A karaoke application can control the volume and destination speaker for each auxiliary channel.

When the DVD Navigator detects karaoke content on a disc and goes into karaoke mode, it informs the decoder, which then should mute the upper three channels (the auxiliary channels) until any or all of them are explicitly turned on by an application. The basic tasks of a karaoke application are to:

  1. Determine the number of auxiliary channels and their contents using IDvdInfo2 methods.
  2. Provide a user interface that displays the channel contents and enables users to turn any auxiliary channel on or off at any time, using IDvdControl2::SelectKaraokeAudioPresentationMode.

These steps are illustrated in the DVD Sample application in DVDCore.cpp in the GetAudioAttributes method.

Synchronizing DVD Commands

This section contains the following subtopics.

About Command Synchronization

Command synchronization is an advanced topic for developers who are familiar with the DVD Navigator and DVD navigation issues in general, and whose application requires the sophisticated functionality that command synchronization provides. If you are just starting out with the DVD Navigator, or if your application does not require command synchronization, simply use the various play commands as illustrated in Approach #1. You can always incorporate command synchronization later.

The various playback methods in IDvdControl2—for example, PlayTitle—are asynchronous in nature. When the method is called, it immediately returns a result before it has carried out all the necessary steps. During the period between the successful return of the method call and the ultimate completion of the operation, other commands might be issued that put the DVD Navigator into a new DVD domain or new filter state that prevents the method from successfully completing all the necessary steps. For example, assume your application calls PlayTitle, and immediately afterward, the user clicks the Stop button. If the commands are not synchronized, the DVD Navigator can enter the DVD Stop domain before the disc begins playing at the specified location. This is because PlayTitle returns successfully before the entire operation is carried out. When the disc finally begins to play, the DVD Navigator is no longer in the correct state, and an error will result.

Because many method calls depend on the DVD Navigator being in a particular state, applications need a way to synchronize commands. This involves the following two basic capabilities.

The DVD Navigator gives applications these capabilities, along with several options for synchronizing commands. Each playback method in IDvdControl2 has two parameters used for synchronization: an interface pointer to a synchronization object, and a set of DVD command flags. An application can use these parameters to implement command synchronization in various ways depending on its needs.

Approach 1: No Synchronization

To maintain the asynchronous nature of the method call, use the following syntax, where pDVDControl2 is an IDvdControl2 pointer.

	
HRESULT hres = pDVDControl2->PlayTitle( uTitle, 
                                        DVD_CMD_FLAG_None, // = 0
                                        NULL);

Approach 2: Simple Blocking Without Synchronization

This approach uses the DVD command flags to implement simple blocking without managing command synchronization objects. This is the simplest way to synchronize commands, because you do not have to manage synchronization objects or handle event notifications, but it does not enable you to determine the return status of any particular command.

HRESULT hres = pDVDControl2->PlayTitle( uTitle,
                                        EC_DVD_CMD_FLAG_Block,         
                                        NULL);

Approach 3: Using Only the Synchronization Object

This approach is functionally equivalent to Approach 2. It provides another way to block the DVD Navigator directly without processing event notifications. When an application passes the address of an IDvdCmd pointer, it receives a command synchronization object that is associated with this specific command. Calling the object's WaitToEnd function will block the DVD Navigator until the method successfully completes. In the meantime, new commands might be added to the queue. Be sure to release the object when you are done with it.

IDvdCmd* pObj;
HRESULT hr = pDVDControl2->PlayTitle( uTitle,  0, &pObj);
if(SUCCEEDED(hr))
{
    pObj->WaitToEnd();
    pObj->Release();
}

Approach 4: Synchronization of One Command at a Time Using Events

By specifying the EC_DVD_CMD_FLAG_SendEvents flag through a call to IDvdControl2::SetOption, the DVD Navigator will not block on the "play" method calls. Instead, it sends your application EC_DVD_CMD_START and EC_DVD_CMD_END event notifications when the command begins and then when its entire asynchronous operation completes. This enables you to synchronize single events without managing IDvdCmd objects. But by not using IDvdCmd objects, you cannot know for sure which command the event is associated with. For many situations, such information is probably not needed anyway, so this approach is sufficient in those cases.

// Call PlayChapterInTitle using "SendEvents".
HRESULT hres = pDVDControl2->PlayChapterInTitle( uTitle, 0x2,
                    DVD_CMD_FLAG_SendEvents, NULL);
…
// In the event notification handler function
switch (lEvent)
{   
   case EC_DVD_CMD_END:
	 DoSomethingGeneric(); 
	 break;
}
     

Approach 5: Synchronization Using Events and IDvdCmd Objects

This approach provides the greatest control over command synchronization, but it is also the most complex to implement. It enables applications to know which command an event is associated with, so that the application can tailor its responses to the EC_DVD_CMD_END events. The first example shows a single-threaded solution, and the second example shows a multithreaded solution.

// In global code
IDvdCmd* pGlobalObj=0;
…
// pGlobalObj is assigned by the Navigator BEFORE the event
// is issued; otherwise the event can occur at (*1) before
// pGlobalObj is assigned.
HRESULT hres = pDVDControl2->PlayTitle( uTitle,
DVD_CMD_FLAG_SendEvents, &pGlobalObj );
// (*1)
If( FAILED( hres)) {
pGlobalObj = NULL;
}
…
switch (lEvent)
{
case EC_DVD_CMD_END:
   IDvdCmd* pObj = GetDvdCmd( lParam1 );
   HRESULT hres = HRESULT( lParam2 );
If( NULL != pObj ) {
	If(pGlobalObj == pObj ) {
…
pGlobalObj->Release();
pGlobalObj = NULL;
}
pObj->Release();
   }
break;

Use this approach when your event loop is on a separate thread.

// In global code
IDvdCmd* pGlobalObj=0;
CcritSect globalCritSect;
…
{
CautoLock(globalCritSect );
HRESULT hres = pDVDControl2->PlayTitle( uTitle,
DVD_CMD_FLAG_SendEvents, &pGlobalObj );
If( FAILED( hres)) {
pGlobalObj = NULL;
}
}
…
switch (lEvent)
{
case EC_DVD_CMD_COMPLETE:
case EC_DVD_CMD_CANCEL:
{
CautoLock(globalCritSect );

IDvdCmd* pObj = GetDvdCmd( lParam1 );
HRESULT hres = HRESULT( lParam2 );
If( NULL != pObj ) {
If(pGlobalObj == obj ) {
…
pGlobalObj->Release();
pGlobalObj = NULL;
}
pObj->Release();
}
break;
}

DVD Command Flags

The DVD command flags passed in the dwFlags parameter of SetOption are used either in addition to or in place of the synchronization object, depending on which approach you are using for the particular method call. The use of DVD_CMD_FLAG_Block was shown in Approach 2 and DVD_CMD_FLAG_SendEvents was illustrated in Approaches 4 through 6. Either of these flags can also be combined in a bitwise OR with another flag, DVD_CMD_FLAG_Flush. This flag causes the method to be executed immediately, without waiting for the one or two seconds of video in the filter graph's buffers to play. Specifying this flag results in a faster response time to user input. Do not use this flag if you need to ensure that a particular video segment finishes completely before the next command is issued. The four flags and their meanings are summarized in the following table.

NameNumeric valueMeaning
DVD_CMD_FLAG_None0x00000000No flags, meaning the buffer is not flushed before the new command, no events are fired when the operation completes, and the DVD Navigator is not blocked after the method returns.
DVD_CMD_FLAG_Flush0x00000001Empty the buffers and immediately start play at the new location.
DVD_CMD_FLAG_SendEvents0x00000002Tells the DVD navigator to signal the completion of asynchronous operations and their return value by sending events back to the application.
DVD_CMD_FLAG_Block0x00000004Blocks the DVD Navigator until the asynchronous operation completes or is canceled.

Handling Disc Ejections

When the user ejects a DVD from the drive, the DVD Navigator sends your application an EC_DVD_DISC_EJECTED message. The application can safely ignore this message and let the DVD Navigator manage the graph's state. An application can also handle the EC_DVD_DISC_EJECTED method to implement custom behavior when a disc is ejected. If you handle the message, you must set the DVD_ResetOnStop flag to TRUE with the SetOption method and then call IMediaControl::Stop before closing the application or playing another disc.