简单的控制台 DirectShow 播放器
DirectShow 如何用于在控制台模式下播放视频。
引言
那么,我们从哪里开始呢?要使用 DirectShow,我们必须了解 COM 的基本知识。如果您不知道 COM 是如何工作的,那么不用担心,您仍然可以顺利地完成教程,但请记住,您绝对应该对 COM 有一定的了解。此外,对线程的了解也非常重要,但如果您不了解线程,那么请不要随意修改 main()
方法中的代码。请记住,DirectShow 广泛使用“过滤器”这个术语,但我避免使用这些术语,尽管我们仍在程序中使用它们,我称之为接口!
解释
由于这是一个控制台程序,并且媒体在 ActiveMovie 窗口中播放,我不得不编写两个独立执行的线程。将程序拆分成线程的基本目的是,DirectShow 编程要求在严格按照 DirectShow 的要求进行编程时使用线程,但这是一个更复杂的问题,所以我使用了我自己的线程来根据我的要求处理情况。处理这两个功能的两个线程如下:
线程
// 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()
和 start_play()
,顾名思义,前者是包含捕获按键代码的函数,而另一个线程只是启动媒体文件“default.avi”并使媒体在 ActiveMovie 窗口中保持所需状态。在这种情况下,打开文件,渲染它,然后以暂停模式显示它。现在,要控制电影,例如暂停它,您将首先单击控制台窗口,然后按“P”。一旦按下按键,它们就会调用“switch”代码中定义的函数。因此,使用全局变量,您现在可以轻松地看到按下特定按键时需要调用哪个函数,因为使用全局变量可以降低复杂性。
控制台窗口
ActiveMovie 窗口
DirectShow 为大多数媒体功能提供了接口。这些抽象非常出色,您无需担心硬件,并允许开发人员专注于媒体编程的重要部分,而不是担心硬件兼容性问题、复杂性等。
程序“ConsolePlayer”以全局变量和函数开始。
全局定义
#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.
/************************/
我将首先解释全局变量。正如我所说,DirectShow 提供了接口。这些接口,例如 IMediaControl
,提供了对控制媒体流的函数的访问。IMediaControl
接口有几个函数,我将解释其中一个我使用的函数。该接口的 run()
函数基本上会开始播放电影文件。
start_playing() 的解释
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");
}
}
在这里,首先,m_pGraph
接口引用用于渲染文件“default.avi”。成功后,下一步是运行图或播放媒体文件。但我立即暂停,为什么?因为在这里,一旦电影的第一帧出现,我就暂停电影,以便用户可以使用键盘再次开始播放电影。其他使用的接口都带有注释,可以轻松理解它们的使用目的。
两种方法的解释
在这里,我想解释两种方法的功能:音量控制和快进(如果您愿意,也可以称为前进)。
case 'U':
case 'u':
if(lvolume<0)lvolume-=10;
hr = m_pAudio->put_Volume(lvolume);
printf("%c - Volume Up\n",ch);
break;
由于满音量是 -10000,代码特意减小了“lvolume
”的值。从普通事实来看,这会很奇怪,即以分贝或 dB 为单位进行计算。指针 m_pAudio
是 IBasicAudio
接口的引用,该接口公开了 put_volume(lvolume);
方法。
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;
快进功能仅限于暂停模式,即您需要按“p”键暂停媒体,然后使用按键来快进媒体。这种限制是由于我没有放置处理媒体仍在播放而我们又同时快进的情况的代码,而是坚持使用低技术版本的快进。变量 lPosition
通过将电影前进 1 秒来设置电影的位置。但是首先,m_pSeek
指针获取媒体的当前位置,然后获取以纳秒为单位播放的剪辑的时长,然后以秒为单位计算位置,然后计算总时长;之后,计算新的位置并存储在 rtNew
中,最后,m_pSeek->SetPosition(....)
设置新位置。我希望以上解释有助于理解代码。
因此,一旦您了解了这些接口,您将轻松地看到 DirectShow 编程是如何工作的,以及一旦您习惯使用接口后,它会变得多么容易。查看完程序后,请尝试阅读有关接口的信息,然后您将能够根据需要修改代码,并且很快就能掌握 DirectShow 编程的概念。
程序如何运行
我再解释一下程序的流程。
- 调用
init()
来初始化 COM 类。 - 创建两个线程,一个用于
keyboard_capture()
,另一个用于play_start()
。 - 一旦调用
play_start()
,电影剪辑就会被打开、运行,然后暂停。 - 现在,程序等待用户按下某个键,即处理键盘的线程。
- 一旦按下按键,程序将根据“
switch
”代码中定义的特定按键来控制媒体。 - “Q”将退出程序。
一旦剪辑播放完毕,您**将**需要退出程序并重新开始以从头开始播放电影。我没有放置处理剪辑播放完毕后立即将其重绕到开头的代码。您可以使用 m_pSeek->SetPositions(&rtNew, AM_SEEKING_AbsolutePositioning,NULL,AM_SEEKING_NoPositioning);
来重绕剪辑,其中 rtNew
在这种情况下必须为零。只需确保将此行代码放在正确的位置,即在剪辑播放完毕后。
我故意没有正确处理异常,因为我不想用我调试过的代码给任何人留下深刻印象,而是采用了能够说明 DirectShow 工作原理的代码。所以,我预计会有 bug!