本篇是
用DirectX
Audio和DirectShow播放聲音和音樂(6)的續(xù)篇。
加入到MP3的革命中
MP3 是一種音頻壓縮格式,它通過刪除或修改音樂中不易被人耳察覺的部分來使音樂更小,占用的存儲空間更少。在項目中使用MP3(.MP3文件)需要使用
DirectX中的 DirectShow組件,在這個組件的幫助下,只需幾行短短的代碼,就能使用任意的MP3文件了(DirectShow也支持其他的媒體文件,比如
WMA,AVI,MPG等)。當然要想使用更多的媒體文件,必須已經(jīng)在操作系統(tǒng)中安裝了解碼器。
解碼器(codec)是一個程序,用于解碼或編碼一些指定的格式(比如MP3解碼器專門解碼.MP3文件)。通常可以從發(fā)明或者創(chuàng)建這種格式的公司中獲取這種格式的解碼器。比如,MP3解碼器來自于Fraunhofer
Insitute。幸運的是,MP3解碼器等幾種比較流行的解碼器已經(jīng)被集成到操作系統(tǒng)中(比如.mp3,.avi,.mpg等),而無需另外從
internet下載這些格式的解碼器了。
要在項目中使用DirectShow,需要包含dshow.h頭文件,并且在鏈接庫中加入strmiids.lib。
使用DirectShow
DirectX是一組COM接口組件,DirectShow也不例外,DirectShow中經(jīng)常使用的組件如下:
IGraphBuilder: 幫助建立濾波圖,濾波過濾圖是一組對象或接口的集合,用于處理某種媒體文件。
IMediaControl:控制數(shù)據(jù)在濾波圖中的流程,使用該接口控制音樂的回放。
IMediaEvents:
從濾波圖中獲取事件及通告,當希望知道在濾波圖中發(fā)生了什么的時候這個對象很有用,比如希望知道一個音樂是否仍然在播放或者已經(jīng)停止播放。
其中第一個接口IGraphBuilder是比較重要的對象,其他對象都依賴于它,或者靠它創(chuàng)建。它創(chuàng)建濾波器,用于處理媒體問題,另外很多有用的功能也是依靠這個對象。
This interface provides methods that enable an application to build a filter
graph. The Filter Graph Manager implements this interface.
IGraphBuilder inherits from the IFilterGraph interface.
IFilterGraph provides basic operations, such as adding a filter to the graph
or connecting two pins. IGraphBuilder adds further methods that construct
graphs from partial information. For example, the IGraphBuilder::RenderFile
method builds a graph for file playback, given the name of the file. The
IGraphBuilder::Render method renders data from an output pin by connecting
new filters to the pin.
Using these methods, an application does not need to specify every filter and
pin connection in the graph. Instead, the Filter Graph Manager selects filters
that are registered on the user's system, adds them to the graph, and connects
them. For more information, see Intelligent Connect.
In addition to the methods inherited from IUnknown and IFilterGraph,
the IGraphBuilder interface exposes the following methods.
Method |
Description |
Connect |
Connects two
pins. If they will not connect directly, this method connects them with
intervening transforms. |
Render |
Adds a chain
of filters to a specified output pin to render it. |
RenderFile |
Builds a
filter graph that renders the specified file. |
AddSourceFilter |
Adds a
source filter to the filter graph for a specific file. |
SetLogFile |
Sets the
file for logging actions taken when attempting to perform an operation. |
Abort |
Requests
that the graph builder return as soon as possible from its current task. |
ShouldOperationContinue |
Queries
whether the current operation should continue. |
使用DirectShow播放MP3的第一步是調(diào)用 CoCreateInstance函數(shù)創(chuàng)建濾波圖對象IGraphBuilder。
// initialize COM
//
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
// apartment (STA).
CoInitialize(0);
// create the DirectMusic performance object
//
// creates a single uninitialized object of the class associated with a specified CLSID.
if(FAILED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder,
(void**)&g_graph_builder)))
{
MessageBox(NULL, "Unable to create DirectShow Graph Builder object.", "Error", MB_OK);
return FALSE;
}
一旦創(chuàng)建對象IGraphBuilder成功,就可以請求另兩個接口了:
// Query for the media control and event objects
g_graph_builder->QueryInterface(IID_IMediaControl, (void**)&g_media_control);
g_graph_builder->QueryInterface(IID_IMediaEvent, (void**)&g_media_event);
加載媒體文件
實際上,
DirectShow并不加載媒體文件,而是創(chuàng)建一個DirectShow濾波器到文件的連接。數(shù)據(jù)在解碼的時候被流化,這樣可以減少在播放過程中的內(nèi)存使用,創(chuàng)建連接的過程叫做渲染(rendering)。渲染一個文件,需要調(diào)用IGraphBuilder::RenderFile函數(shù)。
Builds a filter graph that renders the specified file.
Syntax
HRESULT RenderFile(
LPCWSTR lpwstrFile,
LPCWSTR lpwstrPlayList
);
Parameters
- lpwstrFile
- [in] Pointer to the name of the file containing the data to be
rendered.
- lpwstrPlayList
- [in] Pointer to the playlist name. Reserved; must be NULL. (This
parameter is currently unimplemented.)
Return Value
Returns an HRESULT value, which can include one of the following:
- VFW_S_AUDIO_NOT_RENDERED
- VFW_S_DUPLICATE_NAME
- VFW_S_PARTIAL_RENDER
- VFW_S_RPZA
- VFW_S_VIDEO_NOT_RENDERED
Remarks
If the lpwstrPlayList parameter is NULL, this method would use
the default playlist, which typically renders the entire file.
控制音樂的播放
在媒體文件被渲染之后,就可以使用另外兩個接口 IMediaControl和IMediaEvent進行播放或者對播放進行控制了。
第一個接口 IMediaControl用于播放和各種播放相關的操作,這個接口有三個函數(shù):IMediaControl::Run,IMediaControl::
Pause,IMediaControl::Stop。
The IMediaControl interface provides methods
for controlling the flow of data through the filter graph. It includes methods
for running, pausing, and stopping the graph. The Filter Graph Manager
implements this interface. For more information on filter graph states, see Data
Flow in the Filter Graph.
IMediaControl also provides Automation-compatible methods for building
graphs. Applications written in Microsoft® Visual Basic® can use these methods
to construct filter graphs or retrieve information about the graph. Applications
written in C or C++ should use the methods in IGraphBuilder and
IFilterGraph2 instead, because they are more efficient.
In addition to the methods inherited from IDispatch, the
IMediaControl interface exposes the following methods.
Method |
Description |
Run |
Runs all the
filters in the filter graph. |
Pause |
Pauses all
filters in the filter graph. |
Stop |
Stops all
the filters in the filter graph. |
StopWhenReady |
Pauses the
filter graph, allowing filters to queue data, and then stops the filter
graph. |
GetState |
Retrieves
the state of the filter graph. |
RenderFile |
Builds a
filter graph that renders the specified file. (For Visual Basic.) |
AddSourceFilter |
Adds a
source filter to the filter graph, for a specified file. (For Visual
Basic.) |
get_FilterCollection |
Retrieves a
collection of the filters in the filter graph. (For Visual Basic.) |
get_RegFilterCollection |
Retrieves a
collection of all the filters listed in the registry. (For Visual
Basic.) |
如果要開始播放一段音樂,調(diào)用 IMediaControl::Run就可以了。
Switches the entire filter graph into a running state.
Syntax
HRESULT Run(void);
Return Value
Returns S_OK if the graph is actually running.
Returns S_FALSE if the graph is preparing to run (the graph will run
automatically when it's ready). Call GetState to wait for the transition to
the running state to complete or to check if the transition has completed.
If the method returns S_FALSE, subsequent calls to GetState will return a
value of State_Running when the graph is actually running. If the transition
to the running state is not complete GetState can return a return code of
VFW_S_STATE_INTERMEDIATE.
Returns an HRESULT error code if the graph could not run and is now
stopped.
Remarks
In a running state, data is pushed down the filter graph and rendered.
The graph remains in a running state until it is stopped by the
IMediaControl::Pause or IMediaControl::Stop method. The graph remains in a
running state even after notifying the application of completion (that is,
the EC_COMPLETE notification is sent to the application). This allows the
application to determine whether to pause or stop after completion.
If the filter graph is in the stopped state, this method first pauses the
graph before running.
If an error value is returned, some filters within the graph might have
successfully entered the running state. In a multistream graph, entire
streams might be playing successfully. The application must determine
whether to stop running or not.
一旦播放開始,就可以隨時暫停播放或者停止播放,如果希望暫停播放,調(diào)用IMediaControl::Pause函數(shù)。
Pauses all the filters in the filter graph.
Syntax
HRESULT Pause(void);
Return Value
Returns S_OK if the graph is actually paused.
Returns S_FALSE if the graph is in paused state but some filters have not
completed the transition to pause. Call GetState to wait for the transition
to the paused state to complete or to check if the transition has completed.
If the method returns S_FALSE, subsequent calls to GetState will return a
value of State_Paused when the graph is paused. If the transition to paused
is not complete GetState can return a return code of
VFW_S_STATE_INTERMEDIATE.
Returns an HRESULT error code if the graph could not transition to paused
state and is now stopped.
Remarks
In the paused state, filters process data but do not render it. Data is
pushed down the filter graph and is processed by
transform filters as far as buffering permits. No data is rendered
(except that media types capable of being rendered statically, such as
video, have a static, poster frame rendered in paused mode). Therefore,
putting a filter graph into a paused state cues the graph for immediate
rendering when put into a running state.
開始播放之后,可以隨時調(diào)用IMediaContrl::Stop函數(shù)來停止播放。
Switches all filters in the filter graph to a stopped state.
Syntax
HRESULT Stop(void);
Return Value
Returns an HRESULT value.
Remarks
In this mode, filters release resources and no data is processed. If the
filters are in a running state, this method pauses them before stopping
them. This allows video renderers to make a copy of the current frame for
poster frame display while stopped.
以下代碼演示了如何播放MP3文件。
//--------------------------------------------------------------------------------
// Play mp3 which specified by filename.
//--------------------------------------------------------------------------------
BOOL Play_MP3(char* filename)
{
// convert filename to wide-character string
WCHAR w_filename[MAX_PATH] = {0};
mbstowcs(w_filename, filename, MAX_PATH);
// render the file
g_graph_builder->RenderFile(w_filename, NULL);
// play the file, switches the entire filter graph into a running state.
g_media_control->Run();
return TRUE;
}
檢測播放事件
我們主要感興趣的IMediaEvent函數(shù)大概有三個:GetEvent,F(xiàn)reeEventParams,WaitForCompletion。
The IMediaEvent interface contains methods for retrieving event
notifications and for overriding the Filter Graph Manager's default handling of
events. The IMediaEventEx interface inherits this interface and extends
it.
The Filter Graph Manager implements this interface. Applications can use it
to respond to events that occur in the filter graph, such as the end of a stream
or a rendering error. Filters post events to the filter graph using the
IMediaEventSink interface.
For more information about event notification, see Event Notification in
DirectShow. For a list of system-defined event notifications, see Event
Notification Codes.
In addition to the methods inherited from IDispatch, the
IMediaEvent interface exposes the following methods.
Method |
Description |
CancelDefaultHandling |
Cancels the
Filter Graph Manager's default handling for a specified event. |
FreeEventParams |
Frees
resources associated with the parameters of an event. |
GetEvent |
Retrieves
the next event notification from the event queue. |
GetEventHandle |
Retrieves a
handle to a manual-reset event that remains signaled while the queue
contains event notifications. |
RestoreDefaultHandling |
Restores the
Filter Graph Manager's default handling for a specified event. |
WaitForCompletion |
Waits for
the filter graph to render all available data. |
第一個函數(shù)GetEvent可能是使用最多的函數(shù),它用于找回通知播放狀態(tài)的事件。
Retrieves the next notification event.
Syntax
HRESULT GetEvent(
long *lEventCode,
long *lParam1,
long *lParam2,
long msTimeout
);
Parameters
- IEventCode
- [out] Pointer to the next event notification.
- lParam1
- [out] Pointer to the first parameter of the event.
- lParam2
- [out] Pointer to the second parameter of the event.
- msTimeout
- [in] Time, in milliseconds, to wait before assuming that there are
no events.
Return Value
Returns an HRESULT value that depends on the implementation of the
interface. If the time-out is zero and no event is waiting, or if the
time-out elapses before an event appears, this method returns E_ABORT.
Remarks
The application can pass a time-out value of INFINITE to indicate that
the method should block until there is an event; however, applications
should avoid using INFINITE. Threads cannot process any messages while
waiting in GetEvent. If you call GetEvent
from the thread that processes Windows messages, specify only small wait
times on the call in order to remain responsive to user input. This is most
important when streaming data from a source such as the Internet, because
state transitions can take significantly more time to complete.
After calling GetEvent, applications should always call
FreeEventParams to release any resource associated with the event.
For a list of notification codes and event parameter values, see Event
Notification Codes.
一般我們最希望獲取到的事件是播放完成,這個事件的值是EC_COMPLETE。如果調(diào)用GetEvent函數(shù)之后,lEventCode的值是
EC_COMPLETE,說明播放完成了。
當獲取并處理了GetEvent產(chǎn)生的事件后,必須把事件所占用的資源釋放,釋放資源使用IMediaEvent::FreeEventParams函數(shù)。
Frees resources associated with the parameters of an event.
Syntax
HRESULT FreeEventParams(
long lEventCode,
long lParam1,
long lParam2
);
Parameters
- lEventCode
- [in] Next event notification.
- lParam1
- [in] First parameter of the event.
- lParam2
- [in] Second parameter of the event.
Return Value
Returns an HRESULT value.
Remarks
Event parameters can be of type LONG or BSTR. If a BSTR
is passed as an event, it will have been allocated by the task allocator and
should be freed using this method. No reference-counted interfaces are
passed to an application using IMediaEvent::GetEvent, because these cannot
be overridden by IMediaEvent::CancelDefaultHandling. Therefore, do not use
this method to release interfaces.
以下代碼演示了如何捕捉事件并釋放事件所占用的資源:
// get th status of the song, it if is done, exit program.
long event_code, param1, param2;
// retrieves the next notification event
if(SUCCEEDED(g_media_event->GetEvent(&event_code, ¶m1, ¶m2, 1)))
{
if(event_code == EC_COMPLETE)
{
// frees resources associated with the parameters of an events.
g_media_event->FreeEventParams(event_code, param1, param2);
break;
}
}
在需要逐幀連續(xù)監(jiān)視事件的時候,用GetEvent和FreeEventParams組合是很有用的,它能幫助我們獲取事件并作恰當?shù)奶幚怼5斘覀冃枰B續(xù)播放而不希望連續(xù)監(jiān)視播放過程的時候,另外一個函數(shù)就會很有用,即WaitForCompletion函數(shù)。它可以一直播放,在播放完成的時候,會把控制權交給我們。
Blocks execution of the application thread until the graph's operation
finishes.
Syntax
HRESULT WaitForCompletion(
long msTimeout,
long *pEvCode
);
Parameters
- msTimeout
- [in] Duration of the time-out, in milliseconds. Pass zero to return
immediately. To block indefinitely, pass INFINITE.
- pEvCode
- [out] Pointer to the event that terminated the wait. This value can
be one of the following:
EC_COMPLETE
|
Operation
completed. |
EC_ERRORABORT
|
Error.
Playback can't continue. |
EC_USERABORT
|
User
terminated the operation. |
Zero |
Operation has
not completed. |
Return Value
Returns one of the following HRESULT values.
E_ABORT |
Function timed
out before the operation completed. This is equivalent to a zero
pEvCode value. |
S_OK |
Operation
completed. |
Remarks
This method is the equivalent of blocking until the event notification
EC_COMPLETE, EC_ERRORABORT, or EC_USERABORT is received, or the time-out
occurs.
When this method returns, the filter graph is still running. This method
assumes that separate calls to the IMediaEvent interface are not being made.
The method fails if the graph is not in, or transitioning into, a running
state.
The time-out parameter (msTimeout) specifies the length of time
to wait for completion. To test whether the operation completed, specify a
zero msTimeout value and check the event code value (pEvCode)
for zero, indicating that the operation has not completed.
釋放DirectShow資源
一旦播放完成,就要關閉和釋放所有占用的DirectShow資源。
// stop music and relaese DirectShow objects
// switches all filters in the filter graph to a stopped state.
g_media_control->Stop();
g_media_event->Release();
g_media_event = NULL;
g_media_control->Release();
g_media_control = NULL;
g_graph_builder->Release();
g_graph_builder = NULL;
下面給出完整的代碼示例,
由于DirectX9已經(jīng)將DirectShow移除,所以需要安裝DirectX8
SDK,并在Vistual Studio中設置頭文件目錄。
點擊下載源碼和工程
/***************************************************************************************
PURPOSE:
MP3 Playing Demo
***************************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <dshow.h>
#include "resource.h"
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "strmiids.lib")
#pragma warning(disable : 4996)
#define Safe_Release(p) if((p)) (p)->Release();
// window handles, class.
HWND g_hwnd;
char g_class_name[] = "MP3PlayClass";
// DirectShows components
IGraphBuilder* g_graph_builder = NULL;
IMediaControl* g_media_control = NULL;
IMediaEvent* g_media_event = NULL;
//--------------------------------------------------------------------------------
// Play mp3 which specified by filename.
//--------------------------------------------------------------------------------
BOOL Play_MP3(char* filename)
{
// convert filename to wide-character string
WCHAR w_filename[MAX_PATH] = {0};
mbstowcs(w_filename, filename, MAX_PATH);
// render the file
g_graph_builder->RenderFile(w_filename, NULL);
// play the file, switches the entire filter graph into a running state.
g_media_control->Run();
return TRUE;
}
//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}
//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
WNDCLASS win_class;
MSG msg;
// create window class and register it
win_class.style = CS_HREDRAW | CS_VREDRAW;
win_class.lpfnWndProc = Window_Proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = DLGWINDOWEXTRA;
win_class.hInstance = inst;
win_class.hIcon = LoadIcon(inst, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
win_class.lpszMenuName = NULL;
win_class.lpszClassName = g_class_name;
if(! RegisterClass(&win_class))
return FALSE;
// create the main window
g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_MP3PLAY), 0, NULL);
ShowWindow(g_hwnd, cmd_show);
UpdateWindow(g_hwnd);
// initialize COM
//
// initialize the COM library on the current thread and identifies the concurrency model as single-thread
// apartment (STA).
CoInitialize(0);
// create the DirectMusic performance object
//
// creates a single uninitialized object of the class associated with a specified CLSID.
if(FAILED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder,
(void**)&g_graph_builder)))
{
MessageBox(NULL, "Unable to create DirectShow Graph Builder object.", "Error", MB_OK);
return FALSE;
}
// Query for the media control and event objects
g_graph_builder->QueryInterface(IID_IMediaControl, (void**)&g_media_control);
g_graph_builder->QueryInterface(IID_IMediaEvent, (void**)&g_media_event);
// play mp3
Play_MP3("escape.mp3");
// start message pump, waiting for signal to quit.
ZeroMemory(&msg, sizeof(MSG));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// get th status of the song, it if is done, exit program.
long event_code, param1, param2;
// retrieves the next notification event
if(SUCCEEDED(g_media_event->GetEvent(&event_code, ¶m1, ¶m2, 1)))
{
if(event_code == EC_COMPLETE)
{
// frees resources associated with the parameters of an events.
g_media_event->FreeEventParams(event_code, param1, param2);
break;
}
}
// frees resources associated with the parameters of an events.
g_media_event->FreeEventParams(event_code, param1, param2);
}
// stop music and relaese DirectShow objects
// switches all filters in the filter graph to a stopped state.
g_media_control->Stop();
g_media_event->Release();
g_media_event = NULL;
g_media_control->Release();
g_media_control = NULL;
g_graph_builder->Release();
g_graph_builder = NULL;
UnregisterClass(g_class_name, inst);
// release COM system
//
// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other
// resources that the thread maintains, and forces all RPC connections on the thread to close.
CoUninitialize();
return (int) msg.wParam;
}
運行截圖: