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
HANDLE hThread[2];
DWORD dwID[2];
DWORD dwRetVal = 0;
hThread[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)keyboard_capture,
NULL,0,&dwID[0]);
hThread[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)play_start,
NULL,0, &dwID[1]);
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
ActiveMovie window
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
#include<dshow.h>
#include<stdio.h>
#include<conio.h>
#include<windows.h>
void play_start(void);
void init(void);
void keyboard_capture(void);
HRESULT hr; IGraphBuilder *m_pGraph = NULL; IMediaControl *m_pControl = NULL; IMediaEvent *m_pEvent = NULL; IMediaSeeking *m_pSeek = NULL; IBasicAudio *m_pAudio = NULL; REFERENCE_TIME timeNow; LONGLONG lPositionInSecs = 0; LONGLONG lDurationInNanoSecs; LONGLONG lTotalDuration = 0; REFERENCE_TIME rtNew; long lPosition = 0; char ch = '\0'; long lvolume = -100000; long evCode = -1;
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()
hr = m_pGraph->RenderFile(L"..\\default.avi", NULL);
if (SUCCEEDED(hr))
{
hr = m_pControl->Run();
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).
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);
.
case ']':
case '}':
printf("%c - Forward\n",ch);
lPosition+=1;
hr = m_pSeek->GetCurrentPosition(&timeNow);
m_pSeek->GetDuration(&lDurationInNanoSecs);
lPositionInSecs = (long)((timeNow * 100)/ lDurationInNanoSecs);
m_pSeek->GetDuration(&lTotalDuration);
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.
init()
is called for initialisation of the COM classes.- The creation of two threads, one for
keyboard_capture()
and the other for play_start()
. - Once a call to
play_start()
occurs, a movie clip is opened, run, and then paused . - Now, the program waits for the user to hit a key, i.e., the thread that handles the keyboard.
- Once a key is hit, the program will control the media according to the specific keys defined in the '
switch
' code. - '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