Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / DirectX

A simple console DirectShow player

3.25/5 (5 votes)
12 Dec 2008CPOL5 min read 3   2.7K  
How DirectShow can be used to play a video in console mode.

Introduction

So, where do we start? Well, to get to work with DirectShow, we have to know the basics of COM. If you have no idea how COM works, then don't worry, you can still go through the tutorial without a hitch, but please remember, you should definitely have some sort of knowledge regarding COM. In addition, a knowledge of threads is very important, but if you do not know about threads, then do not tinker with the code that resides in the main() method. Remember, DirectShow extensively uses the term 'filters', but I have avoided using these terms although we are still using them in the program, which I called interfaces!

Explanation

Since this is a console program, and the media plays in an ActiveMovie window, I had to write down two threads that executed separately. The basic purpose of splitting into threads is that DirectShow programming requires threads when programming is done exactly according to the requirements of DirectShow, but that is a more complicated issue, so I used my own threads to handle the situation according to my requirements. The two threads that handle the two functions are as follows:

Threads

C++
// Handles for two threads
  HANDLE hThread[2];
  DWORD dwID[2];
  DWORD dwRetVal = 0;

//Create first thread for keyboard capture
hThread[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)keyboard_capture,
                          NULL,0,&dwID[0]);
//Create second thread for playback
hThread[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)play_start, 
                          NULL,0, &dwID[1]);
//Wait for threads to complete
dwRetVal = WaitForMultipleObjects(2, hThread, TRUE, INFINITE);

keybard_capture() and start_play(), as the names suggest, are the functions that contain the code to capture the keys pressed and the other thread is just to kick-start the media file "default.avi" and keep the media in the desired state in the ActiveMovie window. In this case, opening the file, rendering it, and then displaying it in a paused mode. Now, to control the movie, like pausing it, you will first click on the console window and then press "P". The keys, once pressed, will make a call to the functions defined in the "switch" code. Thus, using global variables, you now easily see which function needs to be called when a certain key is pressed as complexity is reduced due to the use of global variables.

Console window

console.jpg

ActiveMovie window

movie.jpg

DirectShow provides interfaces to most of the media functionalities. These abstractions are so wonderful you need not worry about the hardware and allows the developers to concentrate on the important parts of media programming instead of worrying about hardware compatibility issues, complexities etc.

The program "ConsolePlayer" starts with global variables and functions.

Global definitions

C++
#include<dshow.h>
#include<stdio.h>
#include<conio.h>
#include<windows.h>

/*The Global functions*/
void play_start(void);
void init(void);
void keyboard_capture(void);

/* The Global Variables */ 
HRESULT hr;                            // COM return value
IGraphBuilder *m_pGraph = NULL;        // Graph Builder interface
IMediaControl *m_pControl = NULL;      // Media Control interface
IMediaEvent   *m_pEvent = NULL;        // Media Event interface
IMediaSeeking *m_pSeek = NULL;         // Media Seeking interface
IBasicAudio   *m_pAudio = NULL;        // Audio Settings interface 
REFERENCE_TIME timeNow;                // Used for FF & REW of movie, current time
LONGLONG lPositionInSecs = 0;          // Time in  seconds
LONGLONG lDurationInNanoSecs;          // Duration in nanoseconds
LONGLONG lTotalDuration = 0;           // Total duration
REFERENCE_TIME rtNew;                  // Reference time of movie 
long lPosition = 0;                    // Desired position of movie used in FF & REW
char ch = '\0';                        // keyboard capture character
long lvolume = -100000;                // The volume level in 1/100ths dB Valid values
                                       // range from -10,000
                                       // (silence) to 0 (full volume), 
                                       // 0 = 0 dB -10000 = -100 dB 
long evCode = -1;                      // event variable, used to in file to complete wait.
/************************/

I'll explain the global variables first. As I said, DirectShow provides interfaces. These interfaces, like IMediaControl, provides access to functions that control the flow of media. The IMediaControl interface has a few functions, and I will explain one of them that I have used. The run() function of the interface basically starts playing the movie file.

Explanation of start_playing()

C++
hr = m_pGraph->RenderFile(L"..\\default.avi", NULL);
if (SUCCEEDED(hr))
{
    // Run the graph.
    hr = m_pControl->Run();
    // Now pause the graph.
    hr = m_pControl->Pause();
    if(FAILED(hr))
    {
        printf("Error occured while playing or pausing or opening the file\n");
    }
}

Here, first, the m_pGraph reference to the interface is for rendering the file "default.avi". After success, the next step is to run the graph or play the media file. But I pause immediately, why? Because here, once the first frames of the movie appear, I paused the movie so that the user can then use the keyboard to once again start playing the movie. The other interfaces that have been used are with comments, and can easily be understood as for what purpose they have been used.

Explanation of two methods

Here, I would like to explain the functionality of two methods, volume control and fast forward (or just forward if you like).

C++
case 'U':
case 'u':
    if(lvolume<0)lvolume-=10;
    hr = m_pAudio->put_Volume(lvolume);
    printf("%c - Volume Up\n",ch);
    break;

Since the full volume is at -10000, the code specifically reduces the value of 'lvolume'. In terms of ordinary facts, this would be odd, that is calculating in terms of decibels or dB. The pointer m_pAudio is the reference to the IBasicAudio interface, and the interface exposes the method put_volume(lvolume);.

C++
case ']':
case '}':
    //Fast Forwar function works only in Paused mode
    printf("%c - Forward\n",ch);
    lPosition+=1;
    hr = m_pSeek->GetCurrentPosition(&timeNow);
    m_pSeek->GetDuration(&lDurationInNanoSecs);
    lPositionInSecs = (long)((timeNow * 100)/ lDurationInNanoSecs);
    // Get the Duration of the playing file
    m_pSeek->GetDuration(&lTotalDuration);
    // Set the Current Playing position
    rtNew = (lTotalDuration * lPosition) / 100;
    hr = m_pSeek->SetPositions(&rtNew, AM_SEEKING_AbsolutePositioning, 
                               NULL,AM_SEEKING_NoPositioning);
    break;

The fast forward functionality is limited to a pause mode, i.e., you need to pause the media by pressing 'p' and then use the keys to forward the media. This limitation is due to the fact that I have not placed the code that handles the situation where the media is still playing and we are fast forwarding it too, rather stick to a low-tech version of the fast-forward. The variable lPosition sets the position of the movie by advancing it by 1 second. But first, the m_pSeek pointer gets the current position of the media, then gets the duration of the clip played in nanoseconds, the position is then calculated in seconds, then the total duration is calculated; after that, the new position is calculated and stored in rtNew, and finally, m_pSeek->SetPosition(....) sets the new position. I hope the above bit of explanation was useful in understanding the code.

So, once you get to know these interfaces, you shall easily see how DirectShow programming works and how easy it really becomes once you get used to using interfaces. After taking a look at the program, do try to read about the interfaces, and then you will be able to modify the code as you like, and soon you will grasp the concepts of programming in DirectShow.

How the program flows

Let me explain the flow of the program a bit too.

  1. init() is called for initialisation of the COM classes.
  2. The creation of two threads, one for keyboard_capture() and the other for play_start().
  3. Once a call to play_start() occurs, a movie clip is opened, run, and then paused .
  4. Now, the program waits for the user to hit a key, i.e., the thread that handles the keyboard.
  5. Once a key is hit, the program will control the media according to the specific keys defined in the 'switch' code.
  6. 'Q' will exit the program.

Once the clip finishes playing, you will need to exit the program and start again to play the movie from the beginning. I did not put the code that handles the part of rewinding the clip to the beginning once if has finished. You can rewind the clip by using m_pSeek->SetPositions(&rtNew, AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning);, where rtNew must be zero in this case. Just make sure that you put this line of code in the proper place, i.e., after the clip has finished playing.

I deliberately did not handle exceptions properly as I did not want to impress anyone with my debugged code, but rather I resorted to writing a code that served the purpose of explaining the working of DirectShow. So, I expect bugs!

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)