123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511 |
- /* -*- mode: c++; tab-width: 4; c-basic-offset: 4; c-file-style: "linux" -*- */
- //
- // Copyright (c) 2015, Lou Yihua <louyihua@21cn.com>.
- // All rights reserved.
- //
- // 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 3 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, see <http://www.gnu.org/licenses/>.
- //
- // This file uses some code from decoder_example.c in libvorbis-1.3.4.
- #include "util.h"
- #include "global.h"
- #include "players.h"
- #include "sound.h"
- #include <math.h>
- #if PAL_HAS_OGG
- #include <vorbis/vorbisfile.h>
- #include "resampler.h"
- #define FLAG_OY 0x01
- #define FLAG_VI 0x02
- #define FLAG_VC 0x04
- #define FLAG_OS 0x08
- #define FLAG_VD 0x10
- #define FLAG_VB 0x20
- #define STAGE_PAGEOUT 1
- #define STAGE_PACKETOUT 2
- #define STAGE_PCMOUT 3
- #define STAGE_REWIND 4
- #define OGG_BUFFER_LENGTH 4096
- typedef struct tagOGGPLAYER
- {
- MUSICPLAYER_FUNCTIONS;
- ogg_sync_state oy; /* sync and verify incoming physical bitstream */
- ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */
- ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
- vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */
- vorbis_comment vc; /* struct that stores all the bitstream user comments */
- vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
- vorbis_block vb; /* local working space for packet->PCM decode */
- FILE *fp;
- void *resampler[2];
- INT iFlags;
- INT iMusic;
- INT iStage;
- INT nChannels;
- BOOL fLoop;
- BOOL fReady;
- BOOL fUseResampler;
- } OGGPLAYER, *LPOGGPLAYER;
- PAL_FORCE_INLINE ogg_int16_t OGG_GetSample(float pcm, double volume)
- {
- int val = (int)(floor(pcm * 32767.f + .5f) * volume);
- /* might as well guard against clipping */
- if (val > 32767) {
- val = 32767;
- }
- else if (val < -32768) {
- val = -32768;
- }
- return (ogg_int16_t)val;
- }
- PAL_FORCE_INLINE void OGG_FillResample(LPOGGPLAYER player, ogg_int16_t* stream)
- {
- if (gpGlobals->iAudioChannels == 2) {
- stream[0] = SDL_SwapLE16(resampler_get_and_remove_sample(player->resampler[0]));
- stream[1] = (player->vi.channels > 1) ? SDL_SwapLE16(resampler_get_and_remove_sample(player->resampler[1])) : stream[0];
- }
- else {
- if (player->vi.channels > 1) {
- *stream = SDL_SwapLE16((short)((int)(resampler_get_and_remove_sample(player->resampler[0]) + resampler_get_and_remove_sample(player->resampler[1])) >> 1));
- }
- else {
- *stream = SDL_SwapLE16(resampler_get_and_remove_sample(player->resampler[0]));
- }
- }
- }
- static void OGG_Cleanup(LPOGGPLAYER player)
- {
- int i;
- for (i = 0; i < gpGlobals->iAudioChannels; i++) resampler_clear(player->resampler[0]);
- /* Do various cleanups */
- if (player->iFlags & FLAG_VB) vorbis_block_clear(&player->vb);
- if (player->iFlags & FLAG_VD) vorbis_dsp_clear(&player->vd);
- if (player->iFlags & FLAG_OS) ogg_stream_clear(&player->os);
- if (player->iFlags & FLAG_VC) vorbis_comment_clear(&player->vc);
- if (player->iFlags & FLAG_VI) vorbis_info_clear(&player->vi); /* must be called last */
- if (player->iFlags & FLAG_OY) ogg_sync_clear(&player->oy);
- player->iFlags = player->iStage = 0;
- player->fReady = FALSE;
- }
- static BOOL OGG_Rewind(LPOGGPLAYER player)
- {
- ogg_packet op; /* one raw packet of data for decode */
- char *buffer;
- int i, bytes;
- OGG_Cleanup(player);
- fseek(player->fp, 0, SEEK_SET);
- ogg_sync_init(&player->oy); player->iFlags = FLAG_OY;
- /* grab some data at the head of the stream. We want the first page
- (which is guaranteed to be small and only contain the Vorbis
- stream initial header) We need the first page to get the stream
- serialno. */
- /* submit a 4k block to libvorbis' Ogg layer */
- buffer = ogg_sync_buffer(&player->oy, OGG_BUFFER_LENGTH);
- bytes = fread(buffer, 1, OGG_BUFFER_LENGTH, player->fp);
- ogg_sync_wrote(&player->oy, bytes);
- /* Get the first page. */
- if (ogg_sync_pageout(&player->oy, &player->og) != 1) {
- /* have we simply run out of data? If so, we're done. */
- /* error case. Must not be Vorbis data */
- OGG_Cleanup(player);
- return (player->fReady = FALSE);
- }
- /* Get the serial number and set up the rest of decode. */
- /* serialno first; use it to set up a logical stream */
- ogg_stream_init(&player->os, ogg_page_serialno(&player->og));
- player->iFlags |= FLAG_OS;
- /* extract the initial header from the first page and verify that the
- Ogg bitstream is in fact Vorbis data */
- /* I handle the initial header first instead of just having the code
- read all three Vorbis headers at once because reading the initial
- header is an easy way to identify a Vorbis bitstream and it's
- useful to see that functionality seperated out. */
- vorbis_info_init(&player->vi); player->iFlags |= FLAG_VI;
- vorbis_comment_init(&player->vc); player->iFlags |= FLAG_VC;
- if (ogg_stream_pagein(&player->os, &player->og)<0) {
- /* error; stream version mismatch perhaps */
- OGG_Cleanup(player);
- return (player->fReady = FALSE);
- }
- if (ogg_stream_packetout(&player->os, &op) != 1) {
- /* no page? must not be vorbis */
- OGG_Cleanup(player);
- return (player->fReady = FALSE);
- }
- if (vorbis_synthesis_headerin(&player->vi, &player->vc, &op)<0) {
- /* error case; not a vorbis header */
- OGG_Cleanup(player);
- return (player->fReady = FALSE);
- }
- /* At this point, we're sure we're Vorbis. We've set up the logical
- (Ogg) bitstream decoder. Get the comment and codebook headers and
- set up the Vorbis decoder */
- /* The next two packets in order are the comment and codebook headers.
- They're likely large and may span multiple pages. Thus we read
- and submit data until we get our two packets, watching that no
- pages are missing. If a page is missing, error out; losing a
- header page is the only place where missing data is fatal. */
- i = 0;
- while (i < 2) {
- while (i < 2) {
- int result = ogg_sync_pageout(&player->oy, &player->og);
- if (result == 0)break; /* Need more data */
- /* Don't complain about missing or corrupt data yet. We'll
- catch it at the packet output phase */
- if (result == 1) {
- ogg_stream_pagein(&player->os, &player->og); /* we can ignore any errors here
- as they'll also become apparent
- at packetout */
- while (i < 2) {
- result = ogg_stream_packetout(&player->os, &op);
- if (result == 0)break;
- if (result < 0) {
- /* Uh oh; data at some point was corrupted or missing!
- We can't tolerate that in a header. Die. */
- OGG_Cleanup(player);
- return (player->fReady = FALSE);
- }
- result = vorbis_synthesis_headerin(&player->vi, &player->vc, &op);
- if (result < 0) {
- OGG_Cleanup(player);
- return (player->fReady = FALSE);
- }
- i++;
- }
- }
- }
- /* no harm in not checking before adding more */
- buffer = ogg_sync_buffer(&player->oy, OGG_BUFFER_LENGTH);
- bytes = fread(buffer, 1, OGG_BUFFER_LENGTH, player->fp);
- if (bytes == 0 && i < 2) {
- OGG_Cleanup(player);
- return (player->fReady = FALSE);
- }
- ogg_sync_wrote(&player->oy, bytes);
- }
- if (vorbis_synthesis_init(&player->vd, &player->vi) == 0) { /* central decode state */
- vorbis_block_init(&player->vd, &player->vb); /* local state for most of the decode
- so multiple block decodes can
- proceed in parallel. We could init
- multiple vorbis_block structures
- for vd here */
- player->iStage = STAGE_PAGEOUT;
- player->iFlags |= FLAG_VD | FLAG_VB;
- if (player->fUseResampler = player->vi.rate != gpGlobals->iSampleRate) {
- double factor = (double)player->vi.rate / (double)gpGlobals->iSampleRate;
- for (i = 0; i < min(player->vi.channels, 2); i++)
- {
- resampler_set_quality(player->resampler[i], SOUND_IsIntegerConversion(player->vi.rate) ? RESAMPLER_QUALITY_MIN : gpGlobals->iResampleQuality);
- resampler_set_rate(player->resampler[i], factor);
- resampler_clear(player->resampler[i]);
- }
- }
- return (player->fReady = TRUE);
- }
- else {
- OGG_Cleanup(player);
- return (player->fReady = FALSE);
- }
- }
- static VOID
- OGG_FillBuffer(
- VOID *object,
- LPBYTE stream,
- INT len
- )
- {
- LPOGGPLAYER player = (LPOGGPLAYER)object;
- if (player->fReady) {
- ogg_packet op; /* one raw packet of data for decode */
- double volume = (double)gpGlobals->iVolume / (SDL_MIX_MAXVOLUME * 3 / 4);
- int total_bytes = 0, stage = player->iStage;
- while (total_bytes < len) {
- float **pcm;
- int samples, result;
- switch (stage)
- {
- case STAGE_PAGEOUT: /* PAGEOUT stage */
- result = ogg_sync_pageout(&player->oy, &player->og);
- if (result > 0) {
- /* can safely ignore errors at this point */
- ogg_stream_pagein(&player->os, &player->og);
- stage = STAGE_PACKETOUT;
- }
- else {
- if (result == 0) { /* need more data */
- char *buffer = ogg_sync_buffer(&player->oy, OGG_BUFFER_LENGTH);
- int bytes = fread(buffer, 1, OGG_BUFFER_LENGTH, player->fp);
- ogg_sync_wrote(&player->oy, bytes);
- stage = (bytes > 0) ? STAGE_PAGEOUT : STAGE_REWIND;
- }
- break;
- }
- case STAGE_PACKETOUT:
- result = ogg_stream_packetout(&player->os, &op);
- if (result > 0) {
- /* we have a packet. Decode it */
- if (vorbis_synthesis(&player->vb, &op) == 0) { /* test for success! */
- vorbis_synthesis_blockin(&player->vd, &player->vb);
- }
- stage = STAGE_PCMOUT;
- }
- else {
- if (result == 0) { /* need more data */
- if (ogg_page_eos(&player->og)) {
- if (player->fLoop) {
- stage = STAGE_REWIND;
- }
- else {
- OGG_Cleanup(player);
- UTIL_CloseFile(player->fp);
- player->fp = NULL;
- return;
- }
- }
- else {
- stage = STAGE_PAGEOUT;
- }
- }
- break;
- }
- case STAGE_PCMOUT:
- if ((samples = vorbis_synthesis_pcmout(&player->vd, &pcm)) > 0) {
- int bout;
- if (player->fUseResampler) { /* Resampler is available and should be used */
- bout = 0;
- while (total_bytes < len && samples > 0) { /* Fill as many samples into resampler as possible */
- int i, j, to_write = resampler_get_free_count(player->resampler[0]);
- if (to_write > 0) {
- if (to_write >= samples) to_write = samples;
- for (i = 0; i < min(player->vi.channels, 2); i++) {
- float *mono = pcm[i] + bout;
- for (j = 0; j < to_write; j++) {
- resampler_write_sample(player->resampler[i], OGG_GetSample(mono[j], volume));
- }
- }
- }
- /* Fetch resampled samples if available */
- j = resampler_get_sample_count(player->resampler[0]);
- while (total_bytes < len && resampler_get_sample_count(player->resampler[0]) > 0) {
- OGG_FillResample(player, (ogg_int16_t *)(stream + total_bytes));
- total_bytes += gpGlobals->iAudioChannels * sizeof(ogg_int16_t);
- }
- samples -= to_write; bout += to_write;
- }
- }
- else {
- int i;
- ogg_int16_t *ptr = (ogg_int16_t *)(stream + total_bytes);
- bout = (len - total_bytes) / gpGlobals->iAudioChannels / sizeof(ogg_int16_t);
- if (bout > samples) bout = samples;
- for (i = 0; i < bout; i++) {
- if (gpGlobals->iAudioChannels == 2) {
- ptr[0] = SDL_SwapLE16(OGG_GetSample(pcm[0][i], volume));
- ptr[1] = (player->vi.channels > 1) ? SDL_SwapLE16(OGG_GetSample(pcm[1][i], volume)) : ptr[0];
- }
- else {
- if (player->vi.channels > 1) {
- ptr[0] = SDL_SwapLE16((short)((int)(OGG_GetSample(pcm[0][i], volume) + OGG_GetSample(pcm[1][i], volume)) >> 1));
- }
- else {
- ptr[0] = SDL_SwapLE16(OGG_GetSample(pcm[0][i], volume));
- }
- }
- ptr += gpGlobals->iAudioChannels;
- }
- total_bytes += bout * gpGlobals->iAudioChannels * sizeof(ogg_int16_t);
- }
- /* tell libvorbis how many samples we actually consumed */
- vorbis_synthesis_read(&player->vd, bout);
- }
- else {
- stage = STAGE_PACKETOUT;
- }
- break;
- case STAGE_REWIND:
- if (player->vi.rate != gpGlobals->iSampleRate) { /* If there are samples in the resampler, fetch them first */
- while (total_bytes < len && resampler_get_sample_count(player->resampler[0]) > 0) {
- OGG_FillResample(player, (ogg_int16_t *)(stream + total_bytes));
- total_bytes += gpGlobals->iAudioChannels * sizeof(ogg_int16_t);
- }
- /* Break out if there are still samples in the resampler */
- if (resampler_get_sample_count(player->resampler[0]) > 0) break;
- }
- OGG_Rewind(player);
- stage = player->iStage;
- break;
- default:
- return;
- }
- }
- player->iStage = stage;
- }
- }
- static BOOL
- OGG_Play(
- VOID *object,
- INT iNum,
- BOOL fLoop,
- FLOAT flFadeTime
- )
- {
- char filename[256];
- LPOGGPLAYER player = (LPOGGPLAYER)object;
- //
- // Check for NULL pointer.
- //
- if (player == NULL)
- {
- return FALSE;
- }
- player->fLoop = fLoop;
- if (iNum == player->iMusic)
- {
- return TRUE;
- }
- player->fReady = FALSE;
- OGG_Cleanup(player);
- if (player->fp)
- {
- UTIL_CloseFile(player->fp);
- player->fp = NULL;
- }
- if (iNum == -1)
- {
- return TRUE;
- }
- player->fp = UTIL_OpenFile(strcpy(filename, va("ogg/%.2d.ogg", iNum)));
- if (player->fp == NULL)
- {
- return FALSE;
- }
- else
- {
- player->iMusic = iNum;
- }
- if (!OGG_Rewind(player))
- {
- UTIL_CloseFile(player->fp);
- player->fp = NULL;
- return FALSE;
- }
- return TRUE;
- }
- static VOID
- OGG_Shutdown(
- VOID *object
- )
- {
- if (object)
- {
- LPOGGPLAYER player = (LPOGGPLAYER)object;
- OGG_Cleanup(player);
- resampler_delete(player->resampler[0]);
- resampler_delete(player->resampler[1]);
- UTIL_CloseFile(player->fp);
- free(player);
- }
- }
- LPMUSICPLAYER
- OGG_Init(
- LPCSTR szFileName
- )
- {
- LPOGGPLAYER player;
- if (player = (LPOGGPLAYER)malloc(sizeof(OGGPLAYER)))
- {
- memset(player, 0, sizeof(LPOGGPLAYER));
- player->FillBuffer = OGG_FillBuffer;
- player->Play = OGG_Play;
- player->Shutdown = OGG_Shutdown;
- player->fp = NULL;
- player->iMusic = -1;
- player->iFlags = 0;
- player->iStage = 0;
- player->fLoop = FALSE;
- player->fReady = FALSE;
- player->fUseResampler = FALSE;
- player->resampler[0] = resampler_create();
- if (player->resampler[0])
- {
- player->resampler[1] = resampler_create();
- if (player->resampler[1] == NULL)
- {
- resampler_delete(player->resampler[0]);
- player->resampler[0] = NULL;
- }
- }
- return (LPMUSICPLAYER)player;
- }
- else
- {
- return NULL;
- }
- }
- #endif
|