mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-14 08:50:58 +00:00
4e550ac9c7
Sounds no longer use the cache, which is good for multi-threaded, but a pain for memory management: the buffers are shared between channels that play back the sounds, but when the sounds were cached, they were automagically (thus problematically) freed when the space was needed. That no longer happens, so they leak. I think the solution is to use reference counting and retain/release in sfx->open() and sfx->close().
469 lines
12 KiB
C
469 lines
12 KiB
C
/*
|
|
flac.c
|
|
|
|
FLAC support
|
|
|
|
Copyright (C) 2005 Bill Currie <bill@taniwha.org>
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2005/6/15
|
|
|
|
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:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include "string.h"
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include "strings.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <FLAC/export.h>
|
|
|
|
#include "qfalloca.h"
|
|
|
|
/* FLAC 1.1.3 has FLAC_API_VERSION_CURRENT == 8 */
|
|
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT < 8
|
|
#define LEGACY_FLAC
|
|
#else
|
|
#undef LEGACY_FLAC
|
|
#endif
|
|
|
|
#ifdef LEGACY_FLAC
|
|
#include <FLAC/seekable_stream_decoder.h>
|
|
#else
|
|
#include <FLAC/stream_decoder.h>
|
|
#endif
|
|
#include <FLAC/metadata.h>
|
|
|
|
#include "QF/cvar.h"
|
|
#include "QF/qendian.h"
|
|
#include "QF/quakefs.h"
|
|
#include "QF/sound.h"
|
|
#include "QF/sys.h"
|
|
|
|
#include "snd_internal.h"
|
|
|
|
#ifdef LEGACY_FLAC
|
|
#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder
|
|
#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus
|
|
#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus
|
|
#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus
|
|
#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus
|
|
|
|
#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new
|
|
#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish
|
|
#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete
|
|
#define FLAC__stream_decoder_process_single \
|
|
FLAC__seekable_stream_decoder_process_single
|
|
#define FLAC__stream_decoder_seek_absolute \
|
|
FLAC__seekable_stream_decoder_seek_absolute
|
|
|
|
#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE \
|
|
FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
|
|
#define FLAC__STREAM_DECODER_SEEK_STATUS_OK \
|
|
FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
|
|
#define FLAC__STREAM_DECODER_TELL_STATUS_OK \
|
|
FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
|
|
#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK \
|
|
FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
|
|
#endif
|
|
|
|
typedef struct {
|
|
FLAC__StreamDecoder *decoder;
|
|
QFile *file;
|
|
FLAC__StreamMetadata_StreamInfo info;
|
|
FLAC__StreamMetadata *vorbis_info;
|
|
float *buffer;
|
|
int size; // in frames
|
|
int pos; // in frames
|
|
} flacfile_t;
|
|
|
|
static void
|
|
flac_error_func (const FLAC__StreamDecoder *decoder,
|
|
FLAC__StreamDecoderErrorStatus status, void *client_data)
|
|
{
|
|
}
|
|
|
|
static FLAC__StreamDecoderReadStatus
|
|
flac_read_func (const FLAC__StreamDecoder *decoder, FLAC__byte buffer[],
|
|
size_t *bytes, void *client_data)
|
|
{
|
|
flacfile_t *ff = (flacfile_t *) client_data;
|
|
*bytes = Qread (ff->file, buffer, *bytes);
|
|
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
|
}
|
|
|
|
static FLAC__StreamDecoderSeekStatus
|
|
flac_seek_func (const FLAC__StreamDecoder *decoder,
|
|
FLAC__uint64 absolute_byte_offset, void *client_data)
|
|
{
|
|
flacfile_t *ff = (flacfile_t *) client_data;
|
|
Qseek (ff->file, absolute_byte_offset, SEEK_SET);
|
|
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
|
|
}
|
|
|
|
static FLAC__StreamDecoderTellStatus
|
|
flac_tell_func (const FLAC__StreamDecoder *decoder,
|
|
FLAC__uint64 *absolute_byte_offset, void *client_data)
|
|
{
|
|
flacfile_t *ff = (flacfile_t *) client_data;
|
|
*absolute_byte_offset = Qtell (ff->file);
|
|
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
|
}
|
|
|
|
static FLAC__StreamDecoderLengthStatus
|
|
flac_length_func (const FLAC__StreamDecoder *decoder,
|
|
FLAC__uint64 *stream_length, void *client_data)
|
|
{
|
|
flacfile_t *ff = (flacfile_t *) client_data;
|
|
*stream_length = Qfilesize (ff->file);
|
|
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
|
|
}
|
|
|
|
static FLAC__bool
|
|
flac_eof_func (const FLAC__StreamDecoder *decoder, void *client_data)
|
|
{
|
|
flacfile_t *ff = (flacfile_t *) client_data;
|
|
return Qeof (ff->file);
|
|
}
|
|
|
|
static FLAC__StreamDecoderWriteStatus
|
|
flac_write_func (const FLAC__StreamDecoder *decoder,
|
|
const FLAC__Frame *frame, const FLAC__int32 * const buffer[],
|
|
void *client_data)
|
|
{
|
|
flacfile_t *ff = (flacfile_t *) client_data;
|
|
float *out;
|
|
float scale = 2.0 / (1 << ff->info.bits_per_sample);
|
|
int step = ff->info.channels;
|
|
unsigned j;
|
|
int i;
|
|
|
|
if (!ff->buffer)
|
|
ff->buffer = calloc (ff->info.max_blocksize * ff->info.channels,
|
|
sizeof (float));
|
|
ff->size = frame->header.blocksize;
|
|
ff->pos = 0;
|
|
for (j = 0; j < ff->info.channels; j++) {
|
|
const FLAC__int32 *in = buffer[j];
|
|
|
|
out = ff->buffer + j;
|
|
for (i = 0; i < ff->size; i++, out += step)
|
|
*out = *in++ * scale;
|
|
}
|
|
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
flac_meta_func (const FLAC__StreamDecoder *decoder,
|
|
const FLAC__StreamMetadata *metadata, void *client_data)
|
|
{
|
|
flacfile_t *ff = (flacfile_t *) client_data;
|
|
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO)
|
|
ff->info = metadata->data.stream_info;
|
|
if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
|
|
ff->vorbis_info = FLAC__metadata_object_clone (metadata);
|
|
}
|
|
|
|
static flacfile_t *
|
|
flac_open (QFile *file)
|
|
{
|
|
flacfile_t *ff = calloc (1, sizeof (flacfile_t));
|
|
ff->decoder = FLAC__stream_decoder_new ();
|
|
ff->file = file;
|
|
|
|
#ifdef LEGACY_FLAC
|
|
FLAC__seekable_stream_decoder_set_error_callback (ff->decoder,
|
|
flac_error_func);
|
|
FLAC__seekable_stream_decoder_set_read_callback (ff->decoder,
|
|
flac_read_func);
|
|
FLAC__seekable_stream_decoder_set_seek_callback (ff->decoder,
|
|
flac_seek_func);
|
|
FLAC__seekable_stream_decoder_set_tell_callback (ff->decoder,
|
|
flac_tell_func);
|
|
FLAC__seekable_stream_decoder_set_length_callback (ff->decoder,
|
|
flac_length_func);
|
|
FLAC__seekable_stream_decoder_set_eof_callback (ff->decoder,
|
|
flac_eof_func);
|
|
FLAC__seekable_stream_decoder_set_write_callback (ff->decoder,
|
|
flac_write_func);
|
|
FLAC__seekable_stream_decoder_set_metadata_callback (ff->decoder,
|
|
flac_meta_func);
|
|
FLAC__seekable_stream_decoder_set_client_data (ff->decoder, ff);
|
|
FLAC__seekable_stream_decoder_set_metadata_respond (ff->decoder,
|
|
FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
|
|
|
FLAC__seekable_stream_decoder_init (ff->decoder);
|
|
FLAC__seekable_stream_decoder_process_until_end_of_metadata (ff->decoder);
|
|
#else
|
|
FLAC__stream_decoder_set_metadata_respond (ff->decoder,
|
|
FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
|
FLAC__stream_decoder_init_stream(ff->decoder,
|
|
flac_read_func, flac_seek_func,
|
|
flac_tell_func, flac_length_func,
|
|
flac_eof_func, flac_write_func,
|
|
flac_meta_func, flac_error_func,
|
|
ff);
|
|
FLAC__stream_decoder_process_until_end_of_metadata (ff->decoder);
|
|
#endif
|
|
return ff;
|
|
}
|
|
|
|
static void
|
|
flac_close (flacfile_t *ff)
|
|
{
|
|
FLAC__stream_decoder_finish (ff->decoder);
|
|
FLAC__stream_decoder_delete (ff->decoder);
|
|
|
|
if (ff->vorbis_info)
|
|
FLAC__metadata_object_delete (ff->vorbis_info);
|
|
|
|
if (ff->buffer)
|
|
free (ff->buffer);
|
|
|
|
Qclose (ff->file);
|
|
|
|
free (ff);
|
|
}
|
|
|
|
static int
|
|
flac_read (flacfile_t *ff, float *buf, int len)
|
|
{
|
|
int count = 0;
|
|
|
|
while (len) {
|
|
int res = 0;
|
|
if (ff->size == ff->pos)
|
|
FLAC__stream_decoder_process_single (ff->decoder);
|
|
res = ff->size - ff->pos;
|
|
if (res > len)
|
|
res = len;
|
|
if (res > 0) {
|
|
memcpy (buf, ff->buffer + ff->pos * ff->info.channels,
|
|
res * ff->info.channels * sizeof (float));
|
|
count += res;
|
|
len -= res;
|
|
buf += res * ff->info.channels;
|
|
ff->pos += res;
|
|
} else if (res < 0) {
|
|
Sys_Printf ("flac error %d\n", res);
|
|
return -1;
|
|
} else {
|
|
Sys_Printf ("unexpected eof\n");
|
|
break;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static sfxbuffer_t *
|
|
flac_load (flacfile_t *ff, sfxblock_t *block)
|
|
{
|
|
float *data;
|
|
sfxbuffer_t *sb = 0;
|
|
sfx_t *sfx = block->sfx;
|
|
wavinfo_t *info = &block->wavinfo;
|
|
|
|
data = malloc (info->datalen);
|
|
if (!data)
|
|
goto bail;
|
|
unsigned buffer_frames = SND_ResamplerFrames (sfx);
|
|
sb = SND_Memory_AllocBuffer (buffer_frames * info->channels);
|
|
if (!sb)
|
|
goto bail;
|
|
sb->size = buffer_frames * info->channels;
|
|
sb->sfx = sfx;
|
|
if (flac_read (ff, data, info->frames) < 0)
|
|
goto bail;
|
|
SND_SetPaint (sb);
|
|
SND_SetupResampler (sb, 0);
|
|
SND_Resample (sb, data, info->frames);
|
|
sb->head = sb->size;
|
|
bail:
|
|
if (data)
|
|
free (data);
|
|
flac_close (ff);
|
|
return sb;
|
|
}
|
|
|
|
static sfxbuffer_t *
|
|
flac_callback_load (sfxblock_t *block)
|
|
{
|
|
QFile *file;
|
|
flacfile_t *ff;
|
|
|
|
file = QFS_FOpenFile (block->file);
|
|
if (!file)
|
|
return 0;
|
|
|
|
if (!(ff = flac_open (file))) {
|
|
Sys_Printf ("Input does not appear to be an Ogg bitstream.\n");
|
|
Qclose (file);
|
|
return 0;
|
|
}
|
|
return flac_load (ff, block);
|
|
}
|
|
|
|
static void
|
|
flac_cache (sfx_t *sfx, char *realname, flacfile_t *ff, wavinfo_t info)
|
|
{
|
|
flac_close (ff);
|
|
SND_SFX_Block (sfx, realname, info, flac_callback_load);
|
|
}
|
|
|
|
static long
|
|
flac_stream_read (void *file, float **buf)
|
|
{
|
|
sfxstream_t *stream = (sfxstream_t *) file;
|
|
flacfile_t *ff = (flacfile_t *) stream->file;
|
|
int res = FLAC__stream_decoder_process_single (ff->decoder);
|
|
|
|
if (!res) {
|
|
stream->error = 1;
|
|
return 0;
|
|
}
|
|
*buf = ff->buffer;
|
|
return ff->size;
|
|
}
|
|
|
|
static int
|
|
flac_stream_seek (sfxstream_t *stream, int pos)
|
|
{
|
|
flacfile_t *ff = stream->file;
|
|
|
|
ff->pos = ff->size = 0;
|
|
return FLAC__stream_decoder_seek_absolute (ff->decoder, pos);
|
|
}
|
|
|
|
static void
|
|
flac_stream_close (sfx_t *sfx)
|
|
{
|
|
sfxstream_t *stream = sfx->data.stream;
|
|
|
|
flac_close (stream->file);
|
|
SND_SFX_StreamClose (sfx);
|
|
}
|
|
|
|
static sfx_t *
|
|
flac_stream_open (sfx_t *sfx)
|
|
{
|
|
sfxstream_t *stream = sfx->data.stream;
|
|
QFile *file;
|
|
void *f;
|
|
|
|
file = QFS_FOpenFile (stream->file);
|
|
if (!file)
|
|
return 0;
|
|
|
|
f = flac_open (file);
|
|
if (!f) {
|
|
Sys_Printf ("Input does not appear to be a flac bitstream.\n");
|
|
Qclose (file);
|
|
return 0;
|
|
}
|
|
|
|
return SND_SFX_StreamOpen (sfx, f, flac_stream_read, flac_stream_seek,
|
|
flac_stream_close);
|
|
}
|
|
|
|
static void
|
|
flac_stream (sfx_t *sfx, char *realname, flacfile_t *ff, wavinfo_t info)
|
|
{
|
|
flac_close (ff);
|
|
SND_SFX_Stream (sfx, realname, info, flac_stream_open);
|
|
}
|
|
|
|
static wavinfo_t
|
|
flac_get_info (flacfile_t *ff)
|
|
{
|
|
int sample_start = -1, sample_count = 0;
|
|
int samples;
|
|
wavinfo_t info;
|
|
FLAC__StreamMetadata_VorbisComment *vc = 0;
|
|
FLAC__StreamMetadata_VorbisComment_Entry *ve;
|
|
FLAC__uint32 i;
|
|
|
|
samples = ff->info.total_samples;
|
|
if (ff->vorbis_info) {
|
|
vc = &ff->vorbis_info->data.vorbis_comment;
|
|
|
|
for (i = 0, ve = vc->comments; i < vc->num_comments; ve++, i++) {
|
|
Sys_MaskPrintf (SYS_dev, "%.*s\n", ve->length, ve->entry);
|
|
if (strncmp ("CUEPOINT=", (char *) ve->entry, 9) == 0) {
|
|
char *str = alloca (ve->length + 1);
|
|
strncpy (str, (char *) ve->entry, ve->length);
|
|
str[ve->length] = 0;
|
|
sscanf (str + 9, "%d %d", &sample_start, &sample_count);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sample_start != -1)
|
|
samples = sample_start + sample_count;
|
|
|
|
info.rate = ff->info.sample_rate;
|
|
info.width = ff->info.bits_per_sample / 8;
|
|
info.channels = ff->info.channels;
|
|
info.loopstart = sample_start;
|
|
info.frames = samples;
|
|
info.dataofs = 0;
|
|
info.datalen = samples * info.channels * sizeof (float);
|
|
|
|
Sys_MaskPrintf (SYS_dev, "\nBitstream is %d channel, %dHz\n",
|
|
info.channels, info.rate);
|
|
Sys_MaskPrintf (SYS_dev, "\nDecoded length: %d samples (%d bytes)\n",
|
|
info.frames, info.width);
|
|
if (vc) {
|
|
Sys_MaskPrintf (SYS_dev, "Encoded by: %.*s\n\n",
|
|
vc->vendor_string.length, vc->vendor_string.entry);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
int
|
|
SND_LoadFLAC (QFile *file, sfx_t *sfx, char *realname)
|
|
{
|
|
flacfile_t *ff;
|
|
wavinfo_t info;
|
|
|
|
if (!(ff = flac_open (file))) {
|
|
Sys_Printf ("Input does not appear to be a FLAC bitstream.\n");
|
|
return -1;
|
|
}
|
|
info = flac_get_info (ff);
|
|
if (info.channels < 1 || info.channels > 8) {
|
|
Sys_Printf ("unsupported number of channels");
|
|
return -1;
|
|
}
|
|
if (info.frames / info.rate < 3) {
|
|
Sys_MaskPrintf (SYS_dev, "cache %s\n", realname);
|
|
flac_cache (sfx, realname, ff, info);
|
|
} else {
|
|
Sys_MaskPrintf (SYS_dev, "stream %s\n", realname);
|
|
flac_stream (sfx, realname, ff, info);
|
|
}
|
|
return 0;
|
|
}
|