📄 mixer.c
字号:
/* SDL_mixer: An audio mixer library based on the SDL library Copyright (C) 1997-2004 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Sam Lantinga slouken@libsdl.org*//* $Id: mixer.c 2290 2006-05-01 03:34:46Z slouken $ */#include <stdio.h>#include <stdlib.h>#include <string.h>#include "SDL_mutex.h"#include "SDL_endian.h"#include "SDL_timer.h"#include "SDL_mixer.h"#include "load_aiff.h"#include "load_voc.h"#include "load_ogg.h"/* Magic numbers for various audio file formats */#define RIFF 0x46464952 /* "RIFF" */#define WAVE 0x45564157 /* "WAVE" */#define FORM 0x4d524f46 /* "FORM" */#define OGGS 0x5367674f /* "OggS" */#define CREA 0x61657243 /* "Crea" */static int audio_opened = 0;static SDL_AudioSpec mixer;typedef struct _Mix_effectinfo{ Mix_EffectFunc_t callback; Mix_EffectDone_t done_callback; void *udata; struct _Mix_effectinfo *next;} effect_info;static struct _Mix_Channel { Mix_Chunk *chunk; int playing; int paused; Uint8 *samples; int volume; int looping; int tag; Uint32 expire; Uint32 start_time; Mix_Fading fading; int fade_volume; Uint32 fade_length; Uint32 ticks_fade; effect_info *effects;} *mix_channel = NULL;static effect_info *posteffects = NULL;static int num_channels;static int reserved_channels = 0;/* Support for hooking into the mixer callback system */static void (*mix_postmix)(void *udata, Uint8 *stream, int len) = NULL;static void *mix_postmix_data = NULL;/* rcg07062001 callback to alert when channels are done playing. */static void (*channel_done_callback)(int channel) = NULL;/* Music function declarations */extern int open_music(SDL_AudioSpec *mixer);extern void close_music(void);/* Support for user defined music functions, plus the default one */extern int volatile music_active;extern void music_mixer(void *udata, Uint8 *stream, int len);static void (*mix_music)(void *udata, Uint8 *stream, int len) = music_mixer;static void *music_data = NULL;/* rcg06192001 get linked library's version. */const SDL_version *Mix_Linked_Version(void){ static SDL_version linked_version; SDL_MIXER_VERSION(&linked_version); return(&linked_version);}static int _Mix_remove_all_effects(int channel, effect_info **e);/* * rcg06122001 Cleanup effect callbacks. * MAKE SURE SDL_LockAudio() is called before this (or you're in the * audio callback). */static void _Mix_channel_done_playing(int channel){ if (channel_done_callback) { channel_done_callback(channel); } /* * Call internal function directly, to avoid locking audio from * inside audio callback. */ _Mix_remove_all_effects(channel, &mix_channel[channel].effects);}static void *Mix_DoEffects(int chan, void *snd, int len){ int posteffect = (chan == MIX_CHANNEL_POST); effect_info *e = ((posteffect) ? posteffects : mix_channel[chan].effects); void *buf = snd; if (e != NULL) { /* are there any registered effects? */ /* if this is the postmix, we can just overwrite the original. */ if (!posteffect) { buf = malloc(len); if (buf == NULL) { return(snd); } memcpy(buf, snd, len); } for (; e != NULL; e = e->next) { if (e->callback != NULL) { e->callback(chan, buf, len, e->udata); } } } /* be sure to free() the return value if != snd ... */ return(buf);}/* Mixing function */static void mix_channels(void *udata, Uint8 *stream, int len){ Uint8 *mix_input; int i, mixable, volume; Uint32 sdl_ticks; /* Mix the music (must be done before the channels are added) */ if ( music_active || (mix_music != music_mixer) ) { mix_music(music_data, stream, len); } /* Mix any playing channels... */ sdl_ticks = SDL_GetTicks(); for ( i=0; i<num_channels; ++i ) { if( ! mix_channel[i].paused ) { if ( mix_channel[i].expire > 0 && mix_channel[i].expire < sdl_ticks ) { /* Expiration delay for that channel is reached */ mix_channel[i].playing = 0; mix_channel[i].fading = MIX_NO_FADING; mix_channel[i].expire = 0; _Mix_channel_done_playing(i); } else if ( mix_channel[i].fading != MIX_NO_FADING ) { Uint32 ticks = sdl_ticks - mix_channel[i].ticks_fade; if( ticks > mix_channel[i].fade_length ) { if( mix_channel[i].fading == MIX_FADING_OUT ) { mix_channel[i].playing = 0; mix_channel[i].expire = 0; Mix_Volume(i, mix_channel[i].fade_volume); /* Restore the volume */ _Mix_channel_done_playing(i); } mix_channel[i].fading = MIX_NO_FADING; } else { if( mix_channel[i].fading == MIX_FADING_OUT ) { Mix_Volume(i, (mix_channel[i].fade_volume * (mix_channel[i].fade_length-ticks)) / mix_channel[i].fade_length ); } else { Mix_Volume(i, (mix_channel[i].fade_volume * ticks) / mix_channel[i].fade_length ); } } } if ( mix_channel[i].playing > 0 ) { int index = 0; int remaining = len; while (mix_channel[i].playing > 0 && index < len) { remaining = len - index; volume = (mix_channel[i].volume*mix_channel[i].chunk->volume) / MIX_MAX_VOLUME; mixable = mix_channel[i].playing; if ( mixable > remaining ) { mixable = remaining; } mix_input = Mix_DoEffects(i, mix_channel[i].samples, mixable); SDL_MixAudio(stream+index,mix_input,mixable,volume); if (mix_input != mix_channel[i].samples) free(mix_input); mix_channel[i].samples += mixable; mix_channel[i].playing -= mixable; index += mixable; /* rcg06072001 Alert app if channel is done playing. */ if (!mix_channel[i].playing && !mix_channel[i].looping) { _Mix_channel_done_playing(i); } } /* If looping the sample and we are at its end, make sure we will still return a full buffer */ while ( mix_channel[i].looping && index < len ) { int alen = mix_channel[i].chunk->alen; remaining = len - index; if (remaining > alen) { remaining = alen; } mix_input = Mix_DoEffects(i, mix_channel[i].chunk->abuf, remaining); SDL_MixAudio(stream+index, mix_input, remaining, volume); if (mix_input != mix_channel[i].chunk->abuf) free(mix_input); --mix_channel[i].looping; mix_channel[i].samples = mix_channel[i].chunk->abuf + remaining; mix_channel[i].playing = mix_channel[i].chunk->alen - remaining; index += remaining; } if ( ! mix_channel[i].playing && mix_channel[i].looping ) { if ( --mix_channel[i].looping ) { mix_channel[i].samples = mix_channel[i].chunk->abuf; mix_channel[i].playing = mix_channel[i].chunk->alen; } } } } } /* rcg06122001 run posteffects... */ Mix_DoEffects(MIX_CHANNEL_POST, stream, len); if ( mix_postmix ) { mix_postmix(mix_postmix_data, stream, len); }}static void PrintFormat(char *title, SDL_AudioSpec *fmt){ printf("%s: %d bit %s audio (%s) at %u Hz\n", title, (fmt->format&0xFF), (fmt->format&0x8000) ? "signed" : "unsigned", (fmt->channels > 2) ? "surround" : (fmt->channels > 1) ? "stereo" : "mono", fmt->freq);}void _Mix_InitEffects(void);/* Open the mixer with a certain desired audio format */int Mix_OpenAudio(int frequency, Uint16 format, int nchannels, int chunksize){ int i; SDL_AudioSpec desired; /* If the mixer is already opened, increment open count */ if ( audio_opened ) { if ( format == mixer.format && nchannels == mixer.channels ) { ++audio_opened; return(0); } while ( audio_opened ) { Mix_CloseAudio(); } } /* Set the desired format and frequency */ desired.freq = frequency; desired.format = format; desired.channels = nchannels; desired.samples = chunksize; desired.callback = mix_channels; desired.userdata = NULL; /* Accept nearly any audio format */ if ( SDL_OpenAudio(&desired, &mixer) < 0 ) { return(-1); }#if 0 PrintFormat("Audio device", &mixer);#endif /* Initialize the music players */ if ( open_music(&mixer) < 0 ) { SDL_CloseAudio(); return(-1); } num_channels = MIX_CHANNELS; mix_channel = (struct _Mix_Channel *) malloc(num_channels * sizeof(struct _Mix_Channel)); /* Clear out the audio channels */ for ( i=0; i<num_channels; ++i ) { mix_channel[i].chunk = NULL; mix_channel[i].playing = 0; mix_channel[i].looping = 0; mix_channel[i].volume = SDL_MIX_MAXVOLUME; mix_channel[i].fade_volume = SDL_MIX_MAXVOLUME; mix_channel[i].fading = MIX_NO_FADING; mix_channel[i].tag = -1; mix_channel[i].expire = 0; mix_channel[i].effects = NULL; mix_channel[i].paused = 0; } Mix_VolumeMusic(SDL_MIX_MAXVOLUME); _Mix_InitEffects(); audio_opened = 1; SDL_PauseAudio(0); return(0);}/* Dynamically change the number of channels managed by the mixer. If decreasing the number of channels, the upper channels are stopped. */int Mix_AllocateChannels(int numchans){ if ( numchans<0 || numchans==num_channels ) return(num_channels); if ( numchans < num_channels ) { /* Stop the affected channels */ int i; for(i=numchans; i < num_channels; i++) { Mix_HaltChannel(i); } } SDL_LockAudio(); mix_channel = (struct _Mix_Channel *) realloc(mix_channel, numchans * sizeof(struct _Mix_Channel)); if ( numchans > num_channels ) { /* Initialize the new channels */ int i; for(i=num_channels; i < numchans; i++) { mix_channel[i].chunk = NULL; mix_channel[i].playing = 0; mix_channel[i].looping = 0; mix_channel[i].volume = SDL_MIX_MAXVOLUME; mix_channel[i].fade_volume = SDL_MIX_MAXVOLUME; mix_channel[i].fading = MIX_NO_FADING; mix_channel[i].tag = -1; mix_channel[i].expire = 0; mix_channel[i].effects = NULL; mix_channel[i].paused = 0; } } num_channels = numchans; SDL_UnlockAudio(); return(num_channels);}/* Return the actual mixer parameters */int Mix_QuerySpec(int *frequency, Uint16 *format, int *channels){ if ( audio_opened ) { if ( frequency ) { *frequency = mixer.freq; } if ( format ) { *format = mixer.format; } if ( channels ) { *channels = mixer.channels; } } return(audio_opened);}/* * !!! FIXME: Ideally, we want a Mix_LoadSample_RW(), which will handle the * generic setup, then call the correct file format loader. *//* Load a wave file */Mix_Chunk *Mix_LoadWAV_RW(SDL_RWops *src, int freesrc){ Uint32 magic; Mix_Chunk *chunk; SDL_AudioSpec wavespec, *loaded; SDL_AudioCVT wavecvt; int samplesize; /* rcg06012001 Make sure src is valid */ if ( ! src ) { SDL_SetError("Mix_LoadWAV_RW with NULL src"); return(NULL); } /* Make sure audio has been opened */ if ( ! audio_opened ) { SDL_SetError("Audio device hasn't been opened"); if ( freesrc && src ) { SDL_RWclose(src); } return(NULL); } /* Allocate the chunk memory */ chunk = (Mix_Chunk *)malloc(sizeof(Mix_Chunk)); if ( chunk == NULL ) { SDL_SetError("Out of memory"); if ( freesrc ) { SDL_RWclose(src); } return(NULL); } /* Find out what kind of audio file this is */ magic = SDL_ReadLE32(src); /* Seek backwards for compatibility with older loaders */ SDL_RWseek(src, -(int)sizeof(Uint32), SEEK_CUR); switch (magic) { case WAVE: case RIFF: loaded = SDL_LoadWAV_RW(src, freesrc, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); break; case FORM: loaded = Mix_LoadAIFF_RW(src, freesrc, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); break;#ifdef OGG_MUSIC case OGGS: loaded = Mix_LoadOGG_RW(src, freesrc, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); break;#endif case CREA: loaded = Mix_LoadVOC_RW(src, freesrc, &wavespec, (Uint8 **)&chunk->abuf, &chunk->alen); break; default: SDL_SetError("Unrecognized sound file type"); return(0); } if ( !loaded ) { free(chunk); return(NULL); }#if 0 PrintFormat("Audio device", &mixer); PrintFormat("-- Wave file", &wavespec);#endif /* Build the audio converter and create conversion buffers */ if ( SDL_BuildAudioCVT(&wavecvt, wavespec.format, wavespec.channels, wavespec.freq, mixer.format, mixer.channels, mixer.freq) < 0 ) { SDL_FreeWAV(chunk->abuf); free(chunk); return(NULL); } samplesize = ((wavespec.format & 0xFF)/8)*wavespec.channels; wavecvt.len = chunk->alen & ~(samplesize-1); wavecvt.buf = (Uint8 *)malloc(wavecvt.len*wavecvt.len_mult); if ( wavecvt.buf == NULL ) { SDL_SetError("Out of memory"); SDL_FreeWAV(chunk->abuf); free(chunk); return(NULL); } memcpy(wavecvt.buf, chunk->abuf, chunk->alen); SDL_FreeWAV(chunk->abuf); /* Run the audio converter */ if ( SDL_ConvertAudio(&wavecvt) < 0 ) { free(wavecvt.buf); free(chunk); return(NULL); } chunk->allocated = 1; chunk->abuf = wavecvt.buf; chunk->alen = wavecvt.len_cvt; chunk->volume = MIX_MAX_VOLUME; return(chunk);}/* Load a wave file of the mixer format from a memory buffer */Mix_Chunk *Mix_QuickLoad_WAV(Uint8 *mem){ Mix_Chunk *chunk; Uint8 magic[4]; /* Make sure audio has been opened */ if ( ! audio_opened ) { SDL_SetError("Audio device hasn't been opened"); return(NULL); } /* Allocate the chunk memory */ chunk = (Mix_Chunk *)malloc(sizeof(Mix_Chunk)); if ( chunk == NULL ) { SDL_SetError("Out of memory"); return(NULL); } /* Essentially just skip to the audio data (no error checking - fast) */ chunk->allocated = 0; mem += 12; /* WAV header */ do { memcpy(magic, mem, 4); mem += 4; chunk->alen = ((mem[3]<<24)|(mem[2]<<16)|(mem[1]<<8)|(mem[0])); mem += 4; chunk->abuf = mem; mem += chunk->alen; } while ( memcmp(magic, "data", 4) != 0 ); chunk->volume = MIX_MAX_VOLUME; return(chunk);}/* Load raw audio data of the mixer format from a memory buffer */Mix_Chunk *Mix_QuickLoad_RAW(Uint8 *mem, Uint32 len){ Mix_Chunk *chunk; /* Make sure audio has been opened */ if ( ! audio_opened ) { SDL_SetError("Audio device hasn't been opened"); return(NULL); } /* Allocate the chunk memory */ chunk = (Mix_Chunk *)malloc(sizeof(Mix_Chunk)); if ( chunk == NULL ) { SDL_SetError("Out of memory"); return(NULL); } /* Essentially just point at the audio data (no error checking - fast) */ chunk->allocated = 0; chunk->alen = len; chunk->abuf = mem; chunk->volume = MIX_MAX_VOLUME; return(chunk);}/* Free an audio chunk previously loaded */void Mix_FreeChunk(Mix_Chunk *chunk){ int i; /* Caution -- if the chunk is playing, the mixer will crash */ if ( chunk ) { /* Guarantee that this chunk isn't playing */ SDL_LockAudio(); if ( mix_channel ) { for ( i=0; i<num_channels; ++i ) { if ( chunk == mix_channel[i].chunk ) { mix_channel[i].playing = 0; } } } SDL_UnlockAudio(); /* Actually free the chunk */ if ( chunk->allocated ) { free(chunk->abuf); } free(chunk); }}/* Set a function that is called after all mixing is performed. This can be used to provide real-time visual display of the audio stream or add a custom mixer filter for the stream data.*/void Mix_SetPostMix(void (*mix_func) (void *udata, Uint8 *stream, int len), void *arg){ SDL_LockAudio(); mix_postmix_data = arg; mix_postmix = mix_func; SDL_UnlockAudio();}/* Add your own music player or mixer function. If 'mix_func' is NULL, the default music player is re-enabled. */void Mix_HookMusic(void (*mix_func)(void *udata, Uint8 *stream, int len), void *arg){ SDL_LockAudio(); if ( mix_func != NULL ) { music_data = arg; mix_music = mix_func; } else { music_data = NULL; mix_music = music_mixer; } SDL_UnlockAudio();}void *Mix_GetMusicHookData(void){ return(music_data);}void Mix_ChannelFinished(void (*channel_finished)(int channel)){ SDL_LockAudio(); channel_done_callback = channel_finished; SDL_UnlockAudio();}/* Reserve the first channels (0 -> n-1) for the application, i.e. don't allocate them dynamically to the next sample if requested with a -1 value below. Returns the number of reserved channels. */int Mix_ReserveChannels(int num){ if (num > num_channels) num = num_channels; reserved_channels = num; return num;}static int checkchunkintegral(Mix_Chunk *chunk){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -