MP3 Support

Post tutorials on how to do certain tasks within game or engine code here.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Spike wrote:the down side of actual CD tracks is that quake's sound system is single threaded, that is, start playing a CD track and your rendering thread stalls for a second or two while the CD drive spins up and starts the relevent track.
Super annoying. End of map, a second of lag and sometimes you didn't have time to give your parting "comments" to the other players. ("gg", "that guy sucks", "I entered the game late!")
quakeworld engines default to CD audio disabled. :)
Hehe, ProQuake 4 you can turn the CD music off or no. No, not the volume ... like actually turn the CD off or from off to on. Real-time -nocdaudio command line param.

Not hard, but it was in the interests of eliminating the need for command line parameters which I view as archaic.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Baker wrote:eliminating the need for command line parameters which I view as archaic.
I'll drink to that!

From reading the QuakeOne forums, I'd guess that maybe 95% of the problems people have with Quake are command-line related. I mean real people who actually just play the game, as opposed to modders, mappers and coders (we can all be guilty of just seeing things from our own perspectives sometimes).
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Spirit
Posts: 1065
Joined: Sat Nov 20, 2004 9:00 pm
Contact:

Post by Spirit »

Quoth's "*_command" works well with "cd play x". If you have an engine that supports "cd" for vorbis tracks, it is great fun.
Improve Quaddicted, send me a pull request: https://github.com/SpiritQuaddicted/Quaddicted-reviews
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

mh wrote:From reading QuakeOne forums, I'd guess that maybe 95% of the problems people have with Quake are command-line related.
Hmmm ... neither ProQuake 4 or Qrack need any command line parameters generally speaking. DarkPlaces hasn't needed command line parameters in quite a while (for the most part). FitzQuake barely does. On the Quakeworld side of things, ezQuake doesn't and FTEQW for a long time hasn't needed them.

Within the last year or 2, Rook and I added video mode switching and the ability to switch dinput on or off. Both have gamedir changing, although your engine takes it a step further with being able to toggle the "-hipnotic" type stuff.

In fact, there is quite the general lack of ProQuake questions because it just runs and, in my opinion, has a well designed menu.

QuakeOne has a ton of legacy stuff from 2006 when command line parameters were a severe headache.

Today, if someone asks a command line question, generally they are Steam-purchased GLQuake using player or want to use, say, ProQuake 3.50 because they have always used it or want to use, say, a software renderer but want as many fps as possible so they need the -directdraw command line (or whatever it is).
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Improvement #1: this will modify the code to support both /music and /sound/cdtracks. The fallback is fully automatic; if the track isn't found in one location it will just try the other.

Replace your TouchMP3 function with this one:

Code: Select all

char *TouchMP3 (char *MP3Name, int verbose)
{
	static char MP3File[1024];

	char *musicdirs[] = 
	{
		"music",
		"sound/cdtracks",
		NULL
	};

	int i;

	// slap on a ".mp3" extension if it doesn't have one
	COM_DefaultExtension (MP3Name, ".mp3", sizeof(MP3Name));

	for (i = 0; ; i++)
	{
		// didn't find it at all
		if (!musicdirs[i])
		{
			if (verbose > 0) 
			{
				Con_Printf ("couldn't find %s!!!\n", MP3Name);
			}

			return NULL;
		}

		// try the current game directory first
		va_snprintf ("TouchMP3", MP3File, sizeof(MP3File), "%s/%s/%s", com_gamedir, musicdirs[i], MP3Name);

		FILE *f = fopen (MP3File, "rb");

		if (!f)
		{
			// no music in the current game directory so lets try ID1
			va_snprintf ("TouchMP3", MP3File, sizeof(MP3File), "id1/%s/%s", musicdirs[i], MP3Name);

			f = fopen (MP3File, "rb");

			// no music in ID1 either
			if (!f) continue;
		}

		// found it
		fclose (f);
		break;
	}

	if (verbose) 
	{
		Con_Printf ("playing %s\n", MP3Name);
	}
	return MP3File;
}
All we do here is provide a list of locations we're going to check and loop through that list, checking them all until we either find it or run out of options.

Note that which this provides compatibility with the DarkPlaces directory convention, it doesn't provide compatibility with the DarkPlaces name convention. To get that you have a number of options, but probably the easiest is to find the PlayMP3DShow function and replace this line:

Code: Select all

UserMP3DShow (va ("track%02i", mp3num), -1);
with this one:

Code: Select all

UserMP3DShow (va ("track%03i", mp3num), -1);
Darkplaces provides for up to 3 digits for the track number, which is a bit silly as - as I've previously noted - the Red Book CD audio spec only allows for up to 99 tracks on a CD (i.e. 2 digits).

If you're interested in a more general solution that's capable of checking both directores as well as an optional user-specified directory, that genuinely doesn't care about what the file is called, and that doesn't remove CD audio functionality, have a look at the DirectQ source code.

I'll hopefully post a semi-port of the relevant parts of that to the interface used by this tutorial sometime over the next few days, which will also hopefully enable loading from PAK files.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

MP3 Converted From C++ To C

Post by Baker »

I don't really like having .cpp files living in my project when the rest of them are .c , so I converted this C.

Note: This code is essentially Reckless + MH authored work, I just translated some of the C++ to C by enabling stuffs in the dshow.h header file with a define and then doing a lot of grunt work.

Code: Select all

/*
Ugh, C++

If anybody thinks I'm gonna write a class here though...

This is a drop-in replacement for the existing CD playing stuff.  It's built on DirectShow using the DirectX
8.1 SDK - I don't know if it'll work on older versions of DirectX, but I assume as most people using this
will be gamers anyway, they'll already have upgraded beyond 8.1 a long time ago.  If you want to recompile 
you may need the SDK...

Please don't compile Realm using the Direct X 9 SDK as you'll lose an awful lot of speed if you do (I
gained 50 FPS just by going back to 8.1)

This MP3 interface uses DirectShow for streaming the MP3 from the hard disk.  Better performance may be had
by buffering the entire file in memory and playing from that instead...  I'm kinda new to Direct X in general
so I don't know how to yet :(

I'm not a C++ head by any means, so if anything looks stupid in here, it probably is.

This could probably be very easily modified to enable Quake to stream audio off the web!!!
*/

#include "quakedef.h"
#include "version.h"

#ifdef BUILD_MP3_VERSION

#include <windows.h>
#include <stdio.h>
#include <objbase.h>
#define COBJMACROS
#include <dshow.h>

extern char com_gamedirshort[MAX_OSPATH];


// this needs to be defined in gl_vidnt.c as well
#define WM_GRAPHNOTIFY  WM_USER + 13
static IGraphBuilder *pGraph = NULL;
static IMediaControl *pControl = NULL;
static IMediaEventEx *pEvent = NULL;
static IBasicAudio	  *pAudio = NULL;
static IMediaSeeking *pSeek = NULL;

static BOOL MP3Enabled = FALSE;
static BOOL COMSTUFFOK = FALSE;

void WaitForFilterState (OAFilterState DesiredState) 
{
	OAFilterState MP3FS;
	HRESULT hr;
	while (1) 	
	{
		hr = IMediaControl_GetState(pControl, 1, &MP3FS);
		if (FAILED (hr)) continue;
		if (MP3FS == DesiredState) return;
	}
}

char InitMP3DShow (void) 
{
	// COM is beautiful, intuitive and really easy in VB.  This is just clunky and awful.
	HRESULT hr = CoInitialize (NULL);
	if (FAILED (hr)) {
		Con_Printf ("ERROR - Could not initialize COM library"); 
		return 0;
	}
	if (FAILED (hr)) {
		Con_Printf ("ERROR - Could not create the Filter Graph Manager.");
		CoUninitialize ();  // kill off COM
		return 0;
	}
	COMSTUFFOK = TRUE;
	return 1;
}

void KillMP3DShow (void)
{
	if (!COMSTUFFOK) return;

	// stop anything that's playing
	if (MP3Enabled)
	{
		IMediaEventEx_SetNotifyWindow(pEvent, (OAHWND) NULL, 0, 0); 
		IMediaControl_Stop(pControl); 
		IMediaControl_Release(pControl); 
		IMediaEventEx_Release(pEvent); 
		IBasicAudio_Release(pAudio); 
		IMediaEventEx_Release(pSeek); 
		IGraphBuilder_Release(pGraph); 
	}
	CoUninitialize ();
}

void StopMP3DShow (void)
{
	// don't try anything if we couldn't start COM
	if (!COMSTUFFOK) return;

	// don;t try to stop if we're not even playing!!!
	if (!MP3Enabled) return;

	// kill it straight away
	IMediaEventEx_SetNotifyWindow(pEvent, (OAHWND) NULL, 0, 0);
	IMediaControl_Stop(pControl); 
	WaitForFilterState (State_Stopped);
	IMediaControl_Release(pControl); 
	IMediaEventEx_Release(pEvent); 
	IBasicAudio_Release(pAudio); 
	IMediaSeeking_Release(pSeek); 
	IGraphBuilder_Release(pGraph); 

	// nothing playing now
	MP3Enabled = FALSE;
}

void PawsMP3DShow (int Paused)
{
	// don't try anything if we couldn't start COM
	if (!COMSTUFFOK) return;

	// don;t try to pause if we're not even playing!!!
	if (!MP3Enabled) return;

	// don't wait for the filter states here
	if (Paused)
	{
		IMediaControl_Run(pControl); 
	}
	else
	{
		IMediaControl_Pause(pControl);  
	}
}

void VolmMP3DShow (int Level)
{
	// don't try anything if we couldn't start COM
	if (!COMSTUFFOK) return;

	// don;t try to change volume if we're not even playing!!!
	if (!MP3Enabled) return;

	// put_Volume uses an exponential decibel-based scale going from -10000 (no sound) to 0 (full volume)
	// each 100 represents 1 db.  i could do the maths, but this is faster and more maintainable.
	switch (Level)
	{
	case 0:
		IBasicAudio_put_Volume(pAudio, -10000); 
		break;

	case 1:
		IBasicAudio_put_Volume(pAudio, -2000); 
		break;

	case 2:
		IBasicAudio_put_Volume(pAudio, -1400); 
		break;

	case 3:
		IBasicAudio_put_Volume(pAudio, -1040); 
		break;

	case 4:
		IBasicAudio_put_Volume(pAudio, -800); 
		break;

	case 5:
		// half volume = -6.02 db
		// i got these figures from GoldWave 5's volume changer...
		IBasicAudio_put_Volume(pAudio, -602); 
		break;

	case 6:
		IBasicAudio_put_Volume(pAudio, -440); 
		break;

	case 7:
		IBasicAudio_put_Volume(pAudio, -310); 
		break;

	case 8:
		IBasicAudio_put_Volume(pAudio, -190); 
		break;

	case 9:
		IBasicAudio_put_Volume(pAudio, -90); 
		break;

	case 10:
		IBasicAudio_put_Volume(pAudio, 0); 
		break;
	}
}

/*
===================
MesgMP3DShow

MP3 Message handler.  The only one we're interested in is a stop message.
Everything else is handled within the engine code.
===================
*/
void MesgMP3DShow (int Looping)
{
	LONG		evCode;
	LONG_PTR	evParam1, evParam2;
    HRESULT		hr = S_OK;

	// don't try anything if we couldn't start COM
	if (!COMSTUFFOK) return;

	// don't try anything if we're not even playing!!!
	if (!MP3Enabled) return;

    // Process all queued events
	while (SUCCEEDED (IMediaEventEx_GetEvent (pEvent, &evCode, &evParam1, &evParam2, 0)))
    {
        // Free memory associated with callback, since we're not using it
        hr = IMediaEventEx_FreeEventParams(pEvent, evCode, evParam1, evParam2); 

        // If this is the end of the clip, reset to beginning
        if (evCode == EC_COMPLETE && Looping)
        {
            LONGLONG pos = 0;

            // Reset to first frame of movie
			hr = IMediaSeeking_SetPositions(pSeek, &pos, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning);
        }
		else if (evCode == EC_COMPLETE)
		{
			// have to explicitly stop it when it completes otherwise the interfaces will remain open
			// when the next MP3 is played, and both will play simultaneously...!
			StopMP3DShow ();
		}
    }
    return;
}

/*
==================
TouchMP3

quickly confirm that a file exists without having to route it through labyrinthine COM interfaces
this isn't limited to MP3's only, by the way... specify an extension in your "cd play" or "mp3 play"
command and it'll play the file if you have a codec that works with direct show
==================
*/
char *TouchMP3 (char *MP3Name, int verbose)
{
	static char MP3File[1024];
	#define snprintf _snprintf


	// slap on a ".mp3" extension if it doesn't have one
	StringModify_DefaultExtension (MP3Name, ".mp3"); 
	// try the current game directory first
	snprintf (MP3File, sizeof(MP3File), "%s/music/%s", com_gamedirshort, MP3Name);

	{
		FILE *f = fopen (MP3File, "rb");

		if (!f)
		{
			// no music in the current game directory so lets try ID1
			snprintf (MP3File, sizeof(MP3File), "id1/music/%s", MP3Name);

			f = fopen (MP3File, "rb");

			// no music in ID1 either
			if (!f)
			{
				if (verbose > 0) 
				{
					Con_Printf ("couldn't find %s!!!\n", MP3Name);
				}
				return NULL;
			}
		}
		fclose (f);

		if (verbose) 
		{
			Con_DPrintf ("playing %s\n", MP3Name);
		}
	}
	return MP3File;
}

/*
==================
StringToLPCWSTR

fucking stupid MS data types
==================
*/
WCHAR *StringToLPCWSTR (char *instr)
{
	static WCHAR outstr[1024];
	int	i;

	if (!instr)
	{
		return '\0';
	}

	for (i = 0; ; i++)
	{
		outstr[i] = instr[i];

		if (instr[i] == '\0') break;
	}
	return outstr;
}

/*
=====================
SetupMP3DShow

Initialize COM interfaces and begin playing the MP3
=====================
*/
void SetupMP3DShow (WCHAR *MP3File)
{
	if (!MP3File) return;
	if (!COMSTUFFOK) return;

	// Create the filter graph manager and query for interfaces.

	CoCreateInstance (&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder, (void **) &pGraph);
	IGraphBuilder_QueryInterface(pGraph, &IID_IMediaControl, (void **) &pControl);  
	IGraphBuilder_QueryInterface(pGraph, &IID_IMediaEventEx, (void **) &pEvent); 
	IGraphBuilder_QueryInterface(pGraph, &IID_IBasicAudio, (void **) &pAudio); 
	IGraphBuilder_QueryInterface(pGraph, &IID_IMediaSeeking, (void **) &pSeek); 
	IGraphBuilder_RenderFile(pGraph, MP3File, NULL); 

	// send events through the standard window event handler
	IMediaEventEx_SetNotifyWindow(pEvent, (OAHWND) engine.platform.mainwindow, WM_GRAPHNOTIFY, 0); 

	// Run the graph.
	IMediaControl_Run(pControl); 

	// tell us globally that we can play OK
	MP3Enabled = TRUE;

	// wait until it reports playing
	WaitForFilterState (State_Running);

	// examples in the SDK will wait for the event to complete here, but this is totally inappropriate
	// for a game engine.  
}

void UserMP3DShow (char *mp3name, int verbose)
{
	MP3Enabled = FALSE;
	{
		WCHAR *MP3File = StringToLPCWSTR (TouchMP3 (mp3name, verbose));
		SetupMP3DShow (MP3File);
	}
}

void PlayMP3DShow (int mp3num)
{
	UserMP3DShow (va ("track%02i", mp3num), -1); // Baker: using -1 to denote we SAY what we are playing, but say nothing if no mp3 track is found
}



#endif
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Post by JasonX »

While implementing this, i'm getting a lot of unresolved externals.

Such as:

error LNK2019: unresolved external symbol _CDAudio_Play referenced in function _CL_ParseServerMessage


Any ideas?
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Post by JasonX »

1>cd_win_mp3.obj : error LNK2019: unresolved external symbol _StopMP3DShow referenced in function _CDAudio_Stop
1>cd_win_mp3.obj : error LNK2019: unresolved external symbol _PawsMP3DShow referenced in function _CDAudio_Pause
1>cd_win_mp3.obj : error LNK2019: unresolved external symbol _KillMP3DShow referenced in function _CDAudio_Shutdown
1>cd_win_mp3.obj : error LNK2019: unresolved external symbol _PlayMP3DShow referenced in function _CDAudio_Play
1>cd_win_mp3.obj : error LNK2019: unresolved external symbol _VolmMP3DShow referenced in function _CDAudioSetVolume
1>cd_win_mp3.obj : error LNK2019: unresolved external symbol _MesgMP3DShow referenced in function _CDAudio_MessageHandler
1>cd_win_mp3.obj : error LNK2019: unresolved external symbol _UserMP3DShow referenced in function _CD_f
1>cd_win_mp3.obj : error LNK2019: unresolved external symbol _InitMP3DShow referenced in function _CDAudio_Init
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

You're missing either a header file or function prototypes for these.

Can I seriously recommend spending some time learning some basic C before you go hacking at Quake? It'll make things so much easier for you, you won't be just doing copy-pasta code, you'll start to build up an understanding of how things work, and you'll feel more confident doing stuff.

Anyway, looking back over that code, I'm particularly awestruck by the cutesy 4-letter names I put on each operation. What on earth could I have been thinking?
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Post by JasonX »

A few more! :O

Code: Select all

1>qmp3.c
1>E:\Program Files\Microsoft SDKs\Windows\v6.0A\\include\strmif.h(21060) : error C2061: syntax error : identifier 'LPDIRECTDRAWSURFACE7'
1>E:\Program Files\Microsoft SDKs\Windows\v6.0A\\include\strmif.h(21068) : error C2059: syntax error : '}'
1>E:\Program Files\Microsoft SDKs\Windows\v6.0A\\include\strmif.h(21131) : error C2143: syntax error : missing ')' before '*'
1>E:\Program Files\Microsoft SDKs\Windows\v6.0A\\include\strmif.h(21131) : error C2081: 'VMRPRESENTATIONINFO' : name in formal parameter list illegal
1>E:\Program Files\Microsoft SDKs\Windows\v6.0A\\include\strmif.h(21131) : error C2143: syntax error : missing ';' before '*'
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Post by JasonX »

I don't think it's a problem about learning basic C per se, but learning about damn dependencies and libs in this horrible horrible Visual Studio.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

You're setting things up wrong and then you call it "horrible" when it doesn't work. :?

This code compiles perfectly in Visual Studio. How do I know this? Because I wrote the original using Visual Studio, that's how. From Baker's involvement I can surmise that the Visual Studio versions it is known-good in are 6, 2003, 2008 and 2010 (the latter two including Express versions). From Reckless' involvement I can guess that it's also known-good with his custom mingw. That's pretty damn robust support; there are many other free software projects/libraries/code-snippets that don't even get a quarter of that.

But you gotta set your environment up correctly first.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

yap :) this one suggest a missing dxsdk id say ;)

actually visual studio 2010's standard sdk seems to lack even basic directx support (earlier versions had that integrated to some extent).

http://msdn.microsoft.com/en-us/directx/aa937788

also includes links to other development sdk's if in doubt get it all (i hope you have tons of free space though some of em are pretty monstrous sizewise).

if in visual studio 2010 setting up external dependencies is a little different than older visual studios, in visual studio 2008 feks. you could do it globally 2010 has a pr project way of doing it though.

a little shortcut instead of writing in the full paths in libraries and includes is doing $(DXSDK)\include and $(DXSDK)\lib\x86

i dont use visual studio anymore since development on codeblocks and mingw is in a fine state allready :) (and its free ohoho) which unfortunatly also means im babbling of memory so if i remember wrong in some parts please excuse.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Must download dxsdk8 and follow instructions in post #1.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Post by JasonX »

I'm using DirectX 9 SDK, June 2010.
Post Reply