DirectShow question

289 views
Skip to first unread message

Roulette

unread,
Dec 7, 2004, 2:39:03 PM12/7/04
to
Can DirectShow render an audio stream from data - a WMA file - that is
embedded in another file...or render a WMA file from data stored in memory?
I have a single file that contains a number of WMA files and would like to
be able to play a particular file by specifying its offset within the file,
or possibly by loading that WMA's data into memory and then having
DirectShow render the audio from there.

Thanks in advance for any help....

- Roulette


Chris P. [MVP]

unread,
Dec 7, 2004, 3:37:29 PM12/7/04
to
On Tue, 7 Dec 2004 13:39:03 -0600, Roulette wrote:

> Can DirectShow render an audio stream from data - a WMA file - that is
> embedded in another file...or render a WMA file from data stored in memory?
> I have a single file that contains a number of WMA files and would like to
> be able to play a particular file by specifying its offset within the file,
> or possibly by loading that WMA's data into memory and then having
> DirectShow render the audio from there.

If it was anything but a WMA file, yes. Unfortunately you cannot use any
kind of 3rd party or custom source filter with a WMA file as the parser is
built into the source. The only options are to either write your own ASF
parser from scratch or to trick the system into thinking that's it's
reading from a WMA file even if it's not. There is an example of that in
the form of a sample called 'Detours', but it is license limited. See
http://research.microsoft.com/sn/detours/

Roulette

unread,
Dec 7, 2004, 6:09:55 PM12/7/04
to
Any other ideas on how to play an audio file without directly exposing
the file to the end user? That's prohibited by some music distribution
licenses.

You mentioned that if it was anything other than a WMA file you could do
it...how about for an MP3 file? If that's possible, could you point to an
example as to how to do it? I only know the DirectShow basics and have
minimal knowledge of setting up custom filters, etc..

- Roulette

"Chris P. [MVP]" <ms...@chrisnet.net> wrote in message
news:hxrhapiuez63$.4rdxnazo6zw8.dlg@40tude.net...

Alessandro Angeli [MVP::DigitalMedia]

unread,
Dec 7, 2004, 7:06:57 PM12/7/04
to
Chris P. [MVP] wrote:

> If it was anything but a WMA file, yes. Unfortunately
> you cannot use any kind of 3rd party or custom source
> filter with a WMA file as the parser is built into the
> source. The only options are to either write your own
> ASF parser from scratch or to trick the system into
> thinking that's it's reading from a WMA file even if it's
> not.

There is a third way using the WM7 deprecated source
plug-ins which require a DLL with a single exported function
to be written and a custom IStream implementation, a few
registry keys and nothing else. It worked with DirectShow
using the WMASFReader and WMP, at least up to v9. This
feature however is deprecated and quite buggy.

http://groups-beta.google.com/groups?q=source+%28%22plug-in%22+OR+plugin%29+author%3Aalessandro+author%3Aangeli

The most important threads are "program a source plugin of
windows media format SDK!!!" and "Rendering WMV file from
memory".


> There is an example of that in the form of a sample
> called 'Detours', but it is license limited. See
> http://research.microsoft.com/sn/detours/

Actually, I recently tried a new approach that does not
require Detours or any other third-party library so it is
license-free but wastes some memory. I'll post a sample
sooner or later if anyone is interested (I used the
technique to patch the DLL loader of NT, so I assume it is
quite safe with a non-critical module as the WM DLLs).


--

// Alessandro Angeli
// MVP :: Digital Media
// a dot angeli at psynet dot net


Alessandro Angeli [MVP::DigitalMedia]

unread,
Dec 7, 2004, 7:21:51 PM12/7/04
to
Roulette wrote:

> You mentioned that if it was anything other than a
> WMA file you could do it...how about for an MP3 file? If
> that's possible, could you point to an example as to how
> to do it? I only know the DirectShow basics and have
> minimal knowledge of setting up custom filters, etc..

You need to write an async source filter that extracts only
the required stream on the fly and register it to handle
either a custom protocol or a custom file extension. The
filter must by an async source that works in pull mode to
interoperate with the stock splitters for MPEG, WAV, AVI and
so on.

The Filter in the Async sample of the DirectX SDK does what
you need. This sample feeds the graph from a memory buffer
that's filled reading the whole file into memory. You need
to change this to read directly from the file when needed
and to only read a single embedded stream instead of the
whole file. That is, you do not need the
CAsyncFilter::ReadTheFile() method and you need to implement
CMemStream::Read() and CMemStream::Size() so that they
access the correct data in the file instead of an flat
memory buffer.

When you're done, you need to associate your filter to a
custom protocol or file extension or both as described here:

http://msdn.microsoft.com/library/en-us/directshow/htm/registeringacustomfiletype.asp

If you plan on using your filter inside WMP, you may want to
instruct WMP not to bother the user and on how to classify
your streams in the MediaLibrary, as described here:

http://msdn.microsoft.com/library/en-us/wmplay10/mmp_sdk/customschemeregistrysettings.asp

http://msdn.microsoft.com/library/en-us/wmplay10/mmp_sdk/filenameextensionregistrysettings.asp

http://msdn.microsoft.com/library/en-us/wmplay10/mmp_sdk/specifyingmedialibraryclassification.asp

A much harder way is to not write a custom source filter but
to write a pluggable protocol handler for InternetExplorer
for a custom protocol and to let the AsyncUrlReader filter
use it behind the scenes. I never tried and I think it is
doable only if you already know a lot about pluggable
protocol handlers.

Chris P. [MVP]

unread,
Dec 7, 2004, 11:09:21 PM12/7/04
to
On Wed, 8 Dec 2004 01:06:57 +0100, Alessandro Angeli [MVP::DigitalMedia]
wrote:

>
> There is a third way using the WM7 deprecated source
> plug-ins which require a DLL with a single exported function
> to be written and a custom IStream implementation, a few
> registry keys and nothing else. It worked with DirectShow
> using the WMASFReader and WMP, at least up to v9. This
> feature however is deprecated and quite buggy.

Interesting. But sounds like they are doing everything in their power to
break it with new versions.



> Actually, I recently tried a new approach that does not
> require Detours or any other third-party library so it is
> license-free but wastes some memory. I'll post a sample
> sooner or later if anyone is interested (I used the
> technique to patch the DLL loader of NT, so I assume it is
> quite safe with a non-critical module as the WM DLLs).

I would be interesting in taking a look when you have time.

-Chris

Alessandro Angeli [MVP::DigitalMedia]

unread,
Dec 8, 2004, 1:15:21 PM12/8/04
to
Chris P. [MVP] wrote:

> I would be interesting in taking a look when you have
> time.

The idea to not use Detours is simple: do the same stuff
Detours does on my own.

In the very end, what Detours does is quite "easy": Detours
copies N bytes from the start of the function (F) to
somewhere else (G), then overwrites the first 5 bytes of F
with a jump instruction to your function (H) and last
appends to G a jump to &F[N]. The difficulty is only in the
choice of N: N must be >= 5, it must not go beyond the end
of F and must contain an integral number of instructions.
This last part requires the code of F to be disassembled and
it is not difficult but very long to write given the mess of
the IA-32 opcodes. Of course, you need to VirtualProtect()
both the segments of F and G so that you can write to them
and them VirtualProtect() them as they were before.

Since I didn't want to write a table of IA-32 opcodes by
hand, I decided that I would copy the whole body of F, that
is N = size_of_body(F). This would also make the appended
jump not needed. Of course, there is no way to know where F
ends without disassembling the code. But I could then copy
the whole code segment where F resides (practically the
whole DLL code chunk). This wastes some memory, but doesn't
require me to know anything about IA-32 opcodes (but the
jump opcode I have to write). The idea is that either F lies
wholly inside this segment, or it has to perform an absolute
jump somewhere else sooner or later, and this jump would
still work to the original code I didn't copy. Since I copy
the whole segment, this works even if F jump backwards in
the segment.

I had to make an assumption that F contains at least 5
bytes, but this quite a safe assumptions (Detours actually
checks) given that shorter-than-5-bytes instructions are not
unconditional jumps and thus there must be other
instructions that follow.

So, let's say I want to detour F with my function H and get
a pointer to the original function in G, I do the following
(no error-checking or resource releasing):

VOID AxlDetourFunction(
LPVOID F, /// function to detour
LPVOID H, /// my new function
LPVOID* pG) /// old function
{
LPVOID pv;
DWORD dw, ul;
MEMORY_BASIC_INFORMATION mbi;

/// find out about F's segment
VirtualQuery(F,&mbi,sizeof(mbi));

/// allocate a new writable segment
pv = VirtualAlloc(NULL,mbi.RegionSize,
MEM_COMMIT,PAGE_READWRITE);

/// copy F's segment to the new one
MoveMemory(pv,mbi.BaseAddress,mbi.RegionSize);

/// make the new segment read/exec-only
VirtualProtect(pv,mbi.RegionSize,
PAGE_EXECUTE_READ,&dw);

/// the old function starts at the same offset
/// in the new segment where it started in the old one
*pG = (LPBYTE)pv + ((LPBYTE)F - (LPBYTE)mbi.BaseAddress);

/// make F's segment writable
VirtualProtect(mbi.BaseAddress,mbi.RegionSize,
PAGE_EXECUTE_READWRITE,&dw);

/// write the jump opcode
((LPBYTE)F)[0] = (BYTE)0xE9;
/// calculate the jump offset
ul = (LPBYTE)H - &((LPBYTE)F)[6];
/// write the jump offset (since there is no guarantee
/// that the offset's DWORD is DWORD aligned, this is
/// safer even if not really needed on modern CPUs)
MoveMemory(((LPBYTE)F)[1],&ul,sizeof(ul));

/// protected F's segment as it was before
VirtualProtect(mbi.BaseAddress,
mbi.RegionSize,dw,&dw);

Chris P. [MVP]

unread,
Dec 9, 2004, 9:48:02 AM12/9/04
to
On Wed, 8 Dec 2004 19:15:21 +0100, Alessandro Angeli [MVP::DigitalMedia]
wrote:

>

Very cool. Although I'm not really comfortable with patching in
productions systems, it seems as though it should work in most cases.
Things might get a little more complicated when you start thinking about
different CPU types running differing builds but it's workable.

-Chris

Alessandro Angeli [MVP::DigitalMedia]

unread,
Dec 9, 2004, 11:15:19 AM12/9/04
to
"Chris P. [MVP]" <ms...@chrisnet.net> wrote:

> Very cool. Although I'm not really comfortable with patching in
> productions systems, it seems as though it should work in most cases.

I usually don't do this on production systems as well, but the danger
actually depends on what is being patched, since most functions are well
behaved and patching will not introduce instabilities. A few functions are
not that reliable and patching is risky.

> Things might get a little more complicated when you start thinking
> about different CPU types running differing builds but it's workable.

If the CPU is not an IA-32 compatibile one, the jump opcode bytes will be
different but I suppose they will be very similar (e.g. on a MC68K CPU,
which is very different than an IA-32 one, a jump is a 2-byte code followed
by the offset, which is however practically the same thing). Since Windows
only runs on IA-32, IA-64, AMD64 and ARM4, it should not be that complex to
support all of them (I'm not considering old-fashioned Alpha or exotic MIPS
and others).


--

Alessandro Angeli

MVP::DigitalMedia

a dot angeli at biosys dot net

Reply all
Reply to author
Forward
0 new messages