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

MP3 Support

Post by Baker »

The following tutorial adds MP3 playback support to an engine under Windows with, presumably, DirectX 8 or later installed.

Credit: MH and Reckless

Requires: DX8.1 SDK "dx81sdk_full.exe" to compile (NOT needed to run the engine)

Sample implementation with binary and source: FitzQuake 0.85 MP3

Limitations:
1. No volume control for mp3 playback volume
2. No support for mp3s to be in pak files
3. Music goes in quake\<gamedir>\music or quake\id1\music folder
4. CD tracks are named track01.mp3, track02.mp3, etc.
5. Can directly play MP3s via "mp3 play track03"
6. Doing this tutorial adds MP3 support but removes CD support
Instructions:

1. Download cd_win_mp3.c (file)and add to your project.
2. Download qmp3.cpp (file) and add to your project.
3. Remove cd_win.c from your project.

Step 4.

In sound.h, add below "extern vec_t sound_nominal_clip_dist;":

Code: Select all

#ifdef MP3_VERSION
extern int				sound_started;
#endif
Step 5.

Open common.h and above "void Q_memset (void *dest, int fill, int count);" add:

Code: Select all

#ifdef MP3_VERSION
#undef snprintf
#undef vsnprintf

#ifdef WIN32
# define snprintf _snprintf
# define vsnprintf _vsnprintf
#endif

int		va_snprintf (char *function, char *buffer, size_t buffersize, const char *format, ...);
int		va_vsnprintf (char *function, char *buffer, size_t buffersize, const char *format, va_list args);

#endif
Step 6.

Open common.c and add this where you'd like. It can go to the botton of the file if you want but it'd be better to include somewhere in the vicinity of the string functions.

Code: Select all

#ifdef MP3_VERSION

// fast safe printbuffers courtesy of lord havoc.
int va_snprintf (char *function, char *buffer, size_t buffersize, const char *format, ...)
{
	va_list args;
	int		result;
	
	va_start (args, format);
	result = va_vsnprintf (function, buffer, buffersize, format, args);
	va_end (args);
	
	return result;
}

int va_vsnprintf (char *function, char *buffer, size_t buffersize, const char *format, va_list args)
{
	size_t result;
	
	result = vsnprintf (buffer, buffersize, format, args);
	
	if (result < 0 || result >= buffersize)
	{
		static qboolean inside;
		
		// Beware recursion here
		if (!inside)
		{
			inside = true;
			Con_SafePrintf ("%s: excessive string length, max = %d\n", function, buffersize);
		}		
		inside = false;
		buffer[buffersize - 1] = '\0';
		return -1;
	}	
	return result;
}
#endif
Step #7.1

The MP3 playback needs to pause if the Quake window loses focus and then resume if it gets it back. At least that's what I think. And I was lazy and didn't make proper functions and just tossed commands on the command buffer.

Mostly because I have no time and would like to get this done. :(

Open gl_vidnt.c and locate this:

Code: Select all

// enable/disable sound on focus gain/loss
	if (!ActiveApp && sound_active)
	{
		S_BlockSound ();
		sound_active = false;
	}
	else if (ActiveApp && !sound_active)
	{
		S_UnblockSound ();
		sound_active = true;
	}
And replace it with ...

Code: Select all

// enable/disable sound on focus gain/loss
	if (!ActiveApp && sound_active)
	{
		S_BlockSound ();
#ifdef MP3_VERSION
		// Need to pause CD music here if is playing
		if (sound_started) {
			Cbuf_InsertText ("mp3 pause\n");
			Cbuf_Execute ();
		}
#endif		
		sound_active = false;
	}
	else if (ActiveApp && !sound_active)
	{
		S_UnblockSound ();
#ifdef MP3_VERSION
		// Need to unpause CD music here if was playing
		if (sound_started) {
			Cbuf_InsertText ("mp3 resume\n");
			Cbuf_Execute ();
		}
#endif		
		sound_active = true;
	}
Step #7.2

Still in gl_vidnt.c

Find this:

Code: Select all

/* main window procedure */
LONG WINAPI MainWndProc ...
And add above it:

Code: Select all

#ifdef MP3_VERSION
#ifndef WM_GRAPHNOTIFY
#define WM_GRAPHNOTIFY  WM_USER + 13
#endif
#endif
Step #7.3

This next part, I could Google up what this is doing, but I'm low on time and don't want to. Sorry.

Done is better than not done in this one instance and I have not so much time and so many things I want to get done.

Find this:

Code: Select all

		case MM_MCINOTIFY:
            lRet = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam);
			break;
And replace with ...

Code: Select all

#ifdef MP3_VERSION
		case WM_GRAPHNOTIFY:
#else
		case MM_MCINOTIFY:
#endif
            lRet = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam);
			break;
Step 8.

Now MH wrote this very modularly, so it is easy to implement. In the project, you need to define MP3_VERSION. In MSVC6, do Projects -> Settings. And likewise you need to add references to 2 libraries: dsound.lib strmiids.lib

Then compile!
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post by revelator »

Code: Select all

/*
============
va

does a varargs printf into a temp buffer, so I don't need to have
varargs versions of all text functions.
============
*/
char *va(const char *format, ...)
{
	// now cycles through 8 buffers to avoid problems in most cases
	va_list		argptr;
	static char string[8][1024], *s;
	static int	stringindex = 0;
	
	s = string[stringindex];
	stringindex = (stringindex + 1) & 7;
	va_start (argptr, format);
	va_vsnprintf ("va", s, sizeof(string[0]), format, argptr);
	va_end (argptr);
	
	return s;
}
might want to add this also in regards to lord havocs print buffers
fixes some problems on win7

else looking good :)
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Nice one! :D

The WM_GRAPHNOTIFY message, by the way, is a custom one generated by DirectShow whenever stuff relating to the playback state changes. Rather evil that it's not defined in the SDK headers.
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 »

Yay, Baker's tutorials are back!

Everyone implementing this or similar functionality, PLEASE be compatible to how Darkplaces has done this for years for Vorbis and WAVE.

"id1/sound/cdtracks/track002.ogg"
Improve Quaddicted, send me a pull request: https://github.com/SpiritQuaddicted/Quaddicted-reviews
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

I'd prefer to be compatible with how every other ID game that came after Quake did it and stick them in "/music", to be honest. The DP method seems a little weird, as it makes no top-level distinction between in-game sounds and music, and secondly they're not really CD tracks. Kinda reminiscent of the way Tenebrae used "/override" to store textures in. It would be great if LH could provide insight into his reasoning for choosing that particular location to use.

The best approach however is to let the music location be user-configurable via cvar, falling back to testing both "/sound/cdtracks" and "/music" if not found. No reason to artifically limit your engine's capability, is there? Especially when a way to be more broadly compatible is so obvious.
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
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

sure, but if its a choice of one or the other, it is better to be compatible with another quake engine than it is a q2 or q3 engine, as more people are going to expect mods to work using that notation.
music is sound, and thus it must go in the sounds directory.
Well... that and the fact that all precached sounds in quake are prefixed with 'sound/' before opening.
And they _are_ cd tracks if they're played with the cd command. They use svc_cdtrack protocols, etc. The DP extension that specifies it is named something containing FAKE_TRACKS or something, so...


Just a small note, this won't work for playing sounds in paks. You would need to implement lots of extra COM objects for this, but it would be possible, somehow.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Spirit wrote:Yay, Baker's tutorials are back!

Everyone implementing this or similar functionality, PLEASE be compatible to how Darkplaces has done this for years for Vorbis and WAVE.

"id1/sound/cdtracks/track002.ogg"
For about 5 minutes, I was thinking exactly what you were.

And then I had to Google for what I have posted at least 5 times as to the folder name. Was it "sound/cdtrack" or "sounds/cdtracks" or "sounds/cdtracks/cdtrack01.mp3" or what?

And then I remembered that DarkPlaces doesn't play MP3s so this isn't going to interfere with an existing ogg.

I like the gamedir/music folder because I can remember it, it is modder friendly and makes sense.

Why should the cdtracks be in the sound/cdtracks folder?

They aren't game sounds so they shouldn't be in the sound folder.

And cdtracks is wrong because I bet in 3 years there won't be any games with cd sound tracks because CDs are obsolete. That would be like sticking the music in sounds/cassettetracks or sounds/8track folder.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Random thoughts related to the discussion:

1. I'd like to see the music indicated in worldspawn be like "mysong" instead of a silly number. I think it would be cool for modders to be able to explicitly name the song playing instead of some number. Even worse that this number needs x-1, which is confusing.

2. I haven't looked at the protocol to see if #1 interferes with it or is even indicated (aside from the DP_ extension), but I know any half-way serious player isn't going to use a cd track in multiplayer because it interferes with hearing and consumes resources. But coop maybe.

Case in point, with Travail (download Travail mp3 pack I made that works with the Fitz 0.85 in post #1), why should the names of the sounds be renamed from, say, "In Memoria" or what not to the bland "track03.mp3".

It's like doing the scrubs all the humanity out of authorship of music. Much better for a map author to put "memoria" in worldspawn and have it play that mp3. I mean, if you are going to rethink whether a standard is good, rethink it all.

(No idea what would happen to an existing engine if it encountered a non-numeric "cd track" name. Maybe use different worldspawn "name" like "music".)
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Good points. I addressed this in DirectQ by allowing the MP3s to retain their original names and instead using the number as an index in the file listing, but your idea of being able to specify a name in worldspawn is something I never really thought of. In theory it could be OK, as the worldspawn key can be read and reacted to separately at load time. In practice the messy part is that the Quake architecture specifies a CD tracknumber which is only accessed by the server and sent to the client as an svc_cdtrack message. This would also mean that the server (including QC) would have the ability to specify an arbitrary change of CD track at any point during gameplay.

Now, the interesting thing is that the Red Book spec only allows up to 99 tracks on a CD, and the message is sent as a byte. It's hacky, but given that this is a clear case where a certain type of message that would otherwise never be used exists, one could interpret a value above 100 as indicating that the message is to be followed by a string; possibly just a track name but more usefully a command that could play, pause, stop, change volume, etc.

I wouldn't be comfortable implementing this without a protocol or QC extension, but that's where we're getting into other territories (and where my proposed extensible protocol would be a Good Thing, rather than having to invent complete new protocols for every additional thing, no matter how minor).
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
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Spike wrote:sure, but if its a choice of one or the other, it is better to be compatible with another quake engine than it is a q2 or q3 engine, as more people are going to expect mods to work using that notation.
music is sound, and thus it must go in the sounds directory.
Well... that and the fact that all precached sounds in quake are prefixed with 'sound/' before opening.
And they _are_ cd tracks if they're played with the cd command. They use svc_cdtrack protocols, etc. The DP extension that specifies it is named something containing FAKE_TRACKS or something, so...
It depends really. I'm going by the notion that people who are used to playing Q3A and the like, and coming to Q1 from there, would be more inclined to use a "music" directory; but there are arguments on both sides.

Fortunately it's stupidly easy to support both. Just check in one and if it doesn't exist check in the other. Debate over and everybody happy. :D
Spike wrote:Just a small note, this won't work for playing sounds in paks. You would need to implement lots of extra COM objects for this, but it would be possible, somehow.
You could extract to a temp file; there are Windows API calls (and since we're using DirectShow we're already committed to Windows so portability concerns don't apply) that can create a temp file (optionally holding it in system memory but making it accessible via the file I/O routines) and then delete it when no processes are left holding it open.

I've 99% of what's required to support this for MP3s implemented in DirectQ already (I use it for loading from PK3 or Zip) so I could probably get the rest up and running in half an hour or so. Making it more generally usable in the context of this tutorial would take only slightly longer.
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
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

Baker wrote:And then I remembered that DarkPlaces doesn't play MP3s so this isn't going to interfere with an existing ogg.
However, this extension can play wav, and potentially ogg, even if DP cannot play mp3s.

Oo oo, what happens if you point it to a video instead of a sound file?


mh: doesn't need a protocol extension in the regular way. just use stuffcmds. music tracks don't really change often enough that it warrents incompatibilities. imho. doesn't need qc extensions then either. just some stuffcmd calls (interesting note: prefix extension stuffcmds with // and you don't get nasty messages in clients that don't support it, but you do have to explicitly recognise it then).
Also, what does track -1 mean? (>100 might be bad)
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Spike wrote:Also, what does track -1 mean? (>100 might be bad)
I haven't used a CD with Quake in years but isn't indicating track 01 in the map asking it to play track00?

That's what I mean by -1.

Someone correct me if I am wrong.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post by mh »

Tracks < 1 or > the number of tracks on the CD are just ignored; Quake will do a Con_DPrintf saying "bad track number" and not even bother playing.

As the track is read as an unsigned char, -1 will equate to 255.

Anyway, Spike is right about a stuffcmd being all that's needed. It's a shame that mods don't do it, as it would be great to see some imaginative use of dramatic music at different points during the game.
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

Post by Baker »

mh wrote:It's a shame that mods don't do it, as it would be great to see some imaginative use of dramatic music at different points during the game.
That would be. Like the final battle when the door closes behind you.

Great point.
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

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.
quakeworld engines default to CD audio disabled. :)
fake tracks don't have the same issue, assuming they are streamed and not decoded in one lump, at least. And using window's COM stuff will generally put it in a different thread anyway.
Post Reply