Discussion:
Displaying WMV from a stream with the WM ASF Reader Filter in a filter graph
(too old to reply)
Bob Dellaca
2005-01-12 02:38:42 UTC
Permalink
This is a follow-up to a thread started in this group last October by
Dale Ingold (now lost from my news server).

Here is one way to get the WM ASF Reader filter in a filter graph to
accept WMV or ASF input from a stream. In brief, it involves defining
your own "custom file type" as a "protocol" and getting the ASF Reader
filter to accept an IStream from your application when the filter is
asked to render a file of that custom protocol. Two registry keys need
to be added and a short DLL needs to be written; other than that, the
application needs to be able to supply an IStream interface object
when the filter graph is built.

This works with version 9 of DirectX.

Select your own protocol name such as "myProt", so that the ASF Reader
filter can be asked to render a file such as "myProt://something".


1. As listed in the DirectShow SDK Help, register the protocol under
the key

HKEY_CLASSES_ROOT\myProt

with a (string) entry named "Source Filter" for the GUID for the WM
ASF Reader filter

Source Filter = {187463A0-5BB7-11D3-ACBE-0080C75E246E}


2. As listed in the WM SDK WMFormat Help, register a DLL as a Source
Plug-in for the myProt protocol under the key

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows Media\WMSDK\sources

with a (string) entry, let's call it "myProt", for the full filename
of the DLL (here named "myProt")

myProt = c:\fullpathname\myProt.dll


3. Create a DLL, here named myProt, that exports the function
WMCreateStreamForURL

library myProt;

function WMCreateStreamForURL(pwszURL: PWideChar;
out pfCorrectSource: BOOL;
var pStream: IStream): HRESULT; stdcall;

exports
WMCreateStreamForURL;

As listed in the WM SDK, WMCreateStreamForURL has to output a pointer
to an IStream if it can handle the input "file". Thus, if the myProt
DLL sees a file named "myProt://something" it should set
pfCorrectSource to True and set pStream to point to the IStream;
otherwise it should set pfCorrectSource to False and set pStream to
nil.


4. In the application, define an IStream interface object, create an
instance of it, create a filter graph, and render a "file" of the
"myProt" protocol "somehow" passing the derived IStream object pointer
to the WMCreateStreamForURL function in the DLL (one method is to
include the address of the IStream in the file "name" - the "file"
might then be for example "myProt://$9e4238")

type
TmyProtStream = class(TInterfacedObject, IStream)
[...]
end;

var
myProtStream: TmyProtStream;
pBuild: ICaptureGraphBuilder2;
pGraph: IGraphBuilder;
aFileName: string;
wFileName: WideString;
begin
myProtStream := TmyProtStream.Create(...);
CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,
IID_IFilterGraph2, pGraph);
CoCreateInstance(CLSID_CaptureGraphBuilder2, nil,
CLSCTX_INPROC_SERVER,
IID_ICaptureGraphBuilder2, pBuild);
pBuild.SetFiltergraph(pGraph);
[add other filters]
aFileName := Format('myProt://$%x', [Integer(myProtStream as

IStream)]);
wFileName := aFileName;
pGraph.RenderFile(@wFileName[1], nil);
[...]
pGraph := nil;


The IStream methods Read, Seek & Stat are called by the ASF Reader
filter; it appears that the other methods can be written as not
implemented. It also appears that the ASF Reader filter releases the
IStream interface when the filter graph is released (as one might hope
for but not necessarily see), so that there is no need to free or
release myProtStream in the above example.


If anyone is interested, I can post some sample code.


Bob Dellaca
Thaddy de Koning
2005-01-12 16:08:25 UTC
Permalink
Yes please :)
I am experimenting with the same stuff using OGG streams
Bob Dellaca
2005-01-12 21:28:13 UTC
Permalink
If you are using DirectShow, which filters are you using?

The only DirectShow filters I have seen that handle OGG files are from
the OggDS0995.exe pack. The standard filter sequence then would be

File Source (Async.) -> Ogg Splitter -> Vorbis Decoder ->

That doesn't involve the WM ASF Reader filter. There are various ways
the File Source (Async.) filter can be replaced by a filter that
accepts a stream input (the one I use is derived from the Async Filter
Sample in the DirectShow SDK).

Bob Dellaca
Bob Dellaca
2005-01-13 22:30:30 UTC
Permalink
Here is the source code of a sample unit to play an OGG file from a
stream (here a TStream (TFileStream)).

It uses DS Pack 2.3.4 and the DS filters from oggDS0995.exe The filter
to handle the input stream is derived from the Async Filter Sample in
the DirectShow SDK (as translated in DS Pack). Setting
MEDIASUBTYPE_ogg should result in the filter graph manager rendering

source filter -> ogg splitter -> ogg decoder -> sound renderer


unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms,
Dialogs, Menus,
ActiveX, DirectShow9, BaseClass, UAsyncIO, UAsyncRdr;

type
TBCFileStream = class(TBCAsyncStream)
private
FLock: TBCCritSec;
FLength: LONGLONG;
FPosition: LONGLONG;
FStream: TStream;
public
constructor Create(Stream: TStream);
destructor Destroy; override;
function SetPointer(APos: LONGLONG): HResult; override;
function Read(ABuffer: PByte; ABytesToRead: DWord;
AAlign: Boolean; out ABytesRead: DWord):
HResult; override;
function Size(out ASizeAvailable: LONGLONG): LONGLONG; override;
function Alignment: DWord; override;
procedure Lock; override;
procedure Unlock; override;
end;

TBCFileReader = class(TBCAsyncReader)
public
function Register: HResult; override; stdcall;
function UnRegister: HResult; override; stdcall;
constructor Create(AStream: TBCFileStream; Amt: PAMMediaType;
out hr: HResult);
end;

TForm1 = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
Openoggfile1: TMenuItem;
OpenDialog1: TOpenDialog;
Exit1: TMenuItem;
procedure Openoggfile1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Exit1Click(Sender: TObject);
private
Stream: TBCFileStream;
Reader: TBCFileReader;
pFilgraphManager: IMediaControl;
pGraph: IGraphBuilder;
pStream: TFileStream;
procedure CloseIt;
public
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

constructor TBCFileStream.Create(Stream: TStream);
begin
FStream := Stream;
FLength := FStream.Size;
FPosition := 0;
FLock := TBCCritSec.Create;
inherited Create;
end;

destructor TBCFileStream.Destroy;
begin
if Assigned(FLock) then
FreeAndNil(FLock);
inherited Destroy;
end;

function TBCFileStream.SetPointer(APos: LONGLONG): HResult;
begin
if (APos < 0) or (APos > FLength) then
Result := S_FALSE else
begin
FPosition := APos;
FStream.Position := APos;
Result := S_OK;
end;
end;

function TBCFileStream.Read(ABuffer: PByte; ABytesToRead: DWord;
AAlign: Boolean;
out ABytesRead: DWord): HResult;
var
ReadLength: DWord;
begin
FLock.Lock;
try
if (FPosition + ABytesToRead > FLength) then
ReadLength := FLength - FPosition else
ReadLength := ABytesToRead;
FStream.Read(ABuffer^, ReadLength);
Inc(FPosition, ReadLength);
ABytesRead := ReadLength;
Result := S_OK;
finally
FLock.UnLock;
end;
end;

function TBCFileStream.Size(out ASizeAvailable: LONGLONG): LONGLONG;
begin
ASizeAvailable := FLength;
Result := FLength;
end;

function TBCFileStream.Alignment: DWord;
begin
Result := 1;
end;

procedure TBCFileStream.Lock;
begin
FLock.Lock;
end;

procedure TBCFileStream.Unlock;
begin
FLock.UnLock;
end;

constructor TBCFileReader.Create(AStream: TBCFileStream;
Amt: PAMMediaType;
out hr: HResult);
begin
inherited Create('File reader', nil, AStream, hr);
CopyMemory(@Fmt, Amt, SizeOf(TAMMediaType));
end;

function TBCFileReader.Register: HResult;
begin
Result := S_OK;
end;

function TBCFileReader.UnRegister: HResult;
begin
Result := S_OK;
end;

procedure TForm1.Openoggfile1Click(Sender: TObject);
const
MEDIASUBTYPE_ogg: TGUID = '{D2855FA9-61A7-4DB0-B979-71F297C17A04}';
var
mt: TAMMediaType;
pmt: PAMMediaType;
hr: HResult;
Pin: IPin;
begin
if OpenDialog1.Execute then
begin
CloseIt;
CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,
IID_IFilterGraph2, pGraph);
pFilgraphManager := pGraph as IMediaControl;
pStream := TFileStream.Create(OpenDialog1.FileName, fmOpenRead);
try
Stream := TBCFileStream.Create(pStream);
pmt := @mt;
TBCMediaType(pmt).InitMediaType;
mt.majortype := MEDIATYPE_Stream;
mt.subtype := MEDIASUBTYPE_ogg;
Reader := TBCFileReader.Create(Stream, @mt, hr);
Reader._AddRef;
pGraph.AddFilter(Reader, nil);
Pin := Reader.GetPin(0);
pGraph.Render(Pin);
except
on E:Exception do
begin
MessageDlg(E.Message, mtError, [mbOk], 0);
pGraph := nil;
CloseIt;
Exit;
end;
end;
pFilgraphManager.Run;
end;
end;

procedure TForm1.CloseIt;
begin
if pGraph <> nil then
begin
pFilgraphManager.Stop;
pGraph := nil;
end;
pFilgraphManager := nil;
Reader := nil;
FreeAndNil(Stream);
FreeAndNil(pStream);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseIt;
end;

procedure TForm1.Exit1Click(Sender: TObject);
begin
Close;
end;

end.
Bob Dellaca
2005-01-22 03:14:23 UTC
Permalink
For the record, here is the source code of a sample unit to play a WMA
(or WMV or ASF) file from a stream (here a TStream (TFileStream)).

It uses DS Pack 2.3.4. As outlined earlier in this thread, it involves
defining your own "custom file type" as a "protocol" and getting the
ASF Reader filter to accept an IStream from your application when the
filter is asked to render a file of that custom protocol


unit Unit2;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms,
Dialogs, Menus,
ActiveX, DirectShow9;

type
TmyProtStream = class(TInterfacedObject, IStream)
private
aStream: TStream;
aPosition, aLength: Integer;
public
constructor Create(Stream: Tstream);
function Read(pv: Pointer; cb: Longint; pcbRead: PLongint):
HResult;
stdcall;
function Write(pv: Pointer; cb: Longint; pcbWritten: PLongint):
HResult;
stdcall;
function Seek(dlibMove: Largeint; dwOrigin: Longint;
out libNewPosition: Largeint): HResult; stdcall;
function SetSize(libNewSize: Largeint): HResult; stdcall;
function CopyTo(stm: IStream; cb: Largeint; out cbRead: Largeint;
out cbWritten: Largeint): HResult; stdcall;
function Commit(grfCommitFlags: Longint): HResult; stdcall;
function Revert: HResult; stdcall;
function LockRegion(libOffset: Largeint; cb: Largeint;
dwLockType: Longint): HResult; stdcall;
function UnlockRegion(libOffset: Largeint; cb: Largeint;
dwLockType: Longint): HResult; stdcall;
function Stat(out statstg: TStatStg; grfStatFlag: Longint):
HResult;
stdcall;
function Clone(out stm: IStream): HResult; stdcall;
end;

TForm1 = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
Openwmafile1: TMenuItem;
OpenDialog1: TOpenDialog;
Exit1: TMenuItem;
procedure Openwmafile1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Exit1Click(Sender: TObject);
private
myProtStream: TmyProtStream;
pFilgraphManager: IMediaControl;
pGraph: IGraphBuilder;
pStream: TFileStream;
procedure CloseIt;
public
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

constructor TmyProtStream.Create(Stream: TStream);
begin
inherited Create;
aStream := Stream;
aLength := aStream.Size;
end;

function TmyProtStream.Read(pv: Pointer; cb: Longint; pcbRead:
PLongint): HResult;
var
ReadLength: Integer;
begin
if (aPosition + cb > aLength) then
ReadLength := aLength - aPosition else
ReadLength := cb;
aStream.Read(pv^, ReadLength);
pcbRead^ := ReadLength;
aPosition := aPosition + ReadLength;
Result := S_OK;
end;

function TmyProtStream.Seek(dlibMove: Largeint; dwOrigin: Longint;
out libNewPosition: Largeint): HResult;
var
newPos: Integer;
begin
case dwOrigin of
STREAM_SEEK_SET: newPos := dlibMove;
STREAM_SEEK_CUR: newPos := aPosition + dlibMove;
STREAM_SEEK_END: newPos := aLength - dlibMove;
else
begin
Result := E_INVALIDARG;
Exit;
end;
end;
if (newPos < 0) or (newPos > aLength) then
Result := E_INVALIDARG else
begin
aPosition := newPos;
aStream.Position := aPosition;
if @libnewPosition <> nil then
libNewPosition := newPos;
Result := S_OK;
end;
end;

function TmyProtStream.Stat(out statstg: TStatStg; grfStatFlag:
Longint): HResult;
begin
if (@statstg = nil) or (grfStatFlag <> STATFLAG_NONAME) then
begin
Result := E_INVALIDARG;
Exit;
end;
FillChar(statstg, sizeof(TStatStg), 0);
statstg.dwType := STGTY_STREAM;
statstg.cbSize := aLength;
Result := S_OK;
end;

function TmyProtStream.Write(pv: Pointer; cb: Longint; pcbWritten:
PLongint): HResult;
begin
Result := E_NOTIMPL;
end;

function TmyProtStream.SetSize(libNewSize: Largeint): HResult;
begin
Result := E_NOTIMPL;
end;

function TmyProtStream.CopyTo(stm: IStream; cb: Largeint; out cbRead:
Largeint;
out cbWritten: Largeint): HResult;
begin
Result := E_NOTIMPL;
end;

function TmyProtStream.Commit(grfCommitFlags: Longint): HResult;
begin
Result := E_NOTIMPL;
end;

function TmyProtStream.Revert: HResult;
begin
Result := E_NOTIMPL;
end;

function TmyProtStream.LockRegion(libOffset: Largeint; cb: Largeint;
dwLockType: Longint): HResult;
begin
Result := E_NOTIMPL;
end;

function TmyProtStream.UnlockRegion(libOffset: Largeint; cb: Largeint;
dwLockType: Longint): HResult;
begin
Result := E_NOTIMPL;
end;

function TmyProtStream.Clone(out stm: IStream): HResult;
begin
Result := E_NOTIMPL;
end;

procedure TForm1.Openwmafile1Click(Sender: TObject);
const
myProt = 'myProt://$%x';
var
aFileName: string;
FileName: WideString;
begin
if OpenDialog1.Execute then
begin
CloseIt;
CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,
IID_IFilterGraph2, pGraph);
pFilgraphManager := pGraph as IMediaControl;
pStream := TFileStream.Create(OpenDialog1.FileName, fmOpenRead);
try
myProtStream := TmyProtStream.Create(pStream);
aFileName := Format(myProt, [Integer(myProtStream as IStream)]);
FileName := aFileName;
pGraph.RenderFile(@FileName[1], nil);
except
on E:Exception do
begin
MessageDlg(E.Message, mtError, [mbOk], 0);
pGraph := nil;
CloseIt;
Exit;
end;
end;
pFilgraphManager.Run;
end;
end;

procedure TForm1.CloseIt;
begin
if pGraph <> nil then
begin
pFilgraphManager.Stop;
pGraph := nil;
end;
pFilgraphManager := nil;
myProtStream := nil;
FreeAndNil(pStream);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseIt;
end;

procedure TForm1.Exit1Click(Sender: TObject);
begin
Close;
end;

end.

Loading...