520 lines
13 KiB
C
520 lines
13 KiB
C
/*
|
|
* Copyright 2008-2013 Various Authors
|
|
* Copyright 2005 Timo Hirvonen
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "../ip.h"
|
|
#include "../comment.h"
|
|
#include "../xmalloc.h"
|
|
#include "../debug.h"
|
|
#include "../utils.h"
|
|
|
|
#include <FLAC/export.h>
|
|
#include <FLAC/stream_decoder.h>
|
|
#include <FLAC/metadata.h>
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#ifndef UINT64_MAX
|
|
#define UINT64_MAX ((uint64_t)-1)
|
|
#endif
|
|
|
|
/* Reduce typing. Namespaces are nice but FLAC API is fscking ridiculous. */
|
|
|
|
/* functions, types, enums */
|
|
#define F(s) FLAC__stream_decoder_ ## s
|
|
#define T(s) FLAC__StreamDecoder ## s
|
|
#define Dec FLAC__StreamDecoder
|
|
#define E(s) FLAC__STREAM_DECODER_ ## s
|
|
|
|
#define FLAC_MAX_CHANNELS 8
|
|
|
|
struct flac_private {
|
|
/* file/stream position and length */
|
|
uint64_t pos;
|
|
uint64_t len;
|
|
|
|
Dec *dec;
|
|
|
|
/* PCM data */
|
|
char *buf;
|
|
unsigned int buf_size;
|
|
unsigned int buf_wpos;
|
|
unsigned int buf_rpos;
|
|
|
|
struct keyval *comments;
|
|
double duration;
|
|
long bitrate;
|
|
int bps;
|
|
};
|
|
|
|
static T(ReadStatus) read_cb(const Dec *dec, unsigned char *buf, size_t *size, void *data)
|
|
{
|
|
struct input_plugin_data *ip_data = data;
|
|
struct flac_private *priv = ip_data->private;
|
|
int rc;
|
|
|
|
if (priv->pos == priv->len) {
|
|
*size = 0;
|
|
return E(READ_STATUS_END_OF_STREAM);
|
|
}
|
|
if (*size == 0)
|
|
return E(READ_STATUS_CONTINUE);
|
|
|
|
rc = read(ip_data->fd, buf, *size);
|
|
if (rc == -1) {
|
|
*size = 0;
|
|
if (errno == EINTR || errno == EAGAIN) {
|
|
/* FIXME: not sure how the flac decoder handles this */
|
|
d_print("interrupted\n");
|
|
return E(READ_STATUS_CONTINUE);
|
|
}
|
|
return E(READ_STATUS_ABORT);
|
|
}
|
|
|
|
priv->pos += rc;
|
|
*size = rc;
|
|
if (rc == 0) {
|
|
/* should not happen */
|
|
return E(READ_STATUS_END_OF_STREAM);
|
|
}
|
|
return E(READ_STATUS_CONTINUE);
|
|
}
|
|
|
|
static T(SeekStatus) seek_cb(const Dec *dec, uint64_t offset, void *data)
|
|
{
|
|
struct input_plugin_data *ip_data = data;
|
|
struct flac_private *priv = ip_data->private;
|
|
off_t off;
|
|
|
|
if (priv->len == UINT64_MAX)
|
|
return E(SEEK_STATUS_ERROR);
|
|
off = lseek(ip_data->fd, offset, SEEK_SET);
|
|
if (off == -1) {
|
|
return E(SEEK_STATUS_ERROR);
|
|
}
|
|
priv->pos = off;
|
|
return E(SEEK_STATUS_OK);
|
|
}
|
|
|
|
static T(TellStatus) tell_cb(const Dec *dec, uint64_t *offset, void *data)
|
|
{
|
|
struct input_plugin_data *ip_data = data;
|
|
struct flac_private *priv = ip_data->private;
|
|
|
|
*offset = priv->pos;
|
|
return E(TELL_STATUS_OK);
|
|
}
|
|
|
|
static T(LengthStatus) length_cb(const Dec *dec, uint64_t *len, void *data)
|
|
{
|
|
struct input_plugin_data *ip_data = data;
|
|
struct flac_private *priv = ip_data->private;
|
|
|
|
if (ip_data->remote) {
|
|
return E(LENGTH_STATUS_ERROR);
|
|
}
|
|
*len = priv->len;
|
|
return E(LENGTH_STATUS_OK);
|
|
}
|
|
|
|
static int eof_cb(const Dec *dec, void *data)
|
|
{
|
|
struct input_plugin_data *ip_data = data;
|
|
struct flac_private *priv = ip_data->private;
|
|
|
|
return priv->pos == priv->len;;
|
|
}
|
|
|
|
#if defined(WORDS_BIGENDIAN)
|
|
|
|
#define LE32(x) swap_uint32(x)
|
|
|
|
#else
|
|
|
|
#define LE32(x) (x)
|
|
|
|
#endif
|
|
|
|
static FLAC__StreamDecoderWriteStatus write_cb(const Dec *dec, const FLAC__Frame *frame,
|
|
const int32_t * const *buf, void *data)
|
|
{
|
|
struct input_plugin_data *ip_data = data;
|
|
struct flac_private *priv = ip_data->private;
|
|
int frames, bytes, size, channels, bits, depth;
|
|
int ch, nch, i = 0;
|
|
char *dest; int32_t src;
|
|
|
|
frames = frame->header.blocksize;
|
|
channels = sf_get_channels(ip_data->sf);
|
|
bits = sf_get_bits(ip_data->sf);
|
|
bytes = frames * bits / 8 * channels;
|
|
size = priv->buf_size;
|
|
|
|
if (size - priv->buf_wpos < bytes) {
|
|
if (size < bytes)
|
|
size = bytes;
|
|
size *= 2;
|
|
priv->buf = xrenew(char, priv->buf, size);
|
|
priv->buf_size = size;
|
|
}
|
|
|
|
depth = frame->header.bits_per_sample;
|
|
if (!depth)
|
|
depth = priv->bps;
|
|
nch = frame->header.channels;
|
|
dest = priv->buf + priv->buf_wpos;
|
|
for (i = 0; i < frames; i++) {
|
|
for (ch = 0; ch < channels; ch++) {
|
|
src = LE32(buf[ch % nch][i] << (bits - depth));
|
|
memcpy(dest, &src, bits / 8);
|
|
dest += bits / 8;
|
|
}
|
|
}
|
|
|
|
priv->buf_wpos += bytes;
|
|
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
|
}
|
|
|
|
/* You should make a copy of metadata with FLAC__metadata_object_clone() if you will
|
|
* need it elsewhere. Since metadata blocks can potentially be large, by
|
|
* default the decoder only calls the metadata callback for the STREAMINFO
|
|
* block; you can instruct the decoder to pass or filter other blocks with
|
|
* FLAC__stream_decoder_set_metadata_*() calls.
|
|
*/
|
|
static void metadata_cb(const Dec *dec, const FLAC__StreamMetadata *metadata, void *data)
|
|
{
|
|
struct input_plugin_data *ip_data = data;
|
|
struct flac_private *priv = ip_data->private;
|
|
|
|
switch (metadata->type) {
|
|
case FLAC__METADATA_TYPE_STREAMINFO:
|
|
{
|
|
const FLAC__StreamMetadata_StreamInfo *si = &metadata->data.stream_info;
|
|
int bits = 0;
|
|
|
|
if (si->bits_per_sample >= 4 && si->bits_per_sample <= 32) {
|
|
bits = priv->bps = si->bits_per_sample;
|
|
bits = 8 * ((bits + 7) / 8);
|
|
}
|
|
|
|
ip_data->sf = sf_rate(si->sample_rate) |
|
|
sf_bits(bits) |
|
|
sf_signed(1) |
|
|
sf_channels(si->channels);
|
|
if (!ip_data->remote && si->total_samples) {
|
|
priv->duration = (double) si->total_samples / si->sample_rate;
|
|
if (priv->duration >= 1 && priv->len >= 1)
|
|
priv->bitrate = priv->len * 8 / priv->duration;
|
|
}
|
|
}
|
|
break;
|
|
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
|
if (priv->comments) {
|
|
d_print("Ignoring VORBISCOMMENT\n");
|
|
} else {
|
|
GROWING_KEYVALS(c);
|
|
int i, nr;
|
|
|
|
nr = metadata->data.vorbis_comment.num_comments;
|
|
for (i = 0; i < nr; i++) {
|
|
const char *str = (const char *)metadata->data.vorbis_comment.comments[i].entry;
|
|
char *key, *val;
|
|
|
|
val = strchr(str, '=');
|
|
if (!val)
|
|
continue;
|
|
key = xstrndup(str, val - str);
|
|
val = xstrdup(val + 1);
|
|
comments_add(&c, key, val);
|
|
free(key);
|
|
}
|
|
keyvals_terminate(&c);
|
|
priv->comments = c.keyvals;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void error_cb(const Dec *dec, FLAC__StreamDecoderErrorStatus status, void *data)
|
|
{
|
|
d_print("FLAC error: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
|
|
}
|
|
|
|
static void free_priv(struct input_plugin_data *ip_data)
|
|
{
|
|
struct flac_private *priv = ip_data->private;
|
|
int save = errno;
|
|
|
|
F(finish)(priv->dec);
|
|
F(delete)(priv->dec);
|
|
if (priv->comments)
|
|
keyvals_free(priv->comments);
|
|
free(priv->buf);
|
|
free(priv);
|
|
ip_data->private = NULL;
|
|
errno = save;
|
|
}
|
|
|
|
/* http://flac.sourceforge.net/format.html#frame_header */
|
|
static void channel_map_init_flac(int channels, channel_position_t *map)
|
|
{
|
|
if (channels == 1) {
|
|
map[0] = CHANNEL_POSITION_MONO;
|
|
} else if (channels == 2) {
|
|
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
|
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
|
} else if (channels == 3) {
|
|
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
|
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
|
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
|
} else if (channels == 4) {
|
|
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
|
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
|
map[2] = CHANNEL_POSITION_REAR_LEFT;
|
|
map[3] = CHANNEL_POSITION_REAR_RIGHT;
|
|
} else if (channels == 5) {
|
|
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
|
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
|
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
|
map[3] = CHANNEL_POSITION_REAR_LEFT;
|
|
map[4] = CHANNEL_POSITION_REAR_RIGHT;
|
|
} else if (channels == 6) {
|
|
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
|
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
|
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
|
map[3] = CHANNEL_POSITION_LFE;
|
|
map[4] = CHANNEL_POSITION_REAR_LEFT;
|
|
map[5] = CHANNEL_POSITION_REAR_RIGHT;
|
|
} else if (channels == 7) {
|
|
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
|
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
|
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
|
map[3] = CHANNEL_POSITION_LFE;
|
|
map[4] = CHANNEL_POSITION_REAR_LEFT;
|
|
map[5] = CHANNEL_POSITION_REAR_RIGHT;
|
|
map[6] = CHANNEL_POSITION_REAR_CENTER;
|
|
} else if (channels >= 8) {
|
|
if (channels > 8) {
|
|
d_print("Flac file with %d channels?!", channels);
|
|
}
|
|
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
|
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
|
map[2] = CHANNEL_POSITION_FRONT_CENTER;
|
|
map[3] = CHANNEL_POSITION_LFE;
|
|
map[4] = CHANNEL_POSITION_REAR_LEFT;
|
|
map[5] = CHANNEL_POSITION_REAR_RIGHT;
|
|
map[6] = CHANNEL_POSITION_SIDE_LEFT;
|
|
map[7] = CHANNEL_POSITION_SIDE_RIGHT;
|
|
}
|
|
}
|
|
|
|
static int flac_open(struct input_plugin_data *ip_data)
|
|
{
|
|
struct flac_private *priv;
|
|
|
|
Dec *dec = F(new)();
|
|
|
|
const struct flac_private priv_init = {
|
|
.dec = dec,
|
|
.duration = -1,
|
|
.bitrate = -1,
|
|
.bps = 0
|
|
};
|
|
|
|
if (!dec)
|
|
return -IP_ERROR_INTERNAL;
|
|
|
|
priv = xnew(struct flac_private, 1);
|
|
*priv = priv_init;
|
|
if (ip_data->remote) {
|
|
priv->len = UINT64_MAX;
|
|
} else {
|
|
off_t off = lseek(ip_data->fd, 0, SEEK_END);
|
|
|
|
if (off == -1 || lseek(ip_data->fd, 0, SEEK_SET) == -1) {
|
|
int save = errno;
|
|
|
|
F(delete)(dec);
|
|
free(priv);
|
|
errno = save;
|
|
return -IP_ERROR_ERRNO;
|
|
}
|
|
priv->len = off;
|
|
}
|
|
ip_data->private = priv;
|
|
|
|
FLAC__stream_decoder_set_metadata_respond_all(dec);
|
|
if (FLAC__stream_decoder_init_stream(dec, read_cb, seek_cb, tell_cb,
|
|
length_cb, eof_cb, write_cb, metadata_cb,
|
|
error_cb, ip_data) != E(INIT_STATUS_OK)) {
|
|
int save = errno;
|
|
|
|
d_print("init failed\n");
|
|
F(delete)(priv->dec);
|
|
free(priv);
|
|
ip_data->private = NULL;
|
|
errno = save;
|
|
return -IP_ERROR_ERRNO;
|
|
}
|
|
|
|
ip_data->sf = 0;
|
|
if (!F(process_until_end_of_metadata)(priv->dec)) {
|
|
free_priv(ip_data);
|
|
return -IP_ERROR_ERRNO;
|
|
}
|
|
|
|
if (!ip_data->sf) {
|
|
free_priv(ip_data);
|
|
return -IP_ERROR_FILE_FORMAT;
|
|
}
|
|
int bits = sf_get_bits(ip_data->sf);
|
|
if (!bits) {
|
|
free_priv(ip_data);
|
|
return -IP_ERROR_SAMPLE_FORMAT;
|
|
}
|
|
|
|
int channels = sf_get_channels(ip_data->sf);
|
|
if (channels > 8) {
|
|
free_priv(ip_data);
|
|
return -IP_ERROR_FILE_FORMAT;
|
|
}
|
|
|
|
channel_map_init_flac(sf_get_channels(ip_data->sf), ip_data->channel_map);
|
|
d_print("sr: %d, ch: %d, bits: %d\n",
|
|
sf_get_rate(ip_data->sf),
|
|
channels,
|
|
bits);
|
|
return 0;
|
|
}
|
|
|
|
static int flac_close(struct input_plugin_data *ip_data)
|
|
{
|
|
free_priv(ip_data);
|
|
return 0;
|
|
}
|
|
|
|
static int flac_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
|
{
|
|
struct flac_private *priv = ip_data->private;
|
|
int avail;
|
|
|
|
while (1) {
|
|
avail = priv->buf_wpos - priv->buf_rpos;
|
|
BUG_ON(avail < 0);
|
|
if (avail > 0)
|
|
break;
|
|
FLAC__bool internal_error = !F(process_single)(priv->dec);
|
|
FLAC__StreamDecoderState state = F(get_state)(priv->dec);
|
|
if (state == E(END_OF_STREAM))
|
|
return 0;
|
|
if (state == E(ABORTED) || state == E(OGG_ERROR) || internal_error) {
|
|
d_print("process_single failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
if (count > avail)
|
|
count = avail;
|
|
memcpy(buffer, priv->buf + priv->buf_rpos, count);
|
|
priv->buf_rpos += count;
|
|
BUG_ON(priv->buf_rpos > priv->buf_wpos);
|
|
if (priv->buf_rpos == priv->buf_wpos) {
|
|
priv->buf_rpos = 0;
|
|
priv->buf_wpos = 0;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* Flush the input and seek to an absolute sample. Decoding will resume at the
|
|
* given sample.
|
|
*/
|
|
static int flac_seek(struct input_plugin_data *ip_data, double offset)
|
|
{
|
|
struct flac_private *priv = ip_data->private;
|
|
priv->buf_rpos = 0;
|
|
priv->buf_wpos = 0;
|
|
uint64_t sample;
|
|
|
|
sample = (uint64_t)(offset * (double)sf_get_rate(ip_data->sf) + 0.5);
|
|
if (!F(seek_absolute)(priv->dec, sample)) {
|
|
if (F(get_state(priv->dec)) == FLAC__STREAM_DECODER_SEEK_ERROR) {
|
|
if (!F(flush)(priv->dec))
|
|
d_print("failed to flush\n");
|
|
}
|
|
return -IP_ERROR_ERRNO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int flac_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
|
{
|
|
struct flac_private *priv = ip_data->private;
|
|
|
|
if (priv->comments) {
|
|
*comments = keyvals_dup(priv->comments);
|
|
} else {
|
|
*comments = keyvals_new(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int flac_duration(struct input_plugin_data *ip_data)
|
|
{
|
|
struct flac_private *priv = ip_data->private;
|
|
|
|
return priv->duration;
|
|
}
|
|
|
|
static long flac_bitrate(struct input_plugin_data *ip_data)
|
|
{
|
|
struct flac_private *priv = ip_data->private;
|
|
return priv->bitrate;
|
|
}
|
|
|
|
static char *flac_codec(struct input_plugin_data *ip_data)
|
|
{
|
|
return xstrdup("flac");
|
|
}
|
|
|
|
static char *flac_codec_profile(struct input_plugin_data *ip_data)
|
|
{
|
|
/* maybe identify compression-level over min/max blocksize/framesize */
|
|
return NULL;
|
|
}
|
|
|
|
const struct input_plugin_ops ip_ops = {
|
|
.open = flac_open,
|
|
.close = flac_close,
|
|
.read = flac_read,
|
|
.seek = flac_seek,
|
|
.read_comments = flac_read_comments,
|
|
.duration = flac_duration,
|
|
.bitrate = flac_bitrate,
|
|
.bitrate_current = flac_bitrate,
|
|
.codec = flac_codec,
|
|
.codec_profile = flac_codec_profile
|
|
};
|
|
|
|
const int ip_priority = 50;
|
|
const char * const ip_extensions[] = { "flac", "fla", NULL };
|
|
const char * const ip_mime_types[] = { NULL };
|
|
const struct input_plugin_opt ip_options[] = { { NULL } };
|
|
const unsigned ip_abi_version = IP_ABI_VERSION;
|