quakeforge/libs/audio/renderer/flac.c
Bill Currie 4e550ac9c7 [sound] Move block buffers into locked memory
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().
2022-06-04 23:55:31 +09:00

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;
}