amazonのmp3をDirectShowで再生
amazonでダウンロードしたmp3ファイルは普通にやったのではDirectShowでは再生できません。
ID3タグを削れば再生できるのですが、それでは折角のタグがもったいないし…
ということで色々やって何とか再生にまで漕ぎ着けました。
amazonからダウンロードしたmp3は普通にやったのではDirectShowでは再生できません。
GraphBuilderのRenderFileでエラーになってしまいます。
とうことでGraphEdit.exeを使ってあれこれやってみたところWM ASF Readerというフィルターで読み込めば再生できることが分かりました。
このWM ASF Readerは自分で作成してグラフに追加しなければなりません。
それだけでなくオーディオレンダラーと繋ぐことも自分でやらなければなりません。
結構大変です。
作成はCoCreateInstanceを使います。
GraphBuilderやビデオレンダラーを作成するのと大差ないので問題ありません。
//WM ASF Reader作成。
CoCreateInstance(
CLSID_WMAsfReader,
nil,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
l_ASFReader
);
//ファイル読み込み。
//http://www.codeproject.com/KB/directx/saveimagefromstreamingurl.aspx
F_GraphBuilder.AddFilter(l_ASFReader, 'WM ASF Reader');
l_ASFReader.QueryInterface(IID_IFileSourceFilter, l_FileSourceFilter);
l_FileSourceFilter.Load(PWideChar(sFileName), nil);
l_FileSourceFilter := nil;
ここまでは問題ありません。
オーディオレンダラーと繋ぐにはICaptureGraphBuilder2インターフェースを利用するのが便利だというのでやってみたのですがうまくいきません。
//WM ASF Reader作成。
CoCreateInstance(
CLSID_WMAsfReader,
nil,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
l_ASFReader
);
//ファイル読み込み。
//http://www.codeproject.com/KB/directx/saveimagefromstreamingurl.aspx
F_GraphBuilder.AddFilter(l_ASFReader, 'WM ASF Reader');
l_ASFReader.QueryInterface(IID_IFileSourceFilter, l_FileSourceFilter);
l_FileSourceFilter.Load(PWideChar(sFileName), nil);
l_FileSourceFilter := nil;
//CaptureGraphBuilder2作成。
CoCreateInstance(
CLSID_CaptureGraphBuilder2,
nil,
CLSCTX_INPROC_SERVER,
IID_ICaptureGraphBuilder2,
l_CaptureGraphBuilder2
);
l_CaptureGraphBuilder2.SetFiltergraph(F_GraphBuilder);
//サウンドのフィルターを繋ぐ。
li_Ret := l_CaptureGraphBuilder2.RenderStream(nil, nil, l_ASFReader, nil, nil); //失敗する。
CaptureGraphBuilder2のRenderStreamの説明を読むと、第三引数にはフィルターかPinを指定するとなっています。
フィルターを指定する場合は第一引数と第二引数にメディアタイプを指定するようなのですが、それをやってもうまくきません。
それではというのでWM ASF Readerフィルターのピンを取得して渡してもこれまたうまくいきません。
ということであれこれやってみた結果どうやらデフォルトで使われるオーディオレンダラーもグラフに追加してからRenderStreamで繋がないといけないようでした。
で、ここからが問題なのですがデフォルトで使われるオーディオレンダラー(Default
DirectSound Device)を作成する方法が分からないのです。
GraphEdit.exeのフィルター一覧の「Audio Renderers」の「Default DirectSound
Device」のDisplayName欄を見てもCLSIDが載っていません。
DirectShow9.pasやDirectSound.pasでそれらしきものを探しても見つかりません。
CLSIDが分からなければCoCreateInstanceで作成できないのでどうにもなりません。
あれこれやった時には、amazonのmp3とは別の開けるファイルをGraphBuilderのRednerFileで開いてからグラフのフィルターのピン接続を全部切り、FindFilterByNameでDefault DirectSound Deviceを取得していました。
さすがに別のファイルを余分に読み込ませてDefault DirectSound Deviceを取得するというのは邪道なのでもう少しまともなやり方を探りました。
その結果システムに登録されているデバイスを列挙してその中からDefault DirectSound
Deviceを探し当てることで作成できることが分かりました。
単にデフォルトのオーディオデバイスを使いたいだけなのに意外と手間がかかります。
(私が遠回りしてるだけなのかもしれませんが)
以下の関数はフィルターのカテゴリのGUIDとフレンドリ名を与えるとABaseFilterに該当するフィルタがセットされるもので、デフォルトのオーディオデバイスを取得するには
CreateBaseFilter(F_AudioRenderer, CLSID_AudioRendererCategory, 'Default DirectSound Device');
のようにします。
function CreateBaseFilter(var ABaseFilter : IBaseFilter; ACategory : TGUID; sFriendryName : WideString) : HResult;
//登録フィルターを列挙して該当するフレンドリ名のフィルタを作成する
//http://msdn.microsoft.com/ja-jp/library/cc371168.aspx
//http://msdn.microsoft.com/ja-jp/library/cc353921.aspx
//http://www.geekpage.jp/programming/directshow/list-capture-device-2.php
//http://miraiware.net/memo/dshow-device.html
var
l_CreateDevEnum : ICreateDevEnum;
l_EnumMoniker : IEnumMoniker;
l_Moniker : IMoniker;
l_PropertyBag : IPropertyBag;
l_varName : OleVariant;
ls_Name : WideString;
begin
Result := CoCreateInstance(
CLSID_SystemDeviceEnum,
nil,
CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum,
l_CreateDevEnum
);
if (FAILED(Result)) then begin
Exit;
end;
try
Result := l_CreateDevEnum.CreateClassEnumerator(ACategory,
l_EnumMoniker, 0);
if (Result <> S_OK) then begin
Exit;
end;
//モニカを列挙する。
while (l_EnumMoniker.Next(1, l_Moniker, nil) = S_OK) do begin
try
Result := l_Moniker.BindToStorage(nil, nil, IID_IPropertyBag, l_PropertyBag);
if (Succeeded(Result)) then begin
// フィルタのフレンドリ名を取得するには、次の処理を行う。
VariantInit(l_varName);
l_PropertyBag.Read('FriendlyName', l_varName,
nil);
ls_Name := WideString(l_varName);
VariantClear(l_varName);
if (WideUpperCase(ls_Name) = WideUpperCase(sFriendryName)) then begin
//フィルタ作成。
Result := l_Moniker.BindToObject(nil, nil, IID_IBaseFilter, ABaseFilter);
Exit;
end;
end;
finally
l_PropertyBag := nil;
l_Moniker := nil;
end;
end;
//フィルタが作成された場合は途中でExitするのでここに来る場合は作成失敗であるのでE_Failを返す。
Result := E_Fail;
finally
l_EnumMoniker := nil;
l_CreateDevEnum := nil;
end;
end;
フィルターのカテゴリとフレンドリ名を変えればデフォルトのオーディオレンダラーだけでなくシステムに登録されているデバイスを色々取得できます。
unit main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms,
Dialogs, StdCtrls,
ActiveX,
DirectShow9;
type
TForm1 = class(TForm)
Button1: TButton;
OpenDialog1: TOpenDialog;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
F_GraphBuilder : IGraphBuilder;
F_AudioRenderer : IBaseFilter;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
function GetUnconnectedPin(pFilter : IBaseFilter; PinDir : PIN_DIRECTION;
var ppPin : IPin) : HResult;
function ConnectFilters(pGraph : IGraphBuilder; pOut : IPin;
pDest : IBaseFilter) : HResult;
overload;
function ConnectFilters(pGraph : IGraphBuilder; pSrc : IBaseFilter; pDest : IBaseFilter)
: HResult;
overload;
function CreateBaseFilter(
var ABaseFilter : IBaseFilter; ACategory : TGUID; sFriendryName :
WideString) : HResult;
implementation
uses
DSUtil;
{$R *.dfm}
function GetUnconnectedPin(pFilter : IBaseFilter; PinDir : PIN_DIRECTION;
var ppPin : IPin) : HResult;
//http://msdn.microsoft.com/ja-jp/library/cc973418.aspx
var
li_Ret : HResult;
pEnum : IEnumPins;
pPin : IPin;
pTmp : IPin;
ThisPinDir : TPinDirection ;
begin
ppPin :=
nil;
pEnum :=
nil;
pPin :=
nil;
li_Ret := pFilter.EnumPins(pEnum);
if (FAILED(li_Ret))
then begin
Result := li_Ret;
Exit;
end;
while (pEnum.Next(1, pPin,
nil) = S_OK)
do begin
pPin.QueryDirection(ThisPinDir);
if (ThisPinDir = PinDir)
then begin
pTmp :=
nil;
li_Ret := pPin.ConnectedTo(pTmp);
if (SUCCEEDED(li_Ret))
then begin
// 既に接続済み、必要なピンではない。
pTmp :=
nil;
end else begin
// 未接続、これが必要なピンである。
pEnum :=
nil;
ppPin := pPin;
Result := S_OK;
Exit;
end;
end;
pPin :=
nil;
end;
pEnum :=
nil;
// 一致するピンが見つからなかった。
Result := E_FAIL;
end;
function ConnectFilters(
pGraph : IGraphBuilder;
// フィルタ グラフ マネージャ。
pOut : IPin;
// アップストリーム フィルタの出力ピン。
pDest : IBaseFilter
// ダウンストリーム フィルタ。
) : HResult;
//http://msdn.microsoft.com/ja-jp/library/cc973418.aspx
var
PinDir : TPinDirection;
pIn : IPin;
begin
if (pGraph =
nil)
or (pOut =
nil)
or (pDest =
nil)
then begin
Result := E_POINTER;
Exit;
end;
pOut.QueryDirection(PinDir);
if (PinDir = PINDIR_OUTPUT)
then begin
//アップストリームフィルターのピンが出力ピンではなかった。
Result := E_FAIL;
Exit;
end;
// ダウンストリーム フィルタの入力ピンを検索する。
pIn :=
nil;
Result :=
GetUnconnectedPin(pDest, PINDIR_INPUT, pIn);
if (FAILED(Result))
then begin
Exit;
end;
// 接続を試す。
Result := pGraph.Connect(pOut, pIn);
pIn :=
nil;
end;
function ConnectFilters(
pGraph : IGraphBuilder;
pSrc : IBaseFilter;
pDest : IBaseFilter
) : HResult;
//http://msdn.microsoft.com/ja-jp/library/cc973418.aspx
var
pOut : IPin;
begin
if (pGraph =
nil)
or (pSrc =
nil)
or (pDest =
nil)
then begin
Result := E_POINTER;
Exit;
end;
// 最初のフィルタの出力ピンを検索する。
pOut :=
nil;
Result :=
GetUnconnectedPin(pSrc, PINDIR_OUTPUT, pOut);
if (FAILED(Result))
then begin
Exit;
end;
Result :=
ConnectFilters(pGraph, pOut, pDest);
pOut :=
nil;
end;
function CreateBaseFilter(
var ABaseFilter : IBaseFilter; ACategory : TGUID; sFriendryName :
WideString) : HResult;
//登録フィルタを列挙して該当するフレンドリ名のフィルタを作成する
//http://msdn.microsoft.com/ja-jp/library/cc371168.aspx
//http://msdn.microsoft.com/ja-jp/library/cc353921.aspx
//http://www.geekpage.jp/programming/directshow/list-capture-device-2.php
//http://miraiware.net/memo/dshow-device.html
var
l_CreateDevEnum : ICreateDevEnum;
l_EnumMoniker : IEnumMoniker;
l_Moniker : IMoniker;
l_PropertyBag : IPropertyBag;
l_varName : OleVariant;
ls_Name : WideString;
begin
Result := CoCreateInstance(
CLSID_SystemDeviceEnum,
nil,
CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum,
l_CreateDevEnum
);
if (FAILED(Result))
then begin
Exit;
end;
try
Result := l_CreateDevEnum.CreateClassEnumerator(ACategory,
l_EnumMoniker, 0);
if (Result <> S_OK)
then begin
Exit;
end;
//モニカを列挙する。
while (l_EnumMoniker.Next(1, l_Moniker,
nil) = S_OK)
do begin
try
Result := l_Moniker.BindToStorage(
nil,
nil, IID_IPropertyBag, l_PropertyBag);
if (Succeeded(Result))
then begin
// フィルタのフレンドリ名を取得するには、次の処理を行う。
VariantInit(l_varName);
l_PropertyBag.Read('FriendlyName', l_varName,
nil);
ls_Name :=
WideString(l_varName);
VariantClear(l_varName);
if (WideUpperCase(ls_Name) = WideUpperCase(sFriendryName))
then begin
//フィルタ作成。
Result := l_Moniker.BindToObject(
nil,
nil, IID_IBaseFilter, ABaseFilter);
Exit;
end;
end;
finally
l_PropertyBag :=
nil;
l_Moniker :=
nil;
end;
end;
//フィルタが作成された場合は途中でExitするのでここに来る場合は作成失敗であるのでE_Failを返す。
Result := E_Fail;
finally
l_EnumMoniker :=
nil;
l_CreateDevEnum :=
nil;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
li_Ret : HResult;
l_WMASFReader : IBaseFilter;
l_FileSourceFilter : IFileSourceFilter;
l_MediaControl : IMediaControl;
begin
if (OpenDialog1.Execute)
then begin
F_AudioRenderer :=
nil;
F_GraphBuilder :=
nil;
//グラフ作成
CoCreateInstance(
CLSID_FilterGraph,
nil,
CLSCTX_INPROC,
IID_IGraphBuilder,
F_GraphBuilder
);
//ファイル読み込み。
li_Ret := F_GraphBuilder.RenderFile(PWideChar(
WideString(OpenDialog1.FileName)),
nil);
if (FAILED(li_Ret))
then begin
//amazonのmp3は↑だと失敗するのでWM ASF Readerを使う。
//WM ASF Readerは自分で作成してグラフに追加しておかなければならない。
//更に繋ぐオーディオレンダラーも(デフォルトのものであっても)自分で作成してグラフに追加しておかなくてはならない。
CoCreateInstance(
CLSID_WMAsfReader,
nil,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
l_WMASFReader
);
//http://www.codeproject.com/KB/directx/saveimagefromstreamingurl.aspx
F_GraphBuilder.AddFilter(l_WMASFReader, 'WM ASF Reader');
l_WMASFReader.QueryInterface(IID_IFileSourceFilter,
l_FileSourceFilter);
l_FileSourceFilter.Load(PWideChar(
WideString(OpenDialog1.FileName)),
nil);
l_FileSourceFilter :=
nil;
CreateBaseFilter(F_AudioRenderer, CLSID_AudioRendererCategory, 'Default DirectSound Device');
F_GraphBuilder.AddFilter(F_AudioRenderer, 'Default
DirectSound Device');
li_Ret :=
ConnectFilters(F_GraphBuilder, l_WMASFReader, F_AudioRenderer);
l_WMASFReader :=
nil;
end;
F_GraphBuilder.QueryInterface(IMediaControl, l_MediaControl);
l_MediaControl.Run;
l_MediaControl :=
nil;
end;
end;
注意点
uses節にActiveXとDirectShow9とDSUtilを付け足す必要があります。
DirectShow9.pasとDSUilt.pasはDSPackの中に入っています。
D6のファイル選択ダイアログのFileNameプロパティはAnsiStringなのでF_GraphBuilder.RenderFileやl_FileSourceFilter.Loadでファイル名を渡す時にはWideStringでキャストしてからPWideCharでキャストします。
この例ではフィルターを接続するのにConnectFilters関数を使用していますがICaptureGraphBuilder2インターフェースを使用しても接続できます。
ICaptureGraphBuilder2インターフェースを使わずに接続したのは単にあれこれやった時にフィルターの接続を切ったり繋いだりする関数を作っておいたのでそっちの方が色々試せて便利だったからというだけのことです。
- WM ASF Readerフィルターはflvやmp4ファイルなどは読み込めないようです。
ffdshowのようなデコーダと各種スプリッタを入れて再生できる環境にしてあっても失敗します。
なので普通にGraphBuilderのRenderFileで読み込んで失敗した場合にのみ使うようにします。
- 再生速度の変更はできないようです。
元々DirectShowではwmaとwmvの再生速度は変更できないようなのですがmp3はできました。
けれどもWM ASF Readerフィルターを使った場合はmp3でも再生速度の変更はできません。
もしかしたらWM ASF Reader用のやり方があるのかもしれませんが分かりませんでした。
test.zip 実行ファイルとソースコードの詰め合わせ。