mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-27 06:34:11 +00:00
3efb0c538f
QFS_LoadFile (and its wrappers) now take a file handle rather than a path. This will make vpath usage a little cleaner to implement.
470 lines
12 KiB
C
470 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, cache_allocator_t allocator)
|
|
{
|
|
float *data;
|
|
sfxbuffer_t *sc = 0;
|
|
sfx_t *sfx = block->sfx;
|
|
wavinfo_t *info = &block->wavinfo;
|
|
|
|
data = malloc (info->datalen);
|
|
if (!data)
|
|
goto bail;
|
|
sc = SND_GetCache (info->frames, info->rate, info->channels,
|
|
block, allocator);
|
|
if (!sc)
|
|
goto bail;
|
|
sc->sfx = sfx;
|
|
if (flac_read (ff, data, info->frames) < 0)
|
|
goto bail;
|
|
SND_SetPaint (sc);
|
|
SND_SetupResampler (sc, 0);
|
|
SND_Resample (sc, data, info->frames);
|
|
sc->head = sc->length;
|
|
bail:
|
|
if (data)
|
|
free (data);
|
|
flac_close (ff);
|
|
return sc;
|
|
}
|
|
|
|
static void
|
|
flac_callback_load (void *object, cache_allocator_t allocator)
|
|
{
|
|
QFile *file;
|
|
flacfile_t *ff;
|
|
|
|
sfxblock_t *block = (sfxblock_t *) object;
|
|
|
|
file = QFS_FOpenFile (block->file);
|
|
if (!file)
|
|
return; //FIXME Sys_Error?
|
|
|
|
if (!(ff = flac_open (file))) {
|
|
Sys_Printf ("Input does not appear to be an Ogg bitstream.\n");
|
|
Qclose (file);
|
|
return; //FIXME Sys_Error?
|
|
}
|
|
flac_load (ff, block, allocator);
|
|
}
|
|
|
|
static void
|
|
flac_cache (sfx_t *sfx, char *realname, flacfile_t *ff, wavinfo_t info)
|
|
{
|
|
flac_close (ff);
|
|
SND_SFX_Cache (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;
|
|
}
|