/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. * * ======================================================================= * * This file implements an interface to libvorbis for decoding * OGG/Vorbis files. Strongly spoken this file isn't part of the sound * system but part of the main client. It justs converts Vorbis streams * into normal, raw Wave stream which are injected into the backends as * if they were normal "raw" samples. At this moment only background * music playback and in theory .cin movie file playback is supported. * * ======================================================================= */ #ifdef OGG #define OV_EXCLUDE_STATIC_CALLBACKS #ifndef _WIN32 #include #endif #include #include #include "../header/client.h" #include "header/local.h" #include "header/vorbis.h" qboolean ogg_first_init = true; /* First initialization flag. */ qboolean ogg_started = false; /* Initialization flag. */ int ogg_bigendian = 0; byte *ogg_buffer; /* File buffer. */ char **ogg_filelist; /* List of Ogg Vorbis files. */ char ovBuf[4096]; /* Buffer for sound. */ int ogg_curfile; /* Index of currently played file. */ int ogg_numfiles; /* Number of Ogg Vorbis files. */ int ovSection; /* Position in Ogg Vorbis file. */ ogg_status_t ogg_status; /* Status indicator. */ cvar_t *ogg_autoplay; /* Play this song when started. */ cvar_t *ogg_check; /* Check Ogg files or not. */ cvar_t *ogg_playlist; /* Playlist. */ cvar_t *ogg_sequence; /* Sequence play indicator. */ cvar_t *ogg_volume; /* Music volume. */ cvar_t *ogg_ignoretrack0; /* Toggle track 0 playing */ OggVorbis_File ovFile; /* Ogg Vorbis file. */ vorbis_info *ogg_info; /* Ogg Vorbis file information */ int ogg_numbufs; /* Number of buffers for OpenAL */ /* * Initialize the Ogg Vorbis subsystem. */ void OGG_Init(void) { cvar_t *cv; /* Cvar pointer. */ if (ogg_started) { return; } Com_Printf("Starting Ogg Vorbis.\n"); /* Skip initialization if disabled. */ cv = Cvar_Get("ogg_enable", "1", CVAR_ARCHIVE); if (cv->value != 1) { Com_Printf("Ogg Vorbis not initializing.\n"); return; } if (bigendien == true) { ogg_bigendian = 1; } /* Cvars. */ ogg_autoplay = Cvar_Get("ogg_autoplay", "?", CVAR_ARCHIVE); ogg_check = Cvar_Get("ogg_check", "0", CVAR_ARCHIVE); ogg_playlist = Cvar_Get("ogg_playlist", "playlist", CVAR_ARCHIVE); ogg_sequence = Cvar_Get("ogg_sequence", "loop", CVAR_ARCHIVE); ogg_volume = Cvar_Get("ogg_volume", "0.7", CVAR_ARCHIVE); ogg_ignoretrack0 = Cvar_Get("ogg_ignoretrack0", "0", CVAR_ARCHIVE); /* Console commands. */ Cmd_AddCommand("ogg_list", OGG_ListCmd); Cmd_AddCommand("ogg_pause", OGG_PauseCmd); Cmd_AddCommand("ogg_play", OGG_PlayCmd); Cmd_AddCommand("ogg_reinit", OGG_Reinit); Cmd_AddCommand("ogg_resume", OGG_ResumeCmd); Cmd_AddCommand("ogg_seek", OGG_SeekCmd); Cmd_AddCommand("ogg_status", OGG_StatusCmd); Cmd_AddCommand("ogg_stop", OGG_Stop); /* Build list of files. */ ogg_numfiles = 0; if (ogg_playlist->string[0] != '\0') { OGG_LoadPlaylist(ogg_playlist->string); } if (ogg_numfiles == 0) { OGG_LoadFileList(); } /* Check if we have Ogg Vorbis files. */ if (ogg_numfiles <= 0) { Com_Printf("No Ogg Vorbis files found.\n"); ogg_started = true; /* For OGG_Shutdown(). */ OGG_Shutdown(); return; } /* Initialize variables. */ if (ogg_first_init) { ogg_buffer = NULL; ogg_curfile = -1; ogg_info = NULL; ogg_status = STOP; ogg_first_init = false; } ogg_started = true; Com_Printf("%d Ogg Vorbis files found.\n", ogg_numfiles); /* Autoplay support. */ if (ogg_autoplay->string[0] != '\0') { OGG_ParseCmd(ogg_autoplay->string); } } /* * Shutdown the Ogg Vorbis subsystem. */ void OGG_Shutdown(void) { if (!ogg_started) { return; } Com_Printf("Shutting down Ogg Vorbis.\n"); OGG_Stop(); /* Free the list of files. */ FS_FreeList(ogg_filelist, ogg_numfiles + 1); /* Remove console commands. */ Cmd_RemoveCommand("ogg_list"); Cmd_RemoveCommand("ogg_pause"); Cmd_RemoveCommand("ogg_play"); Cmd_RemoveCommand("ogg_reinit"); Cmd_RemoveCommand("ogg_resume"); Cmd_RemoveCommand("ogg_seek"); Cmd_RemoveCommand("ogg_status"); Cmd_RemoveCommand("ogg_stop"); ogg_started = false; } /* * Reinitialize the Ogg Vorbis subsystem. */ void OGG_Reinit(void) { OGG_Shutdown(); OGG_Init(); } /* * Check if the file is a valid Ogg Vorbis file. */ qboolean OGG_Check(char *name) { qboolean res; /* Return value. */ byte *buffer; /* File buffer. */ int size; /* File size. */ OggVorbis_File ovf; /* Ogg Vorbis file. */ if (ogg_check->value == 0) { return true; } res = false; if ((size = FS_LoadFile(name, (void **)&buffer)) > 0) { if (ov_test(NULL, &ovf, (char *)buffer, size) == 0) { res = true; ov_clear(&ovf); } FS_FreeFile(buffer); } return res; } /* * Change position in the file. */ void OGG_Seek(ogg_seek_t type, double offset) { double pos; /* Position in file (in seconds). */ double total; /* Length of file (in seconds). */ /* Check if the file is seekable. */ if (ov_seekable(&ovFile) == 0) { Com_Printf("OGG_Seek: file is not seekable.\n"); return; } /* Get file information. */ pos = ov_time_tell(&ovFile); total = ov_time_total(&ovFile, -1); switch (type) { case ABS: if ((offset >= 0) && (offset <= total)) { if (ov_time_seek(&ovFile, offset) != 0) { Com_Printf("OGG_Seek: could not seek.\n"); } else { Com_Printf("%0.2f -> %0.2f of %0.2f.\n", pos, offset, total); } } else { Com_Printf("OGG_Seek: invalid offset.\n"); } break; case REL: if ((pos + offset >= 0) && (pos + offset <= total)) { if (ov_time_seek(&ovFile, pos + offset) != 0) { Com_Printf("OGG_Seek: could not seek.\n"); } else { Com_Printf("%0.2f -> %0.2f of %0.2f.\n", pos, pos + offset, total); } } else { Com_Printf("OGG_Seek: invalid offset.\n"); } break; } } /* * Load list of Ogg Vorbis files in "music". */ void OGG_LoadFileList(void) { char **list; /* List of .ogg files. */ int i; /* Loop counter. */ int j; /* Real position in list. */ /* Get file list. */ list = FS_ListFiles2(va("%s/*.ogg", OGG_DIR), &ogg_numfiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM); ogg_numfiles--; /* Check if there are posible Ogg files. */ if (list == NULL) { return; } /* Allocate list of files. */ ogg_filelist = malloc(sizeof(char *) * ogg_numfiles); /* Add valid Ogg Vorbis file to the list. */ for (i = 0, j = 0; i < ogg_numfiles; i++) { if (!OGG_Check(list[i])) { free(list[i]); continue; } ogg_filelist[j++] = list[i]; } /* Free the file list. */ free(list); /* Adjust the list size (remove space for invalid music files). */ ogg_numfiles = j; ogg_filelist = realloc(ogg_filelist, sizeof(char *) * ogg_numfiles); } /* * Load playlist. */ void OGG_LoadPlaylist(char *playlist) { byte *buffer; /* Buffer to read the file. */ char *ptr; /* Pointer for parsing the file. */ int i; /* Loop counter. */ int size; /* Length of buffer and strings. */ /* Open playlist. */ if ((size = FS_LoadFile(va("%s/%s.lst", OGG_DIR, ogg_playlist->string), (void **)&buffer)) < 0) { Com_Printf("OGG_LoadPlaylist: could not open playlist: %s.\n", strerror(errno)); return; } /* Count the files in playlist. */ for (ptr = strtok((char *)buffer, "\n"); ptr != NULL; ptr = strtok(NULL, "\n")) { if ((byte *)ptr != buffer) { ptr[-1] = '\n'; } if (OGG_Check(va("%s/%s", OGG_DIR, ptr))) { ogg_numfiles++; } } /* Allocate file list. */ ogg_filelist = malloc(sizeof(char *) * ogg_numfiles); i = 0; for (ptr = strtok((char *)buffer, "\n"); ptr != NULL; ptr = strtok(NULL, "\n")) { if (OGG_Check(va("%s/%s", OGG_DIR, ptr))) { ogg_filelist[i++] = strdup(va("%s/%s", OGG_DIR, ptr)); } } /* Free file buffer. */ FS_FreeFile(buffer); } /* * Play Ogg Vorbis file (with absolute or relative index). */ qboolean OGG_Open(ogg_seek_t type, int offset) { int size; /* File size. */ int pos = -1; /* Absolute position. */ int res; /* Error indicator. */ switch (type) { case ABS: /* Absolute index. */ if ((offset < 0) || (offset >= ogg_numfiles)) { Com_Printf("OGG_Open: %d out of range.\n", offset + 1); return false; } else { pos = offset; } break; case REL: /* Simulate a loopback. */ if ((ogg_curfile == -1) && (offset < 0)) { offset++; } while (ogg_curfile + offset < 0) { offset += ogg_numfiles; } while (ogg_curfile + offset >= ogg_numfiles) { offset -= ogg_numfiles; } pos = ogg_curfile + offset; break; } /* Check running music. */ if (ogg_status == PLAY) { if (ogg_curfile == pos) { return true; } else { OGG_Stop(); } } /* Find file. */ if ((size = FS_LoadFile(ogg_filelist[pos], (void **)&ogg_buffer)) == -1) { Com_Printf("OGG_Open: could not open %d (%s): %s.\n", pos, ogg_filelist[pos], strerror(errno)); return false; } /* Open ogg vorbis file. */ if ((res = ov_open(NULL, &ovFile, (char *)ogg_buffer, size)) < 0) { Com_Printf("OGG_Open: '%s' is not a valid Ogg Vorbis file (error %i).\n", ogg_filelist[pos], res); FS_FreeFile(ogg_buffer); ogg_buffer = NULL; return false; } ogg_info = ov_info(&ovFile, 0); if (!ogg_info) { Com_Printf("OGG_Open: Unable to get stream information for %s.\n", ogg_filelist[pos]); ov_clear(&ovFile); FS_FreeFile(ogg_buffer); ogg_buffer = NULL; return false; } /* Play file. */ ovSection = 0; ogg_curfile = pos; ogg_status = PLAY; return true; } /* * Play Ogg Vorbis file (with name only). */ qboolean OGG_OpenName(char *filename) { char *name; /* File name. */ int i; /* Loop counter. */ /* If the track name is '00', and ogg_ignoretrack0 is set to 0, stop playback */ if ((!strncmp(filename, "00", sizeof(char) * 3)) && ogg_ignoretrack0->value == 0) { OGG_PauseCmd(); return false; } name = va("%s/%s.ogg", OGG_DIR, filename); for (i = 0; i < ogg_numfiles; i++) { if (strcmp(name, ogg_filelist[i]) == 0) { break; } } if (i < ogg_numfiles) { return OGG_Open(ABS, i); } else { Com_Printf("OGG_OpenName: '%s' not in the list.\n", filename); return false; } } /* * Play a portion of the currently opened file. */ int OGG_Read(void) { int res; /* Number of bytes read. */ /* Read and resample. */ res = ov_read(&ovFile, ovBuf, sizeof(ovBuf), ogg_bigendian, OGG_SAMPLEWIDTH, 1, &ovSection); S_RawSamples(res / (OGG_SAMPLEWIDTH * ogg_info->channels), ogg_info->rate, OGG_SAMPLEWIDTH, ogg_info->channels, (byte *)ovBuf, ogg_volume->value); /* Check for end of file. */ if (res == 0) { OGG_Stop(); OGG_Sequence(); } return res; } /* * Play files in sequence. */ void OGG_Sequence(void) { if (strcmp(ogg_sequence->string, "next") == 0) { OGG_Open(REL, 1); } else if (strcmp(ogg_sequence->string, "prev") == 0) { OGG_Open(REL, -1); } else if (strcmp(ogg_sequence->string, "random") == 0) { OGG_Open(ABS, randk() % ogg_numfiles); } else if (strcmp(ogg_sequence->string, "loop") == 0) { OGG_Open(REL, 0); } else if (strcmp(ogg_sequence->string, "none") != 0) { Com_Printf("Invalid value of ogg_sequence: %s\n", ogg_sequence->string); Cvar_Set("ogg_sequence", "none"); } } /* * Stop playing the current file. */ void OGG_Stop(void) { if (ogg_status == STOP) { return; } #ifdef USE_OPENAL if (sound_started == SS_OAL) { AL_UnqueueRawSamples(); } #endif ov_clear(&ovFile); ogg_status = STOP; ogg_info = NULL; ogg_numbufs = 0; if (ogg_buffer != NULL) { FS_FreeFile(ogg_buffer); ogg_buffer = NULL; } } /* * Stream music. */ void OGG_Stream(void) { if (!ogg_started) { return; } if (ogg_status == PLAY) { #ifdef USE_OPENAL if (sound_started == SS_OAL) { /* Calculate the number of buffers used for storing decoded OGG/Vorbis data. We take the number of active buffers at startup (at this point most of the samples should be precached and loaded into buffers) and add 64. Empircal testing showed, that at most times at least 52 buffers remain available for OGG/Vorbis, enough for about 3 seconds playback. The music won't stutter as long as the framerate stayes over 1 FPS. */ if (ogg_numbufs == 0) { ogg_numbufs = active_buffers + 64; } /* active_buffers are all active OpenAL buffers, buffering normal sfx _and_ ogg/vorbis samples. */ while (active_buffers <= ogg_numbufs) { OGG_Read(); } } else /* using SDL */ #endif { if (sound_started == SS_SDL) { /* Read that number samples into the buffer, that were played since the last call to this function. This keeps the buffer at all times at an "optimal" fill level. */ while (paintedtime + MAX_RAW_SAMPLES - 2048 > s_rawend) { OGG_Read(); } } } /* using SDL */ } /* ogg_status == PLAY */ } /* * List Ogg Vorbis files. */ void OGG_ListCmd(void) { int i; for (i = 0; i < ogg_numfiles; i++) { Com_Printf("%d %s\n", i + 1, ogg_filelist[i]); } Com_Printf("%d Ogg Vorbis files.\n", ogg_numfiles); } /* * Parse play controls. */ void OGG_ParseCmd(char *arg) { int n; cvar_t *ogg_enable; ogg_enable = Cvar_Get("ogg_enable", "1", CVAR_ARCHIVE); switch (arg[0]) { case '#': n = (int)strtol(arg + 1, (char **)NULL, 10) - 1; OGG_Open(ABS, n); break; case '?': OGG_Open(ABS, randk() % ogg_numfiles); break; case '>': if (strlen(arg) > 1) { OGG_Open(REL, (int)strtol(arg + 1, (char **)NULL, 10)); } else { OGG_Open(REL, 1); } break; case '<': if (strlen(arg) > 1) { OGG_Open(REL, -(int)strtol(arg + 1, (char **)NULL, 10)); } else { OGG_Open(REL, -1); } break; default: if (ogg_enable->value != 0) { OGG_OpenName(arg); } break; } } /* * Pause current song. */ void OGG_PauseCmd(void) { if (ogg_status == PLAY) { ogg_status = PAUSE; ogg_numbufs = 0; } #ifdef USE_OPENAL if (sound_started == SS_OAL) { AL_UnqueueRawSamples(); } #endif } /* * Play control. */ void OGG_PlayCmd(void) { if (Cmd_Argc() < 2) { Com_Printf("Usage: ogg_play {filename | #n | ? | >n | n}\n"); return; } switch (Cmd_Argv(1)[0]) { case '>': OGG_Seek(REL, strtod(Cmd_Argv(1) + 1, (char **)NULL)); break; case '<': OGG_Seek(REL, -strtod(Cmd_Argv(1) + 1, (char **)NULL)); break; default: OGG_Seek(ABS, strtod(Cmd_Argv(1), (char **)NULL)); break; } } /* * Display status. */ void OGG_StatusCmd(void) { switch (ogg_status) { case PLAY: Com_Printf("Playing file %d (%s) at %0.2f seconds.\n", ogg_curfile + 1, ogg_filelist[ogg_curfile], ov_time_tell(&ovFile)); break; case PAUSE: Com_Printf("Paused file %d (%s) at %0.2f seconds.\n", ogg_curfile + 1, ogg_filelist[ogg_curfile], ov_time_tell(&ovFile)); break; case STOP: if (ogg_curfile == -1) { Com_Printf("Stopped.\n"); } else { Com_Printf("Stopped file %d (%s).\n", ogg_curfile + 1, ogg_filelist[ogg_curfile]); } break; } } #endif /* OGG */