Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 40 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
40
Dung lượng
286,34 KB
Nội dung
LINUX AUDIO PROGRAMMING 207 the Doppler shift (a change in a sound’s apparent frequency due to relative motion) and attenuation (a loss in intensity over distance). These effects can add a great deal of depth and realism to game environments. OpenAL is designed to be hardware accelerated on multiple platforms by multiple vendors, and as such it is a completely open standard (under the control of a review board very similar to that of OpenGL). OpenAL is free to be downloaded, modified, and used in any type of application, subject to the terms of the GNU LGPL. Although OpenAL is still evolving rapidly, it is usable right now on Linux, Windows, and several other platforms. Not every application needs environmental audio, and sometimes it’s a better idea to stick with SDL’s audio system, OSS, or ALSA. For instance, it would probably be silly to use OpenAL for a sound file player or a recording program. However, OpenAL is flexible enough to handle just about any environmental audio situation as well as basic items like background music, so it’s well suited as a general-purpose audio library for games. Later in this chapter we’ll use OpenAL to add environmental audio and music support to Penguin Warrior. First, let’s talk out about the basic terminology and philosophy of OpenAL. OpenAL Basics OpenAL is an audio rendering library (as opposed to a simple buffer playback system like OSS). It plays sound as it would be heard from a certain point, known as the listener, in a 3D world. Sounds come from points in space called sources, each of which can be stationary or moving. Each source is attached to a buffer, a chunk of raw PCM sound data that describes what the source sounds like when the listener is right on top of it. Multiple sources can share the same buffer (just as multiple brick walls can use the same texture in a game such as Quake), but it’s not possible to assign multiple buffers to the same source. 5 Sources, buffers, and the listener are all considered objects, and they’re all easy to work with after you know which properties (position, velocity, and so forth) are relevant to each type. 5 This makes sense—a source is supposed to represent a particular noise coming from a certain point in space. If you want multiple sounds coming from the same place, put multiple sources at that position. 208 CHAPTER 5 Source Source Source Source Source Buffer (phaser.wav)Buffer (explosion.wav) Buffer (kaboom.wav) Listener 3D World (Top-Down View) An OpenAL world Object properties are the key to getting along with OpenAL. Rather than providing separate functions to set each possible property of a given object, OpenAL defines symbolic names for each property an object can have and supplies a few generic functions for accessing them by name. Instead of functions like alSetSourcePosition and alSetSourceOrientation, for instance, OpenAL provides a single alSourcefv function for modifying the vector properties of source objects. alSourcefv(obj, AL POSITION, pos) would set the position of the source object obj to the vector in pos. (A vector in this case is just an array of three ALfloat values.) The OpenAL specification lists all of the possible properties each type of object can have, and you can find some of the more important ones in the Penguin Warrior code later in this chapter. LINUX AUDIO PROGRAMMING 209 This Looks Familiar. . . If you think OpenAL’s design is a cheap knockoff of the OpenGL 3D graphics library, you’re right! OpenGL is an amazingly clean and well-designed API with a wide following, and OpenAL’s designers thought they’d do well to follow its style. This makes a lot of sense, especially since OpenAL is also used to provide audio support in OpenGL applications. OpenGL-oriented data structures tend to carry over to OpenAL without much trouble. In particular, OpenAL uses OpenGL’s peculiar function naming scheme; for instance, a function call that sets the AL BAR property of a Foo-type object to the first few entries of an array of floats would look something like alFoofv(foo id, AL BAR, position). In this case foo id is the “name” (integer identifier) of the object to be modified, AL BAR is the property of the object to modify, and fv signifies that the function deals with a vector of floats. OpenAL function names are always prefixed with al or AL. Once you understand the basics, OpenAL is even simpler to use than OSS. First you need a device and a context. A device represents an initialized sound card (with possible hardware acceleration features), and a context is piece of data that represents OpenAL’s current state. Each context represents one listener and multiple sources, and all commands dealing with these objects affect the current context. Since nearly everything relevant to OpenAL is stored in the current context, it’s possible to maintain several contexts and swap them out as you please. (This probably won’t be useful in most cases, however.) Only one context can be current at a time. The alcOpenDevice function opens an audio device and prepares it for OpenAL’s use. It typically does this by looking for a supported audio interface (OSS, ALSA, or ESD) and performing whatever initialization steps the interface requires. This function takes a single argument, a device specifier. In most cases you can set this to NULL; it’s useful only if you want to request a particular output device (and doing so is a quick way to kill portability). alcOpenDevice returns NULL if it can’t find a usable device and returns a pointer to a device structure if all goes well. If you can’t open a device, don’t bother with any more OpenAL calls; nearly everything requires a valid context, and a context requires a valid audio device. 210 CHAPTER 5 Once you’ve obtained a usable device, you should create a context. alcCreateContext creates an OpenAL context and associates it with a particular audio device. It takes two parameters: an open device and a list of attributes. The attributes let you request a particular sampling frequency or refresh rate; in most cases, NULL is a sufficient answer. Even with a valid audio device, alcCreateContext could fail, so be sure to check for errors. Once you have an OpenAL context, you’re in business. It’s a good idea to explicitly set your new context as current with alcMakeContextCurrent. (It’s possible, but not common, to use multiple contexts within a single application; in this case, only one will be current at any given time, and you need to switch between them manually.) With a context in place, you can add sources and buffers, configure the listener, and start playback. Once OpenAL is in motion, you can add, remove, or modify objects at any time; whatever changes you make will affect the outgoing audio stream almost immediately. OpenAL runs continuously in the background and requires no attention unless you decide to change something in its 3D world. Sources and the listener are specific to a particular context, and changes you make to these types of objects won’t affect other contexts. Buffers don’t belong to any particular OpenAL context, but you need to have a context before you can create them (since OpenAL uses the current context for error reporting). It follows that you need to destroy all of your buffers before you delete the last context. You should be wary of doing anything with OpenAL without a valid context. Now that you know a bit about the API and its capabilities, let’s put OpenAL to work as a sound engine for Penguin Warrior. Function alcOpenDevice(device) Synopsis Opens an audio device suitable for OpenAL output. Returns Pointer to an ALCdevice structure on success, NULL on failure. On failure, you can retrieve error information with alcGetError. Parameters device—Platform-dependent device specifier. This should be NULL unless you have a good reason to use something else. LINUX AUDIO PROGRAMMING 211 Function alcCloseDevice(device) Synopsis Closes a device opened by a previous call to alcOpenDevice. Never close a device that is currently in use; destroy any context that is using it first. Parameters device—Pointer to the ALCdevice to close. Function alcCreateContext(device, params) Synopsis Creates an OpenAL context. Returns A valid OpenAL context (as an ALvoid pointer) on success, NULL on failure. On failure, you can retrieve error information with alcGetError. Parameters device—Pointer to a valid ALCdevice. params—Pointer to an array of configuration flags, as described in the OpenAL specification. NULL is usually sufficient. (OpenAL will pick the best sampling rate and format it can find, so there’s little need to interfere.) Function alcMakeContextCurrent(context) Synopsis Makes a context current. Only one context can be current at a time, and you must set a current context before you make any non-alc OpenAL calls. This is not likely to fail, but you can check for errors with alcGetError() != ALC NO ERROR. Parameters context—Pointer to the context to make current. Function alcDestroyContext(context) Synopsis Destroys an OpenAL context. It’s a good idea to do this before your program exits. Never destroy the current context; call alcMakeContextCurrent(NULL) first. Parameters context—Pointer to the context to destroy. 212 CHAPTER 5 Function alGenSources(count, buffer) Synopsis Creates count sources in the current OpenAL context and stores their names (integer IDs) in buffer. This is unlikely to fail, but you can test for errors with alGetError() != AL NO ERROR. It’s not necessary for a program to clean up its sources before it exits (destroying the context does that), but you can do this with the alDeleteSources function (which takes identical parameters). Parameters count—Number of sources to generate. buffer—Pointer to an ALuint buffer big enough to hold the generated source names. Function alGenBuffers(count, buffer) Synopsis Generates count buffers. Buffers are not tied to any particular OpenAL context, but alGenBuffers must be an active context for error-handling purposes. This is not likely to fail, but as usual you can test for errors with the alGetError function. You can delete buffers with the alDeleteBuffers function (which takes identical parameters). Parameters count—Number of buffers to generate. buffer—Pointer to an ALuint buffer big enough to hold the generated buffer names. Function alSourcePlay(sourceid) Synopsis Starts playback on a source. Uses the buffer assigned to the source with the AL BUFFER property. If the AL LOOPING attribute of the source is nonzero, playback will never stop; otherwise it will stop when the buffer has been played once. Parameters sourceid—Name of the source in the current context to play. LINUX AUDIO PROGRAMMING 213 Function alSourceStop(sourceid) Synopsis Stops playback on a source immediately. Parameters sourceid—Name of the source in the current context to stop. Function alBufferData(bufferid, format, data, size, freq) Synopsis Sets a buffer’s PCM sample data. This is akin to sending texture data to OpenGL. Parameters bufferid—Name of the buffer to modify. format—Format of the sample data. Valid formats are AL FORMAT MONO8, AL FORMAT MONO16, AL FORMAT STEREO8, and AL FORMAT STEREO16. OpenAL converts between formats as necessary; there’s no need to supply data to it in a particular format. data—ALvoid pointer to the raw sample data. OpenAL copies this data into its own buffer; you can free this pointer immediately after the alBufferData call. size—Size of the sample data, in bytes. Adding Environmental Audio to Penguin Warrior How can Penguin Warrior take advantage of environmental audio? Well, right now it’s pretty hard to navigate in the game world. Locating the opponent ship can be quite an annoyance, since there’s currently no radar or direction pointer. The opponent could be anywhere, and the only way to find him, her, or it is to fly around randomly until you make visual contact. We can make the game a lot more interesting by using OpenAL to simulate the opponent’s engine noise and 214 CHAPTER 5 weapon sounds. 6 To do this, of course, we’d place the listener at the player’s position and orientation in the world, place a source on top of the opponent ship, and attach this source to a buffer that sounds something like an engine. To simulate weapon sounds, we would create another source on top of the opponent and select appropriate buffers for the weapon. Simple? Yes. Effective? OpenAL does an amazing job. 7 Source Files We add the source files audio.c, audio.h, music.c, and music.h to Penguin Warrior in this chapter. In addition, we now need to link the game against libsndfile, libopenal, and libvorbis (-lsndfile -lopenal -lvorbis). To compile this chapter’s version of Penguin Warrior, you’ll need a recent copy of OpenAL from http://www.openal.org, as well as the Vorbis audio compression library from http://www.vorbis.com. We’ll discuss the Vorbis code later in this chapter; for now, we’ll concentrate on the OpenAL side of things. In addition to the main work in audio.c, we’ll make a few simple modifications to main.c and resources.c. These should be easy to spot and simple to understand. You can find this chapter’s Penguin Warrior code in the pw-ch5/ subdirectory of the source archive. 6 Sound doesn’t travel in space, but neither do highly maneuverable ships with curved wings and laser cannons. At least not yet. 7 OpenAL’s filtering and output are reasonably solid, but some of its effects can produce strange results on low-end sound hardware. Penguin Warrior’s environmental audio works quite well on my primary computer (which has an Ensoniq ES1373 card), but Doppler-shifted audio sounds awful on my laptop (which has an integrated Yamaha sound chip and tiny speakers). OpenAL can add a lot to a game, but some players might appreciate an option for disabling advanced environmental effects. LINUX AUDIO PROGRAMMING 215 Code Listing 5–7 (audio.c) #include <math.h> #include <stdio.h> #include <stdlib.h> #include "audio.h" #include "resources.h" /* Include the OpenAL headers. */ #include <AL/al.h> #include <AL/alc.h> /* Factor to control attenuation of audio. We’ll divide all coordinates by this factor each time we update the source positions. OpenAL does provide a cleaner way to do this, but it changed recently. */ #define DISTANCE_FACTOR 50.0 /* We’ll set this flag to 1 after audio has been successfully initialized. */ int audio_enabled = 0; /* Our OpenAL context. This is just like an OpenGL context, if you’re familiar with GL’s workings. A context represents a combination of a particular output device, a sampling frequency, and so on. */ ALvoid *audio_context = NULL; /* An output device. We’ll set this to AL’s default in InitAudio(). */ ALCdevice *audio_device = NULL; /* Our sources. Sources are objects in 3D space that emit sound. We’re ignoring the fact that there’s no sound in space. */ static ALuint opponent_engine_source; static ALuint opponent_phaser_source; /* There is no player engine source; see note in StartAudio below. */ static ALuint player_phaser_source; void InitAudio() { 216 CHAPTER 5 int err; /* Create a context with whatever settings are available. We could replace NULL with a list of parameters. We use alcGetError instead of alGetError for error detection. This is because error conditions are stored within contexts, and it’s pretty meaningless to retrieve an error code from something that does not yet exist. */ audio_device = alcOpenDevice(NULL); if (audio_device == NULL) fprintf(stderr, "Warning: NULL device.\n"); else fprintf(stderr, "Got a device.\n"); audio_context = alcCreateContext(audio_device, NULL); err = alcGetError(); if (err != ALC_NO_ERROR || audio_context == NULL) { fprintf(stderr, "Unable to create an OpenAL context (%s).\n", alGetString(err)); return; } /* Make sure we have a chance to clean up. */ atexit(CleanupAudio); /* Now make the context current. The current context is the subject of all OpenAL API calls. Some calls will even segfault if there isn’t a valid current context. */ alcMakeContextCurrent(audio_context); if (alcGetError() != ALC_NO_ERROR) { fprintf(stderr, "Unable to make OpenAL context current.\n"); goto error_cleanup; } /* Good. Now create some sources (things that make noise). These will be assigned buffers later. Sources don’t do anything until you associate them with buffers (which contain PCM sound data). */ alGenSources(1, &opponent_engine_source); alGenSources(1, &opponent_phaser_source); alGenSources(1, &player_phaser_source); [...]... us a choice so we don’t have to convert between sample sizes ourselves This will obviously be 1 for 8-bit samples and 2 for 16- bit samples signedflag—1 to request signed samples, 0 to request unsigned samples In practice, 16- bit samples are almost always signed (−32, 768 32, 767 ) and 8-bit samples are almost always unsigned (0 255) stream—Pointer to an integer to receive the number of the logical bitstream... buffer with zeroes */ buf_count = MUSIC_BUF_SIZE; memset(buf, 0, MUSIC_BUF_SIZE*2); } } LINUX AUDIO PROGRAMMING 233 /* Determine the correct format This can change at any time (it probably won’t, but Vorbis allows for this) */ if (music_info->channels == 1) format = AL_FORMAT_MONO 16; else format = AL_FORMAT_STEREO 16; /* If we have a buffer of data, append it to the playback buffer alBufferAppendWriteData_LOKI... scripting to work by adding a scripted opponent to Penguin Warrior Chapter 6 Game Scripting Under Linux Games tend to be extremely large these days, and it is usually impractical for programmers to worry about the details of level design and character behavior For example, the game Soldier of Fortune (which has been ported to Linux) weighs in at over 700 megabytes of disk space, and only a small fraction... music_file_loaded = 0; /* 1 if a file is loaded, 0 if not */ /* Buffer for decoding music We use an ALshort because we’ll always request 16- bit samples from Vorbis If you experience skipping or other anomalies, increase the size of this buffer */ #define MUSIC_BUF_SIZE 65 5 36 static ALshort buf[MUSIC_BUF_SIZE]; static int buf_count = 0; /* Number of samples in the buffer */ static int buf_pos = -1; /* Playback... open(file, ovfile, initial, initialsize) Zero on success, an error code on failure It seems that ov read never processes more than 4,0 96 bytes, regardless of how much data you request I’m sure there’s a perfectly good reason for this, but it escapes me LINUX AUDIO PROGRAMMING Parameters file—Pointer to an open FILE (from fopen) You do not need to close this file—libvorbisfile takes care of that ovfile—Pointer... Xiphophorous documentation recommends a 4,0 96- byte buffer, but games usually need larger chunks of music than that 5 Open the ogg file you wish to play with the normal stdio (fopen) interface, and prepare an OggVorbis File structure with ov open After ov open succeeds, don’t touch the original FILE structure Find out relevant facts about the stream with the ov info function 6 Fill buffers of PCM samples with ov... languages for their games Scripting also allows nonprogrammers to create game worlds, and it can add a great deal of realism to games In this chapter we’ll use the Tcl scripting language to add a scripted opponent to Penguin Warrior Although Tcl is neither the fastest scripting language nor the most popular, it is extremely simple to embed into a program and extend with 238 CHAPTER 6 custom commands,... decoded samples This is prototyped as a char *, but of course you can use any type of buffer you wish 225 2 26 CHAPTER 5 length—Maximum number of bytes to decode There is no guarantee that libvorbisfile will return this much data—it’s only an upper limit libvorbisfile seems to return at most 4,0 96 bytes per call to ov read beflag—1 to request big endian samples, 0 otherwise (Intel-based machines are little... probably safe to leave an OpenAL context hanging when your program exits, but doing so could be messy if your OpenAL library happens to be using the sound card’s hardware acceleration features LINUX AUDIO PROGRAMMING 221 Figure 5–1: Penguin Warrior, rendered in 3D space Finally, resources.c has some new sound file loading code The LoadSoundFile function (not shown here) is adapted from Multi-Play’s... bit rate (bits of encoded data per second of audio) can change throughout the stream, the sampling frequency will stay constant throughout The sampling rate can change between logical bitstreams LINUX AUDIO PROGRAMMING 227 Adding Music to Penguin Warrior Penguin Warrior needs some music Music can add a lot of atmosphere to a game, and it can dramatically affect the player’s mood Would the first level . samples and 2 for 16- bit samples. signedflag—1 to request signed samples, 0 to request unsigned samples. In practice, 16- bit samples are almost always signed (−32, 768 32, 767 ) and 8-bit samples. never processes more than 4,0 96 bytes, regardless of how much data you request. I’m sure there’s a perfectly good reason for this, but it escapes me. LINUX AUDIO PROGRAMMING 225 Parameters file—Pointer. device specifier. This should be NULL unless you have a good reason to use something else. LINUX AUDIO PROGRAMMING 211 Function alcCloseDevice(device) Synopsis Closes a device opened by a previous