push
This commit is contained in:
541
ip/aac.c
Normal file
541
ip/aac.c
Normal file
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 dnk <dnk@bjum.net>
|
||||
*
|
||||
* 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 "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../id3.h"
|
||||
#include "../comment.h"
|
||||
#include "../read_wrapper.h"
|
||||
#include "aac.h"
|
||||
|
||||
#include <neaacdec.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* FAAD_MIN_STREAMSIZE == 768, 6 == # of channels */
|
||||
#define BUFFER_SIZE (FAAD_MIN_STREAMSIZE * 6 * 4)
|
||||
|
||||
struct aac_private {
|
||||
char rbuf[BUFFER_SIZE];
|
||||
int rbuf_len;
|
||||
int rbuf_pos;
|
||||
|
||||
unsigned char channels;
|
||||
unsigned long sample_rate;
|
||||
long bitrate;
|
||||
int object_type;
|
||||
|
||||
struct {
|
||||
unsigned long samples;
|
||||
unsigned long bytes;
|
||||
} current;
|
||||
|
||||
char *overflow_buf;
|
||||
int overflow_buf_len;
|
||||
|
||||
NeAACDecHandle decoder; /* typedef void * */
|
||||
};
|
||||
|
||||
static inline int buffer_length(const struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
|
||||
return priv->rbuf_len - priv->rbuf_pos;
|
||||
}
|
||||
|
||||
static inline void *buffer_data(const struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
|
||||
return priv->rbuf + priv->rbuf_pos;
|
||||
}
|
||||
|
||||
static int buffer_fill(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
int32_t n;
|
||||
|
||||
if (priv->rbuf_pos > 0) {
|
||||
priv->rbuf_len = buffer_length(ip_data);
|
||||
memmove(priv->rbuf, priv->rbuf + priv->rbuf_pos, priv->rbuf_len);
|
||||
priv->rbuf_pos = 0;
|
||||
}
|
||||
|
||||
if (priv->rbuf_len == BUFFER_SIZE)
|
||||
return 1;
|
||||
|
||||
n = read_wrapper(ip_data, priv->rbuf + priv->rbuf_len, BUFFER_SIZE - priv->rbuf_len);
|
||||
if (n == -1)
|
||||
return -1;
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
priv->rbuf_len += n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline void buffer_consume(struct input_plugin_data *ip_data, int n)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
|
||||
BUG_ON(n > buffer_length(ip_data));
|
||||
|
||||
priv->rbuf_pos += n;
|
||||
}
|
||||
|
||||
static int buffer_fill_min(struct input_plugin_data *ip_data, int len)
|
||||
{
|
||||
int rc;
|
||||
|
||||
BUG_ON(len > BUFFER_SIZE);
|
||||
|
||||
while (buffer_length(ip_data) < len) {
|
||||
rc = buffer_fill(ip_data);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 'data' must point to at least 6 bytes of data */
|
||||
static inline int parse_frame(const unsigned char data[6])
|
||||
{
|
||||
int len;
|
||||
|
||||
/* http://www.audiocoding.com/modules/wiki/?page=ADTS */
|
||||
|
||||
/* first 12 bits must be set */
|
||||
if (data[0] != 0xFF)
|
||||
return 0;
|
||||
if ((data[1] & 0xF0) != 0xF0)
|
||||
return 0;
|
||||
|
||||
/* layer is always '00' */
|
||||
if ((data[1] & 0x06) != 0x00)
|
||||
return 0;
|
||||
|
||||
/* frame length is stored in 13 bits */
|
||||
len = data[3] << 11; /* ..1100000000000 */
|
||||
len |= data[4] << 3; /* ..xx11111111xxx */
|
||||
len |= data[5] >> 5; /* ..xxxxxxxxxx111 */
|
||||
len &= 0x1FFF; /* 13 bits */
|
||||
return len;
|
||||
}
|
||||
|
||||
/* scans forward to the next aac frame and makes sure
|
||||
* the entire frame is in the buffer.
|
||||
*/
|
||||
static int buffer_fill_frame(struct input_plugin_data *ip_data)
|
||||
{
|
||||
unsigned char *data;
|
||||
int rc, n, len;
|
||||
int max = 32768;
|
||||
|
||||
while (1) {
|
||||
/* need at least 6 bytes of data */
|
||||
rc = buffer_fill_min(ip_data, 6);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
len = buffer_length(ip_data);
|
||||
data = buffer_data(ip_data);
|
||||
|
||||
/* scan for a frame */
|
||||
for (n = 0; n < len - 5; n++) {
|
||||
/* give up after 32KB */
|
||||
if (max-- == 0) {
|
||||
d_print("no frame found!\n");
|
||||
/* FIXME: set errno? */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* see if there's a frame at this location */
|
||||
rc = parse_frame(data + n);
|
||||
if (rc == 0)
|
||||
continue;
|
||||
|
||||
/* found a frame, consume all data up to the frame */
|
||||
buffer_consume(ip_data, n);
|
||||
|
||||
/* rc == frame length */
|
||||
rc = buffer_fill_min(ip_data, rc);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* consume what we used */
|
||||
buffer_consume(ip_data, n);
|
||||
}
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
static void aac_get_channel_map(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
void *buf;
|
||||
int i;
|
||||
|
||||
ip_data->channel_map[0] = CHANNEL_POSITION_INVALID;
|
||||
|
||||
if (buffer_fill_frame(ip_data) <= 0)
|
||||
return;
|
||||
|
||||
buf = NeAACDecDecode(priv->decoder, &frame_info, buffer_data(ip_data), buffer_length(ip_data));
|
||||
NeAACDecPostSeekReset(priv->decoder, 0);
|
||||
if (!buf || frame_info.error != 0 || frame_info.bytesconsumed <= 0
|
||||
|| frame_info.channels > CHANNELS_MAX)
|
||||
return;
|
||||
|
||||
for (i = 0; i < frame_info.channels; i++)
|
||||
ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]);
|
||||
}
|
||||
|
||||
static int aac_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv;
|
||||
NeAACDecConfigurationPtr neaac_cfg;
|
||||
int ret, n;
|
||||
|
||||
/* init private struct */
|
||||
const struct aac_private priv_init = {
|
||||
.decoder = NeAACDecOpen(),
|
||||
.bitrate = -1,
|
||||
.object_type = -1
|
||||
};
|
||||
priv = xnew(struct aac_private, 1);
|
||||
*priv = priv_init;
|
||||
ip_data->private = priv;
|
||||
|
||||
/* set decoder config */
|
||||
neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder);
|
||||
neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
|
||||
neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */
|
||||
neaac_cfg->dontUpSampleImplicitSBR = 0; /* upsample, please! */
|
||||
NeAACDecSetConfiguration(priv->decoder, neaac_cfg);
|
||||
|
||||
/* find a frame */
|
||||
if (buffer_fill_frame(ip_data) <= 0) {
|
||||
ret = -IP_ERROR_FILE_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* in case of a bug, make sure there is at least some data
|
||||
* in the buffer for NeAACDecInit() to work with.
|
||||
*/
|
||||
if (buffer_fill_min(ip_data, 256) <= 0) {
|
||||
d_print("not enough data\n");
|
||||
ret = -IP_ERROR_FILE_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* init decoder, returns the length of the header (if any) */
|
||||
n = NeAACDecInit(priv->decoder, buffer_data(ip_data), buffer_length(ip_data),
|
||||
&priv->sample_rate, &priv->channels);
|
||||
if (n < 0) {
|
||||
d_print("NeAACDecInit failed\n");
|
||||
ret = -IP_ERROR_FILE_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
d_print("sample rate %luhz, channels %u\n", priv->sample_rate, priv->channels);
|
||||
if (!priv->sample_rate || !priv->channels) {
|
||||
ret = -IP_ERROR_FILE_FORMAT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* skip the header */
|
||||
d_print("skipping header (%d bytes)\n", n);
|
||||
|
||||
buffer_consume(ip_data, n);
|
||||
|
||||
/*NeAACDecInitDRM(priv->decoder, priv->sample_rate, priv->channels);*/
|
||||
|
||||
ip_data->sf = sf_rate(priv->sample_rate) | sf_channels(priv->channels) | sf_bits(16) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
aac_get_channel_map(ip_data);
|
||||
|
||||
return 0;
|
||||
out:
|
||||
NeAACDecClose(priv->decoder);
|
||||
free(priv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int aac_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
|
||||
NeAACDecClose(priv->decoder);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns -1 on fatal errors
|
||||
* returns -2 on non-fatal errors
|
||||
* 0 on eof
|
||||
* number of bytes put in 'buffer' on success */
|
||||
static int decode_one_frame(struct input_plugin_data *ip_data, void *buffer, int count)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
unsigned char *aac_data;
|
||||
unsigned int aac_data_size;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
char *sample_buf;
|
||||
int bytes, rc;
|
||||
|
||||
rc = buffer_fill_frame(ip_data);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
aac_data = buffer_data(ip_data);
|
||||
aac_data_size = buffer_length(ip_data);
|
||||
|
||||
/* aac data -> raw pcm */
|
||||
sample_buf = NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_size);
|
||||
if (frame_info.error == 0 && frame_info.samples > 0) {
|
||||
priv->current.samples += frame_info.samples;
|
||||
priv->current.bytes += frame_info.bytesconsumed;
|
||||
}
|
||||
|
||||
buffer_consume(ip_data, frame_info.bytesconsumed);
|
||||
|
||||
if (!sample_buf || frame_info.bytesconsumed <= 0) {
|
||||
d_print("fatal error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (frame_info.error != 0) {
|
||||
d_print("frame error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (frame_info.samples <= 0)
|
||||
return -2;
|
||||
|
||||
if (frame_info.channels != priv->channels || frame_info.samplerate != priv->sample_rate) {
|
||||
d_print("invalid channel or sample_rate count\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* 16-bit samples */
|
||||
bytes = frame_info.samples * 2;
|
||||
|
||||
if (bytes > count) {
|
||||
/* decoded too much, keep overflow */
|
||||
priv->overflow_buf = sample_buf + count;
|
||||
priv->overflow_buf_len = bytes - count;
|
||||
memcpy(buffer, sample_buf, count);
|
||||
return count;
|
||||
} else {
|
||||
memcpy(buffer, sample_buf, bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int aac_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
int rc;
|
||||
|
||||
/* use overflow from previous call (if any) */
|
||||
if (priv->overflow_buf_len) {
|
||||
int len = priv->overflow_buf_len;
|
||||
|
||||
if (len > count)
|
||||
len = count;
|
||||
|
||||
memcpy(buffer, priv->overflow_buf, len);
|
||||
priv->overflow_buf += len;
|
||||
priv->overflow_buf_len -= len;
|
||||
return len;
|
||||
}
|
||||
|
||||
do {
|
||||
rc = decode_one_frame(ip_data, buffer, count);
|
||||
} while (rc == -2);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int aac_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static int aac_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
GROWING_KEYVALS(c);
|
||||
struct id3tag id3;
|
||||
int rc, fd, i;
|
||||
|
||||
fd = open(ip_data->filename, O_RDONLY);
|
||||
if (fd == -1)
|
||||
return -1;
|
||||
|
||||
id3_init(&id3);
|
||||
rc = id3_read_tags(&id3, fd, ID3_V1 | ID3_V2);
|
||||
if (rc == -1) {
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_ID3_KEYS; i++) {
|
||||
char *val = id3_get_comment(&id3, i);
|
||||
|
||||
if (val)
|
||||
comments_add(&c, id3_key_names[i], val);
|
||||
}
|
||||
out:
|
||||
close(fd);
|
||||
id3_free(&id3);
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aac_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
int samples = 0, bytes = 0, frames = 0;
|
||||
off_t file_size;
|
||||
|
||||
file_size = lseek(ip_data->fd, 0, SEEK_END);
|
||||
if (file_size == -1)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
/* Seek to the middle of the file. There is almost always silence at
|
||||
* the beginning, which gives wrong results. */
|
||||
if (lseek(ip_data->fd, file_size/2, SEEK_SET) == -1)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
priv->rbuf_pos = 0;
|
||||
priv->rbuf_len = 0;
|
||||
|
||||
/* guess track length by decoding the first 10 frames */
|
||||
while (frames < 10) {
|
||||
if (buffer_fill_frame(ip_data) <= 0)
|
||||
break;
|
||||
|
||||
NeAACDecDecode(priv->decoder, &frame_info,
|
||||
buffer_data(ip_data), buffer_length(ip_data));
|
||||
if (frame_info.error == 0 && frame_info.samples > 0) {
|
||||
samples += frame_info.samples;
|
||||
bytes += frame_info.bytesconsumed;
|
||||
frames++;
|
||||
}
|
||||
if (frame_info.bytesconsumed == 0)
|
||||
break;
|
||||
|
||||
buffer_consume(ip_data, frame_info.bytesconsumed);
|
||||
}
|
||||
|
||||
if (frames == 0)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
NeAACDecPostSeekReset(priv->decoder, 0);
|
||||
|
||||
samples /= frames;
|
||||
samples /= priv->channels;
|
||||
bytes /= frames;
|
||||
|
||||
/* 8 * file_size / duration */
|
||||
priv->bitrate = (8 * bytes * priv->sample_rate) / samples;
|
||||
|
||||
priv->object_type = frame_info.object_type;
|
||||
|
||||
return ((file_size / bytes) * samples) / priv->sample_rate;
|
||||
}
|
||||
|
||||
static long aac_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
return priv->bitrate != -1 ? priv->bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long aac_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
long bitrate = -1;
|
||||
if (priv->current.samples > 0) {
|
||||
priv->current.samples /= priv->channels;
|
||||
bitrate = (8 * priv->current.bytes * priv->sample_rate) / priv->current.samples;
|
||||
priv->current.samples = 0;
|
||||
priv->current.bytes = 0;
|
||||
}
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static char *aac_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("aac");
|
||||
}
|
||||
|
||||
static const char *object_type_to_str(int object_type)
|
||||
{
|
||||
switch (object_type) {
|
||||
case MAIN: return "Main";
|
||||
case LC: return "LC";
|
||||
case SSR: return "SSR";
|
||||
case LTP: return "LTP";
|
||||
case HE_AAC: return "HE";
|
||||
case ER_LC: return "ER-LD";
|
||||
case ER_LTP: return "ER-LTP";
|
||||
case LD: return "LD";
|
||||
case DRM_ER_LC: return "DRM-ER-LC";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *aac_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct aac_private *priv = ip_data->private;
|
||||
const char *profile = object_type_to_str(priv->object_type);
|
||||
|
||||
return profile ? xstrdup(profile) : NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = aac_open,
|
||||
.close = aac_close,
|
||||
.read = aac_read,
|
||||
.seek = aac_seek,
|
||||
.read_comments = aac_read_comments,
|
||||
.duration = aac_duration,
|
||||
.bitrate = aac_bitrate,
|
||||
.bitrate_current = aac_current_bitrate,
|
||||
.codec = aac_codec,
|
||||
.codec_profile = aac_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "aac", NULL };
|
||||
const char * const ip_mime_types[] = { "audio/aac", "audio/aacp", NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
41
ip/aac.h
Normal file
41
ip/aac.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Various Authors
|
||||
* Copyright 2011 Johannes Weißl
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_AAC_H
|
||||
#define CMUS_AAC_H
|
||||
|
||||
#include "../channelmap.h"
|
||||
#include <neaacdec.h>
|
||||
|
||||
static inline channel_position_t channel_position_aac(unsigned char c)
|
||||
{
|
||||
switch (c) {
|
||||
case FRONT_CHANNEL_CENTER: return CHANNEL_POSITION_FRONT_CENTER;
|
||||
case FRONT_CHANNEL_LEFT: return CHANNEL_POSITION_FRONT_LEFT;
|
||||
case FRONT_CHANNEL_RIGHT: return CHANNEL_POSITION_FRONT_RIGHT;
|
||||
case SIDE_CHANNEL_LEFT: return CHANNEL_POSITION_SIDE_LEFT;
|
||||
case SIDE_CHANNEL_RIGHT: return CHANNEL_POSITION_SIDE_RIGHT;
|
||||
case BACK_CHANNEL_LEFT: return CHANNEL_POSITION_REAR_LEFT;
|
||||
case BACK_CHANNEL_RIGHT: return CHANNEL_POSITION_REAR_RIGHT;
|
||||
case BACK_CHANNEL_CENTER: return CHANNEL_POSITION_REAR_CENTER;
|
||||
case LFE_CHANNEL: return CHANNEL_POSITION_LFE;
|
||||
default: return CHANNEL_POSITION_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
213
ip/bass.c
Normal file
213
ip/bass.c
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2016 Nic Soudée
|
||||
*
|
||||
* 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 "../xmalloc.h"
|
||||
#include "../comment.h"
|
||||
#include "../bass.h"
|
||||
#include "../uchar.h"
|
||||
|
||||
#define BITS (16)
|
||||
#define FREQ (44100)
|
||||
#define CHANS (2)
|
||||
|
||||
struct bass_private {
|
||||
DWORD chan;
|
||||
};
|
||||
|
||||
static int bass_init(void)
|
||||
{
|
||||
static int inited = 0;
|
||||
|
||||
if (inited)
|
||||
return 1;
|
||||
|
||||
if (!BASS_Init(0, FREQ, 0, 0, NULL))
|
||||
return 0;
|
||||
|
||||
inited = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int bass_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct bass_private *priv;
|
||||
DWORD chan;
|
||||
DWORD flags;
|
||||
|
||||
if (!bass_init())
|
||||
return -IP_ERROR_INTERNAL;
|
||||
|
||||
flags = BASS_MUSIC_DECODE;
|
||||
flags |= BASS_MUSIC_RAMP;
|
||||
flags |= BASS_MUSIC_PRESCAN;
|
||||
flags |= BASS_MUSIC_STOPBACK;
|
||||
|
||||
chan = BASS_MusicLoad(FALSE, ip_data->filename, 0, 0, flags, 0);
|
||||
|
||||
if (!chan) {
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
|
||||
priv = xnew(struct bass_private, 1);
|
||||
priv->chan = chan;
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = sf_bits(BITS) | sf_rate(FREQ) | sf_channels(CHANS) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
channel_map_init_stereo(ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bass_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct bass_private *priv = ip_data->private;
|
||||
|
||||
BASS_MusicFree(priv->chan);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bass_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
int length;
|
||||
struct bass_private *priv = ip_data->private;
|
||||
length = BASS_ChannelGetData(priv->chan, buffer, count);
|
||||
if (length < 0) {
|
||||
return 0;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static int bass_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct bass_private *priv = ip_data->private;
|
||||
QWORD pos = (QWORD)(offset * (FREQ * CHANS * (BITS / 8)) + 0.5);
|
||||
QWORD flags = BASS_POS_BYTE | BASS_POS_DECODE;
|
||||
|
||||
if (!BASS_ChannelSetPosition(priv->chan, pos, flags)) {
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned char *encode_ascii_string(const char *str)
|
||||
{
|
||||
unsigned char *ret;
|
||||
int n;
|
||||
|
||||
ret = xmalloc(strlen(str) + 1);
|
||||
n = u_to_ascii(ret, str, strlen(str));
|
||||
ret[n] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bass_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct bass_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *val;
|
||||
|
||||
val = BASS_ChannelGetTags(priv->chan, BASS_TAG_MUSIC_NAME);
|
||||
if (val && val[0]) {
|
||||
unsigned char *val_encoded = encode_ascii_string(val);
|
||||
comments_add_const(&c, "title", (char *)val_encoded);
|
||||
free(val_encoded);
|
||||
}
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bass_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
static float length = 0;
|
||||
int pos;
|
||||
struct bass_private *priv = ip_data->private;
|
||||
|
||||
pos = BASS_ChannelGetLength(priv->chan, BASS_POS_BYTE);
|
||||
if (pos && pos != -1) {
|
||||
length = BASS_ChannelBytes2Seconds(priv->chan, pos);
|
||||
}
|
||||
else {
|
||||
length = -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static long bass_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static const char *bass_type_to_string(int type)
|
||||
{
|
||||
/* from <bass.h> */
|
||||
switch (type) {
|
||||
case 0x20000: return "mod";
|
||||
case 0x20001: return "mtm";
|
||||
case 0x20002: return "s3m";
|
||||
case 0x20003: return "xm";
|
||||
case 0x20004: return "it";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *bass_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
const char *codec;
|
||||
int type;
|
||||
BASS_CHANNELINFO info;
|
||||
struct bass_private *priv = ip_data->private;
|
||||
|
||||
if (!(BASS_ChannelGetInfo(priv->chan, &info))) {
|
||||
return NULL;
|
||||
}
|
||||
type = info.ctype;
|
||||
codec = bass_type_to_string(type);
|
||||
return codec ? xstrdup(codec) : NULL;
|
||||
}
|
||||
|
||||
static char *bass_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = bass_open,
|
||||
.close = bass_close,
|
||||
.read = bass_read,
|
||||
.seek = bass_seek,
|
||||
.read_comments = bass_read_comments,
|
||||
.duration = bass_duration,
|
||||
.bitrate = bass_bitrate,
|
||||
.bitrate_current = bass_bitrate,
|
||||
.codec = bass_codec,
|
||||
.codec_profile = bass_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 60;
|
||||
const char * const ip_extensions[] = {
|
||||
"xm", "it", "s3m", "mod", "mtm", "umx", NULL
|
||||
};
|
||||
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
|
||||
551
ip/cdio.c
Normal file
551
ip/cdio.c
Normal file
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
* Copyright 2011-2013 Various Authors
|
||||
* Copyright 2011 Johannes Weißl
|
||||
*
|
||||
* Based on cdda.c from XMMS2.
|
||||
*
|
||||
* 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 "../file.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../utils.h"
|
||||
#include "../options.h"
|
||||
#include "../comment.h"
|
||||
#include "../discid.h"
|
||||
|
||||
#include <cdio/cdio.h>
|
||||
#include <cdio/logging.h>
|
||||
#if LIBCDIO_VERSION_NUM >= 90
|
||||
#include <cdio/paranoia/cdda.h>
|
||||
#else
|
||||
#include <cdio/cdda.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#undef HAVE_CDDB
|
||||
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/cdio.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
#include "../http.h"
|
||||
#include "../xstrjoin.h"
|
||||
#include <cddb/cddb.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
static char *cddb_url = NULL;
|
||||
#endif
|
||||
|
||||
static struct {
|
||||
CdIo_t *cdio;
|
||||
cdrom_drive_t *drive;
|
||||
const char *disc_id;
|
||||
const char *device;
|
||||
} cached;
|
||||
|
||||
struct cdda_private {
|
||||
CdIo_t *cdio;
|
||||
cdrom_drive_t *drive;
|
||||
char *disc_id;
|
||||
char *device;
|
||||
track_t track;
|
||||
lsn_t first_lsn;
|
||||
lsn_t last_lsn;
|
||||
lsn_t current_lsn;
|
||||
int first_read;
|
||||
|
||||
char read_buf[CDIO_CD_FRAMESIZE_RAW];
|
||||
unsigned long buf_used;
|
||||
};
|
||||
|
||||
static void libcdio_log(cdio_log_level_t level, const char *message)
|
||||
{
|
||||
const char *level_names[] = { "DEBUG", "INFO", "WARN", "ERROR", "ASSERT" };
|
||||
int len = strlen(message);
|
||||
if (len > 0 && message[len-1] == '\n')
|
||||
len--;
|
||||
if (len > 0) {
|
||||
level = clamp(level, 1, N_ELEMENTS(level_names));
|
||||
d_print("%s: %.*s\n", level_names[level-1], len, message);
|
||||
}
|
||||
}
|
||||
|
||||
static int libcdio_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cdda_private *priv, priv_init = {
|
||||
.first_read = 1,
|
||||
.buf_used = CDIO_CD_FRAMESIZE_RAW
|
||||
};
|
||||
CdIo_t *cdio = NULL;
|
||||
cdrom_drive_t *drive = NULL;
|
||||
const char *device = cdda_device;
|
||||
lsn_t first_lsn;
|
||||
int track = -1;
|
||||
char *disc_id = NULL;
|
||||
char *msg = NULL;
|
||||
int rc = 0, save = 0;
|
||||
|
||||
if (!parse_cdda_url(ip_data->filename, &disc_id, &track, NULL)) {
|
||||
rc = -IP_ERROR_INVALID_URI;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (track == -1) {
|
||||
d_print("invalid or missing track number, aborting!\n");
|
||||
rc = -IP_ERROR_INVALID_URI;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* In case of cue/toc/nrg, take filename (= disc_id) as device.
|
||||
* A real disc_id is base64 encoded and never contains a slash */
|
||||
if (strchr(disc_id, '/'))
|
||||
device = disc_id;
|
||||
|
||||
ip_data->fd = open(device, O_RDONLY);
|
||||
if (ip_data->fd == -1) {
|
||||
save = errno;
|
||||
d_print("could not open device %s\n", device);
|
||||
rc = -IP_ERROR_ERRNO;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (cached.cdio && strcmp(disc_id, cached.disc_id) == 0 && strcmp(device, cached.device) == 0) {
|
||||
cdio = cached.cdio;
|
||||
drive = cached.drive;
|
||||
} else {
|
||||
cdio_log_set_handler(libcdio_log);
|
||||
cdio = cdio_open(device, DRIVER_UNKNOWN);
|
||||
if (!cdio) {
|
||||
d_print("failed to open device %s\n", device);
|
||||
rc = -IP_ERROR_NO_DISC;
|
||||
goto end;
|
||||
}
|
||||
cdio_set_speed(cdio, 1);
|
||||
|
||||
drive = cdio_cddap_identify_cdio(cdio, CDDA_MESSAGE_LOGIT, &msg);
|
||||
if (!drive) {
|
||||
d_print("failed to identify drive, aborting!\n");
|
||||
rc = -IP_ERROR_NO_DISC;
|
||||
goto end;
|
||||
}
|
||||
d_print("%s", msg);
|
||||
cdio_cddap_verbose_set(drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_LOGIT);
|
||||
drive->b_swap_bytes = 1;
|
||||
|
||||
if (cdio_cddap_open(drive)) {
|
||||
d_print("unable to open disc, aborting!\n");
|
||||
rc = -IP_ERROR_NO_DISC;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
first_lsn = cdio_cddap_track_firstsector(drive, track);
|
||||
if (first_lsn == -1) {
|
||||
d_print("no such track: %d, aborting!\n", track);
|
||||
rc = -IP_ERROR_INVALID_URI;
|
||||
goto end;
|
||||
}
|
||||
|
||||
priv = xnew(struct cdda_private, 1);
|
||||
*priv = priv_init;
|
||||
priv->cdio = cdio;
|
||||
priv->drive = drive;
|
||||
priv->disc_id = xstrdup(disc_id);
|
||||
priv->device = xstrdup(device);
|
||||
priv->track = track;
|
||||
priv->first_lsn = first_lsn;
|
||||
priv->last_lsn = cdio_cddap_track_lastsector(drive, priv->track);
|
||||
priv->current_lsn = first_lsn;
|
||||
|
||||
cached.cdio = priv->cdio;
|
||||
cached.drive = priv->drive;
|
||||
cached.disc_id = priv->disc_id;
|
||||
cached.device = priv->device;
|
||||
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
|
||||
end:
|
||||
free(disc_id);
|
||||
|
||||
if (rc < 0) {
|
||||
if (ip_data->fd != -1)
|
||||
close(ip_data->fd);
|
||||
ip_data->fd = -1;
|
||||
}
|
||||
|
||||
if (rc == -IP_ERROR_ERRNO)
|
||||
errno = save;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int libcdio_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
|
||||
if (ip_data->fd != -1)
|
||||
close(ip_data->fd);
|
||||
ip_data->fd = -1;
|
||||
|
||||
if (strcmp(priv->disc_id, cached.disc_id) != 0 || strcmp(priv->device, cached.device) != 0) {
|
||||
cdio_cddap_close_no_free_cdio(priv->drive);
|
||||
cdio_destroy(priv->cdio);
|
||||
free(priv->disc_id);
|
||||
free(priv->device);
|
||||
}
|
||||
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcdio_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
int rc = 0;
|
||||
|
||||
if (priv->first_read || cdio_get_media_changed(priv->cdio)) {
|
||||
char *disc_id;
|
||||
priv->first_read = 0;
|
||||
if (!get_disc_id(priv->device, &disc_id, NULL))
|
||||
return -IP_ERROR_NO_DISC;
|
||||
if (strcmp(disc_id, priv->disc_id) != 0) {
|
||||
free(disc_id);
|
||||
return -IP_ERROR_WRONG_DISC;
|
||||
}
|
||||
free(disc_id);
|
||||
}
|
||||
|
||||
if (priv->current_lsn >= priv->last_lsn)
|
||||
return 0;
|
||||
|
||||
if (priv->buf_used == CDIO_CD_FRAMESIZE_RAW) {
|
||||
cdio_cddap_read(priv->drive, priv->read_buf, priv->current_lsn, 1);
|
||||
priv->current_lsn++;
|
||||
priv->buf_used = 0;
|
||||
}
|
||||
|
||||
if (count >= CDIO_CD_FRAMESIZE_RAW) {
|
||||
rc = CDIO_CD_FRAMESIZE_RAW - priv->buf_used;
|
||||
memcpy(buffer, priv->read_buf + priv->buf_used, rc);
|
||||
} else {
|
||||
unsigned long buf_left = CDIO_CD_FRAMESIZE_RAW - priv->buf_used;
|
||||
|
||||
if (buf_left < count) {
|
||||
memcpy(buffer, priv->read_buf + priv->buf_used, buf_left);
|
||||
rc = buf_left;
|
||||
} else {
|
||||
memcpy(buffer, priv->read_buf + priv->buf_used, count);
|
||||
rc = count;
|
||||
}
|
||||
}
|
||||
priv->buf_used += rc;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int libcdio_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
lsn_t new_lsn;
|
||||
int64_t samples = offset * 44100;
|
||||
|
||||
/* Magic number 42... really should think of a better way to do this but
|
||||
* it seemed that the lsn is off by about 42 everytime...
|
||||
*/
|
||||
new_lsn = samples / 441.0 * CDIO_CD_FRAMES_PER_SEC / 100 + 42;
|
||||
|
||||
if ((priv->first_lsn + new_lsn) > priv->last_lsn) {
|
||||
d_print("trying to seek past the end of track.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
priv->current_lsn = priv->first_lsn + new_lsn;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
static int parse_cddb_url(const char *url, struct http_uri *http_uri, int *use_http)
|
||||
{
|
||||
char *full_url;
|
||||
int rc;
|
||||
|
||||
if (is_http_url(url)) {
|
||||
*use_http = 1;
|
||||
full_url = xstrdup(url);
|
||||
} else {
|
||||
*use_http = 0;
|
||||
full_url = xstrjoin("http://", url);
|
||||
}
|
||||
|
||||
rc = http_parse_uri(full_url, http_uri);
|
||||
free(full_url);
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
static void setup_cddb_conn(cddb_conn_t *cddb_conn)
|
||||
{
|
||||
struct http_uri http_uri, http_proxy_uri;
|
||||
const char *proxy;
|
||||
int use_http;
|
||||
|
||||
parse_cddb_url(cddb_url, &http_uri, &use_http);
|
||||
|
||||
proxy = getenv("http_proxy");
|
||||
if (proxy && http_parse_uri(proxy, &http_proxy_uri) == 0) {
|
||||
cddb_http_proxy_enable(cddb_conn);
|
||||
cddb_set_http_proxy_server_name(cddb_conn, http_proxy_uri.host);
|
||||
cddb_set_http_proxy_server_port(cddb_conn, http_proxy_uri.port);
|
||||
if (http_proxy_uri.user)
|
||||
cddb_set_http_proxy_username(cddb_conn, http_proxy_uri.user);
|
||||
if (http_proxy_uri.pass)
|
||||
cddb_set_http_proxy_password(cddb_conn, http_proxy_uri.pass);
|
||||
http_free_uri(&http_proxy_uri);
|
||||
} else
|
||||
cddb_http_proxy_disable(cddb_conn);
|
||||
|
||||
if (use_http)
|
||||
cddb_http_enable(cddb_conn);
|
||||
else
|
||||
cddb_http_disable(cddb_conn);
|
||||
|
||||
cddb_set_server_name(cddb_conn, http_uri.host);
|
||||
cddb_set_email_address(cddb_conn, "me@home");
|
||||
cddb_set_server_port(cddb_conn, http_uri.port);
|
||||
if (strcmp(http_uri.path, "/") != 0)
|
||||
cddb_set_http_path_query(cddb_conn, http_uri.path);
|
||||
#ifdef DEBUG_CDDB
|
||||
cddb_cache_disable(cddb_conn);
|
||||
#endif
|
||||
|
||||
http_free_uri(&http_uri);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#define add_comment(c, x) do { if (x) comments_add_const(c, #x, x); } while (0)
|
||||
|
||||
static int libcdio_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *artist = NULL, *albumartist = NULL, *album = NULL,
|
||||
*title = NULL, *genre = NULL, *comment = NULL;
|
||||
const cdtext_t *cdt;
|
||||
#ifdef HAVE_CDDB
|
||||
bool track_comments_found = false;
|
||||
cddb_conn_t *cddb_conn = NULL;
|
||||
cddb_disc_t *cddb_disc = NULL;
|
||||
#endif
|
||||
char buf[64];
|
||||
|
||||
#if LIBCDIO_VERSION_NUM >= 90
|
||||
cdt = cdio_get_cdtext(priv->cdio);
|
||||
if (cdt) {
|
||||
artist = cdtext_get(cdt, CDTEXT_FIELD_PERFORMER, priv->track);
|
||||
title = cdtext_get(cdt, CDTEXT_FIELD_TITLE, priv->track);
|
||||
genre = cdtext_get(cdt, CDTEXT_FIELD_GENRE, priv->track);
|
||||
comment = cdtext_get(cdt, CDTEXT_FIELD_MESSAGE, priv->track);
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
if (title)
|
||||
track_comments_found = true;
|
||||
#endif
|
||||
|
||||
album = cdtext_get(cdt, CDTEXT_FIELD_TITLE, 0);
|
||||
albumartist = cdtext_get(cdt, CDTEXT_FIELD_PERFORMER, 0);
|
||||
if (!artist)
|
||||
artist = albumartist;
|
||||
if (!genre)
|
||||
genre = cdtext_get(cdt, CDTEXT_FIELD_GENRE, 0);
|
||||
if (!comment)
|
||||
comment = cdtext_get(cdt, CDTEXT_FIELD_MESSAGE, 0);
|
||||
}
|
||||
#else
|
||||
cdt = cdio_get_cdtext(priv->cdio, priv->track);
|
||||
if (cdt) {
|
||||
char * const *field = cdt->field;
|
||||
artist = field[CDTEXT_PERFORMER];
|
||||
title = field[CDTEXT_TITLE];
|
||||
genre = field[CDTEXT_GENRE];
|
||||
comment = field[CDTEXT_MESSAGE];
|
||||
#ifdef HAVE_CDDB
|
||||
track_comments_found = true;
|
||||
#endif
|
||||
}
|
||||
cdt = cdio_get_cdtext(priv->cdio, 0);
|
||||
if (cdt) {
|
||||
char * const *field = cdt->field;
|
||||
album = field[CDTEXT_TITLE];
|
||||
albumartist = field[CDTEXT_PERFORMER];
|
||||
if (!artist)
|
||||
artist = field[CDTEXT_PERFORMER];
|
||||
if (!genre)
|
||||
genre = field[CDTEXT_GENRE];
|
||||
if (!comment)
|
||||
comment = field[CDTEXT_MESSAGE];
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
if (!track_comments_found && cddb_url && cddb_url[0]) {
|
||||
cddb_track_t *cddb_track;
|
||||
track_t i_tracks = cdio_get_num_tracks(priv->cdio);
|
||||
track_t i_first_track = cdio_get_first_track_num(priv->cdio);
|
||||
unsigned int year;
|
||||
int i;
|
||||
|
||||
cddb_conn = cddb_new();
|
||||
if (!cddb_conn)
|
||||
malloc_fail();
|
||||
|
||||
setup_cddb_conn(cddb_conn);
|
||||
|
||||
cddb_disc = cddb_disc_new();
|
||||
if (!cddb_disc)
|
||||
malloc_fail();
|
||||
for (i = 0; i < i_tracks; i++) {
|
||||
cddb_track = cddb_track_new();
|
||||
if (!cddb_track)
|
||||
malloc_fail();
|
||||
cddb_track_set_frame_offset(cddb_track,
|
||||
cdio_get_track_lba(priv->cdio, i+i_first_track));
|
||||
cddb_disc_add_track(cddb_disc, cddb_track);
|
||||
}
|
||||
|
||||
cddb_disc_set_length(cddb_disc, cdio_get_track_lba(priv->cdio,
|
||||
CDIO_CDROM_LEADOUT_TRACK) / CDIO_CD_FRAMES_PER_SEC);
|
||||
if (cddb_query(cddb_conn, cddb_disc) == 1 && cddb_read(cddb_conn, cddb_disc)) {
|
||||
albumartist = cddb_disc_get_artist(cddb_disc);
|
||||
album = cddb_disc_get_title(cddb_disc);
|
||||
genre = cddb_disc_get_genre(cddb_disc);
|
||||
year = cddb_disc_get_year(cddb_disc);
|
||||
if (year) {
|
||||
sprintf(buf, "%u", year);
|
||||
comments_add_const(&c, "date", buf);
|
||||
}
|
||||
cddb_track = cddb_disc_get_track(cddb_disc, priv->track - 1);
|
||||
artist = cddb_track_get_artist(cddb_track);
|
||||
if (!artist)
|
||||
artist = albumartist;
|
||||
title = cddb_track_get_title(cddb_track);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
add_comment(&c, artist);
|
||||
add_comment(&c, albumartist);
|
||||
add_comment(&c, album);
|
||||
add_comment(&c, title);
|
||||
add_comment(&c, genre);
|
||||
add_comment(&c, comment);
|
||||
|
||||
sprintf(buf, "%02d", priv->track);
|
||||
comments_add_const(&c, "tracknumber", buf);
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
if (cddb_disc)
|
||||
cddb_disc_destroy(cddb_disc);
|
||||
if (cddb_conn)
|
||||
cddb_destroy(cddb_conn);
|
||||
#endif
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcdio_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
|
||||
return (priv->last_lsn - priv->first_lsn) / CDIO_CD_FRAMES_PER_SEC;
|
||||
}
|
||||
|
||||
static long libcdio_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return 44100 * 16 * 2;
|
||||
}
|
||||
|
||||
static char *libcdio_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("cdda");
|
||||
}
|
||||
|
||||
static char *libcdio_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cdda_private *priv = ip_data->private;
|
||||
discmode_t cd_discmode = cdio_get_discmode(priv->cdio);
|
||||
|
||||
return xstrdup(discmode2str[cd_discmode]);
|
||||
}
|
||||
|
||||
#ifdef HAVE_CDDB
|
||||
static int libcdio_set_cddb_url(const char *val)
|
||||
{
|
||||
struct http_uri http_uri;
|
||||
int use_http;
|
||||
if (!parse_cddb_url(val, &http_uri, &use_http))
|
||||
return -IP_ERROR_INVALID_URI;
|
||||
http_free_uri(&http_uri);
|
||||
free(cddb_url);
|
||||
cddb_url = xstrdup(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcdio_get_cddb_url(char **val)
|
||||
{
|
||||
if (!cddb_url)
|
||||
cddb_url = xstrdup("freedb.freedb.org:8880");
|
||||
*val = xstrdup(cddb_url);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = libcdio_open,
|
||||
.close = libcdio_close,
|
||||
.read = libcdio_read,
|
||||
.seek = libcdio_seek,
|
||||
.read_comments = libcdio_read_comments,
|
||||
.duration = libcdio_duration,
|
||||
.bitrate = libcdio_bitrate,
|
||||
.codec = libcdio_codec,
|
||||
.codec_profile = libcdio_codec_profile,
|
||||
};
|
||||
|
||||
const struct input_plugin_opt ip_options[] = {
|
||||
#ifdef HAVE_CDDB
|
||||
{ "cddb_url", libcdio_set_cddb_url, libcdio_get_cddb_url },
|
||||
#endif
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { NULL };
|
||||
const char * const ip_mime_types[] = { "x-content/audio-cdda", NULL };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
346
ip/cue.c
Normal file
346
ip/cue.c
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2013 Various Authors
|
||||
* Copyright (C) 2011 Gregory Petrosyan
|
||||
*
|
||||
* 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 "../debug.h"
|
||||
#include "../input.h"
|
||||
#include "../utils.h"
|
||||
#include "../comment.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../cue_utils.h"
|
||||
#include "../cue.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
|
||||
struct cue_private {
|
||||
struct input_plugin *child;
|
||||
|
||||
char *cue_filename;
|
||||
int track_n;
|
||||
|
||||
double start_offset;
|
||||
double current_offset;
|
||||
double end_offset;
|
||||
};
|
||||
|
||||
|
||||
static int _parse_cue_url(const char *url, char **filename, int *track_n)
|
||||
{
|
||||
const char *slash;
|
||||
long n;
|
||||
|
||||
if (!is_cue_url(url))
|
||||
return 1;
|
||||
|
||||
url += 6;
|
||||
|
||||
slash = strrchr(url, '/');
|
||||
if (!slash)
|
||||
return 1;
|
||||
|
||||
if (str_to_int(slash + 1, &n) != 0)
|
||||
return 1;
|
||||
|
||||
*filename = xstrndup(url, slash - url);
|
||||
*track_n = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static char *_make_absolute_path(const char *abs_filename, const char *rel_filename)
|
||||
{
|
||||
char *s;
|
||||
const char *slash;
|
||||
char buf[4096] = {0};
|
||||
|
||||
slash = strrchr(abs_filename, '/');
|
||||
if (slash == NULL)
|
||||
return xstrdup(rel_filename);
|
||||
|
||||
s = xstrndup(abs_filename, slash - abs_filename);
|
||||
snprintf(buf, sizeof(buf), "%s/%s", s, rel_filename);
|
||||
|
||||
free(s);
|
||||
return xstrdup(buf);
|
||||
}
|
||||
|
||||
|
||||
static int cue_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
int rc;
|
||||
char *child_filename;
|
||||
struct cue_sheet *cd;
|
||||
struct cue_track *t;
|
||||
struct cue_private *priv;
|
||||
|
||||
priv = xnew(struct cue_private, 1);
|
||||
|
||||
rc = _parse_cue_url(ip_data->filename, &priv->cue_filename, &priv->track_n);
|
||||
if (rc) {
|
||||
rc = -IP_ERROR_INVALID_URI;
|
||||
goto url_parse_failed;
|
||||
}
|
||||
|
||||
cd = cue_from_file(priv->cue_filename);
|
||||
if (cd == NULL) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto cue_parse_failed;
|
||||
}
|
||||
|
||||
t = cue_get_track(cd, priv->track_n);
|
||||
if (!t) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto cue_read_failed;
|
||||
}
|
||||
|
||||
child_filename = _make_absolute_path(priv->cue_filename, t->file);
|
||||
priv->child = ip_new(child_filename);
|
||||
free(child_filename);
|
||||
|
||||
rc = ip_open(priv->child);
|
||||
if (rc)
|
||||
goto ip_open_failed;
|
||||
|
||||
ip_setup(priv->child);
|
||||
|
||||
priv->start_offset = t->offset;
|
||||
priv->current_offset = t->offset;
|
||||
|
||||
rc = ip_seek(priv->child, priv->start_offset);
|
||||
if (rc)
|
||||
goto ip_open_failed;
|
||||
|
||||
if (t->length >= 0)
|
||||
priv->end_offset = priv->start_offset + t->length;
|
||||
else
|
||||
priv->end_offset = -1;
|
||||
|
||||
ip_data->fd = open(ip_get_filename(priv->child), O_RDONLY);
|
||||
if (ip_data->fd == -1)
|
||||
goto ip_open_failed;
|
||||
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = ip_get_sf(priv->child);
|
||||
ip_get_channel_map(priv->child, ip_data->channel_map);
|
||||
|
||||
cue_free(cd);
|
||||
return 0;
|
||||
|
||||
ip_open_failed:
|
||||
ip_delete(priv->child);
|
||||
|
||||
cue_read_failed:
|
||||
cue_free(cd);
|
||||
|
||||
cue_parse_failed:
|
||||
free(priv->cue_filename);
|
||||
|
||||
url_parse_failed:
|
||||
free(priv);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int cue_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
close(ip_data->fd);
|
||||
ip_data->fd = -1;
|
||||
|
||||
ip_delete(priv->child);
|
||||
free(priv->cue_filename);
|
||||
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int cue_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
int rc;
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
if (priv->end_offset >= 0.0 && priv->current_offset >= priv->end_offset)
|
||||
return 0;
|
||||
|
||||
rc = ip_read(priv->child, buffer, count);
|
||||
if (rc <= 0)
|
||||
return rc;
|
||||
|
||||
if (priv->end_offset >= 0.0) {
|
||||
sample_format_t sf = ip_get_sf(priv->child);
|
||||
double len = (double)rc / sf_get_second_size(sf);
|
||||
|
||||
double rem_len = priv->end_offset - priv->current_offset;
|
||||
priv->current_offset += len;
|
||||
|
||||
if (priv->current_offset >= priv->end_offset)
|
||||
rc = lround(rem_len * sf_get_rate(sf)) * sf_get_frame_size(sf);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int cue_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
double new_offset = priv->start_offset + offset;
|
||||
|
||||
if (priv->end_offset >= 0.0 && new_offset > priv->end_offset)
|
||||
new_offset = priv->end_offset;
|
||||
|
||||
priv->current_offset = new_offset;
|
||||
|
||||
return ip_seek(priv->child, new_offset);
|
||||
}
|
||||
|
||||
|
||||
static int cue_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
struct cue_sheet *cd = cue_from_file(priv->cue_filename);
|
||||
struct cue_track *t;
|
||||
int rc;
|
||||
char buf[32] = { 0 };
|
||||
GROWING_KEYVALS(c);
|
||||
|
||||
if (cd == NULL) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto cue_parse_failed;
|
||||
}
|
||||
|
||||
t = cue_get_track(cd, priv->track_n);
|
||||
if (!t) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto get_track_failed;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d", priv->track_n);
|
||||
comments_add_const(&c, "tracknumber", buf);
|
||||
|
||||
if (t->meta.title)
|
||||
comments_add_const(&c, "title", t->meta.title);
|
||||
if (cd->meta.title)
|
||||
comments_add_const(&c, "album", cd->meta.title);
|
||||
if (t->meta.performer)
|
||||
comments_add_const(&c, "artist", t->meta.performer);
|
||||
if (cd->meta.performer)
|
||||
comments_add_const(&c, "albumartist", cd->meta.performer);
|
||||
if (t->meta.date)
|
||||
comments_add_const(&c, "date", t->meta.date);
|
||||
else if (cd->meta.date)
|
||||
comments_add_const(&c, "date", cd->meta.date);
|
||||
if (cd->meta.compilation)
|
||||
comments_add_const(&c, "compilation", cd->meta.compilation);
|
||||
if (cd->meta.discnumber)
|
||||
comments_add_const(&c, "discnumber", cd->meta.discnumber);
|
||||
if (t->meta.genre)
|
||||
comments_add_const(&c, "genre", t->meta.genre);
|
||||
else if (cd->meta.genre)
|
||||
comments_add_const(&c, "genre", cd->meta.genre);
|
||||
|
||||
if (cd->meta.rg_gain)
|
||||
comments_add_const(&c, "replaygain_album_gain", cd->meta.rg_gain);
|
||||
if (cd->meta.rg_peak)
|
||||
comments_add_const(&c, "replaygain_album_peak", cd->meta.rg_peak);
|
||||
if (t->meta.rg_gain)
|
||||
comments_add_const(&c, "replaygain_track_gain", t->meta.rg_gain);
|
||||
if (t->meta.rg_peak)
|
||||
comments_add_const(&c, "replaygain_track_peak", t->meta.rg_peak);
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
|
||||
cue_free(cd);
|
||||
return 0;
|
||||
|
||||
get_track_failed:
|
||||
cue_free(cd);
|
||||
|
||||
cue_parse_failed:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static int cue_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
if (priv->end_offset < 0.0)
|
||||
return ip_duration(priv->child) - priv->start_offset;
|
||||
else
|
||||
return priv->end_offset - priv->start_offset;
|
||||
}
|
||||
|
||||
|
||||
static long cue_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
return ip_bitrate(priv->child);
|
||||
}
|
||||
|
||||
|
||||
static long cue_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
return ip_current_bitrate(priv->child);
|
||||
}
|
||||
|
||||
|
||||
static char *cue_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
return ip_codec(priv->child);
|
||||
}
|
||||
|
||||
|
||||
static char *cue_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct cue_private *priv = ip_data->private;
|
||||
|
||||
return ip_codec_profile(priv->child);
|
||||
}
|
||||
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = cue_open,
|
||||
.close = cue_close,
|
||||
.read = cue_read,
|
||||
.seek = cue_seek,
|
||||
.read_comments = cue_read_comments,
|
||||
.duration = cue_duration,
|
||||
.bitrate = cue_bitrate,
|
||||
.bitrate_current = cue_current_bitrate,
|
||||
.codec = cue_codec,
|
||||
.codec_profile = cue_codec_profile,
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "cue", NULL };
|
||||
const char * const ip_mime_types[] = { "application/x-cue", NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
552
ip/ffmpeg.c
Normal file
552
ip/ffmpeg.c
Normal file
@@ -0,0 +1,552 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2007 Kevin Ko <kevin.s.ko@gmail.com>
|
||||
*
|
||||
* 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 "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../utils.h"
|
||||
#include "../comment.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/ffmpeg.h"
|
||||
#endif
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#ifndef AVUTIL_MATHEMATICS_H
|
||||
#include <libavutil/mathematics.h>
|
||||
#endif
|
||||
|
||||
struct ffmpeg_private {
|
||||
AVCodecContext *codec_ctx;
|
||||
AVFormatContext *format_ctx;
|
||||
AVCodec const *codec;
|
||||
SwrContext *swr;
|
||||
int stream_index;
|
||||
|
||||
AVPacket *pkt;
|
||||
AVFrame *frame;
|
||||
double seek_ts;
|
||||
int64_t skip_samples;
|
||||
|
||||
/* A buffer to hold swr_convert()-ed samples */
|
||||
AVFrame *swr_frame;
|
||||
int swr_frame_samples_cap;
|
||||
int swr_frame_start;
|
||||
|
||||
/* Bitrate estimation */
|
||||
unsigned long curr_size;
|
||||
unsigned long curr_duration;
|
||||
};
|
||||
|
||||
static const char *ffmpeg_errmsg(int err)
|
||||
{
|
||||
static char errstr[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_strerror(err, errstr, AV_ERROR_MAX_STRING_SIZE);
|
||||
return errstr;
|
||||
}
|
||||
|
||||
static void ffmpeg_init(void)
|
||||
{
|
||||
static int inited = 0;
|
||||
|
||||
if (inited != 0)
|
||||
return;
|
||||
inited = 1;
|
||||
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
||||
/* We could register decoders explicitly to save memory, but we have to
|
||||
* be careful about compatibility. */
|
||||
av_register_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
static int ffmpeg_open_input(struct input_plugin_data *ip_data,
|
||||
struct ffmpeg_private *priv)
|
||||
{
|
||||
AVFormatContext *ic = NULL;
|
||||
AVCodecContext *cc = NULL;
|
||||
AVCodecParameters *cp = NULL;
|
||||
AVCodec const *codec = NULL;
|
||||
int stream_index = -1;
|
||||
|
||||
int err;
|
||||
int res = avformat_open_input(&ic, ip_data->filename, NULL, NULL);
|
||||
if (res < 0) {
|
||||
err = -IP_ERROR_FILE_FORMAT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
res = avformat_find_stream_info(ic, NULL);
|
||||
if (res < 0) {
|
||||
d_print("unable to find stream info\n");
|
||||
err = -IP_ERROR_FILE_FORMAT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ic->nb_streams; i++) {
|
||||
cp = ic->streams[i]->codecpar;
|
||||
if (cp->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
stream_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stream_index == -1) {
|
||||
d_print("could not find audio stream\n");
|
||||
err = -IP_ERROR_FILE_FORMAT;
|
||||
goto err_silent;
|
||||
}
|
||||
|
||||
codec = avcodec_find_decoder(cp->codec_id);
|
||||
if (!codec) {
|
||||
d_print("codec (id: %d, name: %s) not found\n",
|
||||
cc->codec_id, avcodec_get_name(cc->codec_id));
|
||||
err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
goto err_silent;
|
||||
}
|
||||
cc = avcodec_alloc_context3(codec);
|
||||
avcodec_parameters_to_context(cc, cp);
|
||||
|
||||
res = avcodec_open2(cc, codec, NULL);
|
||||
if (res < 0) {
|
||||
d_print("could not open codec (id: %d, name: %s)\n",
|
||||
cc->codec_id, avcodec_get_name(cc->codec_id));
|
||||
err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
goto err;
|
||||
}
|
||||
|
||||
priv->format_ctx = ic;
|
||||
priv->codec_ctx = cc;
|
||||
priv->codec = codec;
|
||||
priv->stream_index = stream_index;
|
||||
return 0;
|
||||
err:
|
||||
d_print("%s\n", ffmpeg_errmsg(res));
|
||||
err_silent:
|
||||
avcodec_free_context(&cc);
|
||||
avformat_close_input(&ic);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void ffmpeg_set_sf_and_swr_opts(SwrContext *swr, AVCodecContext *cc,
|
||||
sample_format_t *sf_out, enum AVSampleFormat *out_sample_fmt)
|
||||
{
|
||||
int out_sample_rate = min_u(cc->sample_rate, 384000);
|
||||
sample_format_t sf = sf_rate(out_sample_rate) | sf_host_endian();
|
||||
av_opt_set_int(swr, "in_sample_rate", cc->sample_rate, 0);
|
||||
av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0);
|
||||
|
||||
switch (cc->sample_fmt) {
|
||||
case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP:
|
||||
case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P:
|
||||
sf |= sf_bits(32) | sf_signed(1);
|
||||
*out_sample_fmt = AV_SAMPLE_FMT_S32;
|
||||
break;
|
||||
default:
|
||||
sf |= sf_bits(16) | sf_signed(1);
|
||||
*out_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
}
|
||||
av_opt_set_sample_fmt(swr, "in_sample_fmt", cc->sample_fmt, 0);
|
||||
av_opt_set_sample_fmt(swr, "out_sample_fmt", *out_sample_fmt, 0);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
sf |= sf_channels(cc->ch_layout.nb_channels);
|
||||
|
||||
if (cc->ch_layout.order == AV_CHANNEL_ORDER_UNSPEC)
|
||||
av_channel_layout_default(&cc->ch_layout, cc->ch_layout.nb_channels);
|
||||
av_opt_set_chlayout(swr, "in_chlayout", &cc->ch_layout, 0);
|
||||
av_opt_set_chlayout(swr, "out_chlayout", &cc->ch_layout, 0);
|
||||
#else
|
||||
sf |= sf_channels(cc->channels);
|
||||
|
||||
av_opt_set_int(swr, "in_channel_layout",
|
||||
av_get_default_channel_layout(cc->channels), 0);
|
||||
av_opt_set_int(swr, "out_channel_layout",
|
||||
av_get_default_channel_layout(cc->channels), 0);
|
||||
#endif
|
||||
|
||||
*sf_out = sf;
|
||||
}
|
||||
|
||||
static int ffmpeg_init_swr_frame(struct ffmpeg_private *priv,
|
||||
sample_format_t sf, enum AVSampleFormat out_sample_fmt)
|
||||
{
|
||||
AVCodecContext *cc = priv->codec_ctx;
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
av_channel_layout_copy(&frame->ch_layout, &cc->ch_layout);
|
||||
#else
|
||||
frame->channel_layout = av_get_default_channel_layout(cc->channels);
|
||||
#endif
|
||||
|
||||
frame->sample_rate = sf_get_rate(sf);
|
||||
frame->format = out_sample_fmt;
|
||||
|
||||
/* NOTE: 10 sec is probably too much, but the amount of space
|
||||
* needed for swr_convert() is unpredictable */
|
||||
frame->nb_samples = 10 * sf_get_rate(sf);
|
||||
int res = av_frame_get_buffer(frame, 0);
|
||||
if (res < 0) {
|
||||
d_print("av_frame_get_buffer(): %s\n", ffmpeg_errmsg(res));
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
priv->swr_frame_samples_cap = frame->nb_samples;
|
||||
frame->nb_samples = 0;
|
||||
|
||||
priv->swr_frame = frame;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ffmpeg_free(struct ffmpeg_private *priv)
|
||||
{
|
||||
avcodec_free_context(&priv->codec_ctx);
|
||||
avformat_close_input(&priv->format_ctx);
|
||||
|
||||
swr_free(&priv->swr);
|
||||
|
||||
av_frame_free(&priv->frame);
|
||||
av_packet_free(&priv->pkt);
|
||||
av_frame_free(&priv->swr_frame);
|
||||
}
|
||||
|
||||
static int ffmpeg_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private priv;
|
||||
enum AVSampleFormat out_sample_fmt;
|
||||
memset(&priv, 0, sizeof(struct ffmpeg_private));
|
||||
|
||||
ffmpeg_init();
|
||||
|
||||
int err = ffmpeg_open_input(ip_data, &priv);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
priv.pkt = av_packet_alloc();
|
||||
priv.frame = av_frame_alloc();
|
||||
priv.seek_ts = -1;
|
||||
|
||||
priv.swr = swr_alloc();
|
||||
ffmpeg_set_sf_and_swr_opts(priv.swr, priv.codec_ctx,
|
||||
&ip_data->sf, &out_sample_fmt);
|
||||
swr_init(priv.swr);
|
||||
|
||||
err = ffmpeg_init_swr_frame(&priv, ip_data->sf, out_sample_fmt);
|
||||
if (err < 0) {
|
||||
ffmpeg_free(&priv);
|
||||
return err;
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
channel_map_init_waveex(priv.codec_ctx->ch_layout.nb_channels,
|
||||
priv.codec_ctx->ch_layout.u.mask, ip_data->channel_map);
|
||||
#else
|
||||
channel_map_init_waveex(priv.codec_ctx->channels,
|
||||
priv.codec_ctx->channel_layout, ip_data->channel_map);
|
||||
#endif
|
||||
|
||||
ip_data->private = xnew(struct ffmpeg_private, 1);
|
||||
memcpy(ip_data->private, &priv, sizeof(struct ffmpeg_private));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ffmpeg_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
ffmpeg_free(ip_data->private);
|
||||
free(ip_data->private);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t ffmpeg_calc_skip_samples(struct ffmpeg_private *priv)
|
||||
{
|
||||
int64_t ts;
|
||||
if (priv->frame->pts >= 0) {
|
||||
ts = priv->frame->pts;
|
||||
} else if (priv->frame->pkt_dts >= 0) {
|
||||
ts = priv->frame->pkt_dts;
|
||||
} else {
|
||||
d_print("AVFrame.pts and AVFrame.pkt_dts are unset\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
AVStream *s = priv->format_ctx->streams[priv->stream_index];
|
||||
double frame_ts = ts * av_q2d(s->time_base);
|
||||
|
||||
d_print("seek_ts: %.6fs, frame_ts: %.6fs\n", priv->seek_ts, frame_ts);
|
||||
|
||||
if (frame_ts >= priv->seek_ts)
|
||||
return 0;
|
||||
return (priv->seek_ts - frame_ts) * priv->frame->sample_rate;
|
||||
}
|
||||
|
||||
static void ffmpeg_skip_frame_part(struct ffmpeg_private *priv)
|
||||
{
|
||||
if (priv->skip_samples >= priv->frame->nb_samples) {
|
||||
d_print("skipping frame: %d samples\n",
|
||||
priv->frame->nb_samples);
|
||||
priv->skip_samples -= priv->frame->nb_samples;
|
||||
priv->frame->nb_samples = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int bps = av_get_bytes_per_sample(priv->frame->format);
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
int channels = priv->codec_ctx->ch_layout.nb_channels;
|
||||
#else
|
||||
int channels = priv->codec_ctx->channels;
|
||||
#endif
|
||||
|
||||
priv->frame->nb_samples -= priv->skip_samples;
|
||||
|
||||
/* Just modify frame's data pointer because it's throw-away */
|
||||
if (av_sample_fmt_is_planar(priv->frame->format)) {
|
||||
for (int i = 0; i < channels; i++)
|
||||
priv->frame->extended_data[i] += priv->skip_samples * bps;
|
||||
} else {
|
||||
priv->frame->extended_data[0] += priv->skip_samples * channels * bps;
|
||||
}
|
||||
d_print("skipping %lld samples\n", (long long)priv->skip_samples);
|
||||
priv->skip_samples = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* return:
|
||||
* <0 - error
|
||||
* 0 - eof
|
||||
* >0 - ok
|
||||
*/
|
||||
static int ffmpeg_get_frame(struct ffmpeg_private *priv)
|
||||
{
|
||||
int res;
|
||||
retry:
|
||||
res = avcodec_receive_frame(priv->codec_ctx, priv->frame);
|
||||
if (res == AVERROR(EAGAIN)) {
|
||||
av_packet_unref(priv->pkt);
|
||||
res = av_read_frame(priv->format_ctx, priv->pkt);
|
||||
if (res < 0)
|
||||
goto err;
|
||||
|
||||
if (priv->pkt->stream_index != priv->stream_index)
|
||||
goto retry;
|
||||
|
||||
priv->curr_size += priv->pkt->size;
|
||||
priv->curr_duration += priv->pkt->duration;
|
||||
|
||||
res = avcodec_send_packet(priv->codec_ctx, priv->pkt);
|
||||
if (res == 0 || res == AVERROR(EAGAIN))
|
||||
goto retry;
|
||||
}
|
||||
if (res < 0)
|
||||
goto err;
|
||||
|
||||
if (priv->seek_ts > 0) {
|
||||
priv->skip_samples = ffmpeg_calc_skip_samples(priv);
|
||||
if (priv->skip_samples >= 0)
|
||||
priv->seek_ts = -1;
|
||||
}
|
||||
|
||||
if (priv->skip_samples > 0) {
|
||||
ffmpeg_skip_frame_part(priv);
|
||||
if (priv->frame->nb_samples == 0)
|
||||
goto retry;
|
||||
}
|
||||
return 1;
|
||||
err:
|
||||
if (res == AVERROR_EOF)
|
||||
return 0;
|
||||
d_print("%s\n", ffmpeg_errmsg(res));
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
|
||||
static int ffmpeg_convert_frame(struct ffmpeg_private *priv)
|
||||
{
|
||||
int res = swr_convert(priv->swr,
|
||||
priv->swr_frame->extended_data,
|
||||
priv->swr_frame_samples_cap,
|
||||
(const uint8_t **)priv->frame->extended_data,
|
||||
priv->frame->nb_samples);
|
||||
if (res >= 0) {
|
||||
priv->swr_frame->nb_samples = res;
|
||||
priv->swr_frame_start = 0;
|
||||
return res;
|
||||
}
|
||||
d_print("%s\n", ffmpeg_errmsg(res));
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
|
||||
static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
int written = 0;
|
||||
int res;
|
||||
|
||||
count /= sf_get_frame_size(ip_data->sf);
|
||||
|
||||
while (count) {
|
||||
if (priv->swr_frame->nb_samples == 0) {
|
||||
res = ffmpeg_get_frame(priv);
|
||||
if (res == 0)
|
||||
break;
|
||||
else if (res < 0)
|
||||
return res;
|
||||
|
||||
res = ffmpeg_convert_frame(priv);
|
||||
if (res < 0)
|
||||
return res;
|
||||
}
|
||||
|
||||
int copy_frames = min_i(count, priv->swr_frame->nb_samples);
|
||||
int copy_bytes = copy_frames * sf_get_frame_size(ip_data->sf);
|
||||
void *dst = priv->swr_frame->extended_data[0] + priv->swr_frame_start;
|
||||
memcpy(buffer + written, dst, copy_bytes);
|
||||
|
||||
priv->swr_frame->nb_samples -= copy_frames;
|
||||
priv->swr_frame_start += copy_bytes;
|
||||
count -= copy_frames;
|
||||
written += copy_bytes;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
AVStream *st = priv->format_ctx->streams[priv->stream_index];
|
||||
|
||||
priv->seek_ts = offset;
|
||||
priv->skip_samples = 0;
|
||||
int64_t ts = offset / av_q2d(st->time_base);
|
||||
|
||||
int ret = avformat_seek_file(priv->format_ctx,
|
||||
priv->stream_index, 0, ts, ts, 0);
|
||||
if (ret < 0)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
priv->swr_frame->nb_samples = 0;
|
||||
priv->swr_frame_start = 0;
|
||||
avcodec_flush_buffers(priv->codec_ctx);
|
||||
swr_convert(priv->swr, NULL, 0, NULL, 0); /* flush swr buffer */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ffmpeg_read_metadata(struct growing_keyvals *c, AVDictionary *metadata)
|
||||
{
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
|
||||
while ((tag = av_dict_get(metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
|
||||
if (tag->value[0])
|
||||
comments_add_const(c, tag->key, tag->value);
|
||||
}
|
||||
}
|
||||
|
||||
static int ffmpeg_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
AVFormatContext *ic = priv->format_ctx;
|
||||
|
||||
GROWING_KEYVALS(c);
|
||||
|
||||
ffmpeg_read_metadata(&c, ic->metadata);
|
||||
for (unsigned i = 0; i < ic->nb_streams; i++) {
|
||||
ffmpeg_read_metadata(&c, ic->streams[i]->metadata);
|
||||
}
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ffmpeg_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
return priv->format_ctx->duration / AV_TIME_BASE;
|
||||
}
|
||||
|
||||
static long ffmpeg_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
long bitrate = priv->format_ctx->bit_rate;
|
||||
return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long ffmpeg_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
AVStream *st = priv->format_ctx->streams[priv->stream_index];
|
||||
long bitrate = -1;
|
||||
/* ape codec returns silly numbers */
|
||||
if (priv->codec->id == AV_CODEC_ID_APE)
|
||||
return -1;
|
||||
if (priv->curr_duration > 0) {
|
||||
double seconds = priv->curr_duration * av_q2d(st->time_base);
|
||||
bitrate = (8 * priv->curr_size) / seconds;
|
||||
priv->curr_size = 0;
|
||||
priv->curr_duration = 0;
|
||||
}
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static char *ffmpeg_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
return xstrdup(priv->codec->name);
|
||||
}
|
||||
|
||||
static char *ffmpeg_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct ffmpeg_private *priv = ip_data->private;
|
||||
const char *profile;
|
||||
profile = av_get_profile_name(priv->codec, priv->codec_ctx->profile);
|
||||
return profile ? xstrdup(profile) : NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = ffmpeg_open,
|
||||
.close = ffmpeg_close,
|
||||
.read = ffmpeg_read,
|
||||
.seek = ffmpeg_seek,
|
||||
.read_comments = ffmpeg_read_comments,
|
||||
.duration = ffmpeg_duration,
|
||||
.bitrate = ffmpeg_bitrate,
|
||||
.bitrate_current = ffmpeg_current_bitrate,
|
||||
.codec = ffmpeg_codec,
|
||||
.codec_profile = ffmpeg_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 30;
|
||||
const char *const ip_extensions[] = {
|
||||
"aa", "aac", "ac3", "aif", "aifc", "aiff", "ape", "au", "dsf", "fla",
|
||||
"flac", "m4a", "m4b", "mka", "mkv", "mp+", "mp2", "mp3", "mp4", "mpc",
|
||||
"mpp", "ogg", "opus", "shn", "tak", "tta", "wav", "webm", "wma", "wv",
|
||||
#ifdef USE_FALLBACK_IP
|
||||
"*",
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
const char *const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
519
ip/flac.c
Normal file
519
ip/flac.c
Normal file
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* 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;
|
||||
287
ip/mad.c
Normal file
287
ip/mad.c
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 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 "nomad.h"
|
||||
#include "../id3.h"
|
||||
#include "../ape.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../read_wrapper.h"
|
||||
#include "../debug.h"
|
||||
#include "../utils.h"
|
||||
#include "../comment.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static ssize_t read_func(void *datasource, void *buffer, size_t count)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
|
||||
return read_wrapper(ip_data, buffer, count);
|
||||
}
|
||||
|
||||
static off_t lseek_func(void *datasource, off_t offset, int whence)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
|
||||
return lseek(ip_data->fd, offset, whence);
|
||||
}
|
||||
|
||||
static int close_func(void *datasource)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
int rc;
|
||||
|
||||
rc = ip_data->fd != -1 ? close(ip_data->fd) : 0;
|
||||
ip_data->fd = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static struct nomad_callbacks callbacks = {
|
||||
.read = read_func,
|
||||
.lseek = lseek_func,
|
||||
.close = close_func
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static int mad_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
const struct nomad_info *info;
|
||||
int rc;
|
||||
|
||||
rc = nomad_open_callbacks(&nomad, ip_data, &callbacks);
|
||||
switch (rc) {
|
||||
case -NOMAD_ERROR_ERRNO:
|
||||
return -1;
|
||||
case -NOMAD_ERROR_FILE_FORMAT:
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
ip_data->private = nomad;
|
||||
|
||||
info = nomad_info(nomad);
|
||||
|
||||
/* always 16-bit signed little-endian */
|
||||
ip_data->sf = sf_rate(info->sample_rate) | sf_channels(info->channels) |
|
||||
sf_bits(16) | sf_signed(1);
|
||||
channel_map_init_waveex(info->channels, 0, ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mad_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
|
||||
nomad = ip_data->private;
|
||||
nomad_close(nomad);
|
||||
ip_data->fd = -1;
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mad_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
|
||||
nomad = ip_data->private;
|
||||
return nomad_read(nomad, buffer, count);
|
||||
}
|
||||
|
||||
static int mad_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
|
||||
nomad = ip_data->private;
|
||||
return nomad_time_seek(nomad, offset);
|
||||
}
|
||||
|
||||
static int mad_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
const struct nomad_lame *lame = nomad_lame(nomad);
|
||||
struct id3tag id3;
|
||||
int fd, rc, save, i;
|
||||
APETAG(ape);
|
||||
GROWING_KEYVALS(c);
|
||||
|
||||
fd = open(ip_data->filename, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
d_print("filename: %s\n", ip_data->filename);
|
||||
|
||||
id3_init(&id3);
|
||||
rc = id3_read_tags(&id3, fd, ID3_V1 | ID3_V2);
|
||||
save = errno;
|
||||
close(fd);
|
||||
errno = save;
|
||||
if (rc) {
|
||||
if (rc == -1) {
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
d_print("corrupted tag?\n");
|
||||
goto next;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_ID3_KEYS; i++) {
|
||||
char *val = id3_get_comment(&id3, i);
|
||||
|
||||
if (val)
|
||||
comments_add(&c, id3_key_names[i], val);
|
||||
}
|
||||
|
||||
next:
|
||||
id3_free(&id3);
|
||||
|
||||
rc = ape_read_tags(&ape, ip_data->fd, 0);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
char *k, *v;
|
||||
k = ape_get_comment(&ape, &v);
|
||||
if (!k)
|
||||
break;
|
||||
comments_add(&c, k, v);
|
||||
free(k);
|
||||
}
|
||||
|
||||
out:
|
||||
ape_free(&ape);
|
||||
|
||||
/* add last so the other tags get preference */
|
||||
if (lame && !isnan(lame->trackGain)) {
|
||||
char buf[64];
|
||||
|
||||
if (!isnan(lame->peak)) {
|
||||
sprintf(buf, "%f", lame->peak);
|
||||
comments_add_const(&c, "replaygain_track_peak", buf);
|
||||
}
|
||||
sprintf(buf, "%+.1f dB", lame->trackGain);
|
||||
comments_add_const(&c, "replaygain_track_gain", buf);
|
||||
}
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mad_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
|
||||
return nomad_info(nomad)->duration;
|
||||
}
|
||||
|
||||
static long mad_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
long bitrate = nomad_info(nomad)->avg_bitrate;
|
||||
|
||||
return bitrate != -1 ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long mad_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
return nomad_current_bitrate(nomad);
|
||||
}
|
||||
|
||||
static char *mad_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
|
||||
switch (nomad_info(nomad)->layer) {
|
||||
case 3:
|
||||
return xstrdup("mp3");
|
||||
case 2:
|
||||
return xstrdup("mp2");
|
||||
case 1:
|
||||
return xstrdup("mp1");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *mad_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct nomad *nomad = ip_data->private;
|
||||
const struct nomad_lame *lame = nomad_lame(nomad);
|
||||
const char *mode = nomad_info(nomad)->vbr ? "VBR" : "CBR";
|
||||
|
||||
if (lame) {
|
||||
/* LAME:
|
||||
* 0: unknown
|
||||
* 1: cbr
|
||||
* 2: abr
|
||||
* 3: vbr rh (--vbr-old)
|
||||
* 4: vbr mtrh (--vbr-new)
|
||||
* 5: vbr mt (obsolete)
|
||||
*/
|
||||
int method = lame->vbr_method;
|
||||
if (method == 2)
|
||||
mode = "ABR";
|
||||
else if (method >= 3 && method <= 5) {
|
||||
const struct nomad_xing *xing = nomad_xing(nomad);
|
||||
|
||||
if (xing && xing->flags & XING_SCALE && xing->scale && xing->scale <= 100) {
|
||||
char buf[16];
|
||||
int v = 10 - (xing->scale + 9) / 10;
|
||||
/* quality (-q): 10 - (xing->scale - ((9 - v) * 10)) */
|
||||
|
||||
sprintf(buf, "VBR V%d", v);
|
||||
return xstrdup(buf);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return xstrdup(mode);
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mad_open,
|
||||
.close = mad_close,
|
||||
.read = mad_read,
|
||||
.seek = mad_seek,
|
||||
.read_comments = mad_read_comments,
|
||||
.duration = mad_duration,
|
||||
.bitrate = mad_bitrate,
|
||||
.bitrate_current = mad_current_bitrate,
|
||||
.codec = mad_codec,
|
||||
.codec_profile = mad_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 55;
|
||||
const char * const ip_extensions[] = { "mp3", "mp2", NULL };
|
||||
const char * const ip_mime_types[] = {
|
||||
"audio/mpeg", "audio/x-mp3", "audio/x-mpeg", NULL
|
||||
};
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
176
ip/mikmod.c
Normal file
176
ip/mikmod.c
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Adapted from AlsaPlayer 0.99.76
|
||||
*
|
||||
* mikmod_engine.c
|
||||
* Copyright (C) 1999 Paul N. Fisher <rao@gnu.org>
|
||||
* Copyright (C) 2002 Andy Lo A Foe <andy@alsaplayer.org>
|
||||
*
|
||||
* 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 "../xmalloc.h"
|
||||
#include <mikmod.h>
|
||||
#include "../debug.h"
|
||||
#include "../comment.h"
|
||||
|
||||
struct mik_private {
|
||||
MODULE *file;
|
||||
};
|
||||
|
||||
static int mikmod_init(void)
|
||||
{
|
||||
static int inited = 0;
|
||||
|
||||
if (inited)
|
||||
return 1;
|
||||
|
||||
MikMod_RegisterAllDrivers();
|
||||
MikMod_RegisterAllLoaders();
|
||||
|
||||
md_reverb = 0;
|
||||
/* we should let the user decide which one is better... */
|
||||
md_mode = DMODE_SOFT_MUSIC | DMODE_SOFT_SNDFX | DMODE_STEREO |
|
||||
DMODE_16BITS | DMODE_INTERP;
|
||||
|
||||
if (MikMod_Init(NULL)) {
|
||||
d_print("Could not initialize mikmod, reason: %s\n",
|
||||
MikMod_strerror(MikMod_errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
inited = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int mik_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
MODULE *mf = NULL;
|
||||
struct mik_private *priv;
|
||||
int mi = mikmod_init();
|
||||
|
||||
if (!mi)
|
||||
return -IP_ERROR_INTERNAL;
|
||||
|
||||
mf = Player_Load(ip_data->filename, 255, 0);
|
||||
|
||||
if (!mf)
|
||||
return -IP_ERROR_ERRNO;
|
||||
|
||||
priv = xnew(struct mik_private, 1);
|
||||
priv->file = mf;
|
||||
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
channel_map_init_stereo(ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mik_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mik_private *priv = ip_data->private;
|
||||
|
||||
Player_Stop();
|
||||
Player_Free(priv->file);
|
||||
free(ip_data->private);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mik_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
int length;
|
||||
struct mik_private *priv = ip_data->private;
|
||||
|
||||
if (!Player_Active())
|
||||
Player_Start(priv->file);
|
||||
|
||||
if (!Player_Active())
|
||||
return 0;
|
||||
|
||||
length = VC_WriteBytes(buffer, count);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
static int mik_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
/* cannot seek in modules properly */
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static int mik_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct mik_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *val;
|
||||
|
||||
val = priv->file->songname;
|
||||
if (val && val[0])
|
||||
comments_add_const(&c, "title", val);
|
||||
|
||||
val = priv->file->comment;
|
||||
if (val && val[0])
|
||||
comments_add_const(&c, "comment", val);
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mik_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long mik_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static char *mik_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mik_private *priv = ip_data->private;
|
||||
const char *codec = priv->file->modtype;
|
||||
return (codec && codec[0]) ? xstrdup(codec) : NULL;
|
||||
}
|
||||
|
||||
static char *mik_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mik_open,
|
||||
.close = mik_close,
|
||||
.read = mik_read,
|
||||
.seek = mik_seek,
|
||||
.read_comments = mik_read_comments,
|
||||
.duration = mik_duration,
|
||||
.bitrate = mik_bitrate,
|
||||
.bitrate_current = mik_bitrate,
|
||||
.codec = mik_codec,
|
||||
.codec_profile = mik_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 40;
|
||||
const char * const ip_extensions[] = {
|
||||
"mod", "s3m", "xm", "it", "669", "amf", "dsm",
|
||||
"far", "med", "mtm", "stm", "ult",
|
||||
NULL
|
||||
};
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
244
ip/modplug.c
Normal file
244
ip/modplug.c
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* 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 "../file.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../comment.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/modplug.h"
|
||||
#endif
|
||||
|
||||
#include <libmodplug/modplug.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct mod_private {
|
||||
ModPlugFile *file;
|
||||
};
|
||||
|
||||
static int mod_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mod_private *priv;
|
||||
char *contents;
|
||||
off_t size;
|
||||
ssize_t rc;
|
||||
ModPlugFile *file;
|
||||
ModPlug_Settings settings;
|
||||
|
||||
size = lseek(ip_data->fd, 0, SEEK_END);
|
||||
if (size == -1)
|
||||
return -IP_ERROR_ERRNO;
|
||||
if (lseek(ip_data->fd, 0, SEEK_SET) == -1)
|
||||
return -IP_ERROR_ERRNO;
|
||||
|
||||
contents = xnew(char, size);
|
||||
rc = read_all(ip_data->fd, contents, size);
|
||||
if (rc == -1) {
|
||||
int save = errno;
|
||||
|
||||
free(contents);
|
||||
errno = save;
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
if (rc != size) {
|
||||
free(contents);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
errno = 0;
|
||||
file = ModPlug_Load(contents, size);
|
||||
if (file == NULL) {
|
||||
int save = errno;
|
||||
|
||||
free(contents);
|
||||
errno = save;
|
||||
if (errno == 0) {
|
||||
/* libmodplug never sets errno? */
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
free(contents);
|
||||
|
||||
priv = xnew(struct mod_private, 1);
|
||||
priv->file = file;
|
||||
|
||||
ModPlug_GetSettings(&settings);
|
||||
settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
|
||||
/* settings.mFlags |= MODPLUG_ENABLE_REVERB; */
|
||||
/* settings.mFlags |= MODPLUG_ENABLE_MEGABASS; */
|
||||
/* settings.mFlags |= MODPLUG_ENABLE_SURROUND; */
|
||||
settings.mChannels = 2;
|
||||
settings.mBits = 16;
|
||||
settings.mFrequency = 44100;
|
||||
settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
|
||||
ModPlug_SetSettings(&settings);
|
||||
|
||||
ip_data->private = priv;
|
||||
ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
channel_map_init_stereo(ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
|
||||
ModPlug_Unload(priv->file);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
int rc;
|
||||
|
||||
errno = 0;
|
||||
rc = ModPlug_Read(priv->file, buffer, count);
|
||||
if (rc < 0) {
|
||||
if (errno == 0)
|
||||
return -IP_ERROR_INTERNAL;
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mod_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
int ms = (int)(offset * 1000.0 + 0.5);
|
||||
|
||||
/* void */
|
||||
ModPlug_Seek(priv->file, ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *val;
|
||||
|
||||
val = ModPlug_GetName(priv->file);
|
||||
if (val && val[0])
|
||||
comments_add_const(&c, "title", val);
|
||||
|
||||
#if MODPLUG_API_8
|
||||
val = ModPlug_GetMessage(priv->file);
|
||||
if (val && val[0])
|
||||
comments_add_const(&c, "comment", val);
|
||||
#endif
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mod_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mod_private *priv = ip_data->private;
|
||||
|
||||
return (ModPlug_GetLength(priv->file) + 500) / 1000;
|
||||
}
|
||||
|
||||
static long mod_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#if MODPLUG_API_8
|
||||
static const char *mod_type_to_string(int type)
|
||||
{
|
||||
/* from <libmodplug/sndfile.h>, which is C++ */
|
||||
switch (type) {
|
||||
case 0x01: return "mod";
|
||||
case 0x02: return "s3m";
|
||||
case 0x04: return "xm";
|
||||
case 0x08: return "med";
|
||||
case 0x10: return "mtm";
|
||||
case 0x20: return "it";
|
||||
case 0x40: return "699";
|
||||
case 0x80: return "ult";
|
||||
case 0x100: return "stm";
|
||||
case 0x200: return "far";
|
||||
case 0x800: return "amf";
|
||||
case 0x1000: return "ams";
|
||||
case 0x2000: return "dsm";
|
||||
case 0x4000: return "mdl";
|
||||
case 0x8000: return "okt";
|
||||
case 0x10000: return "midi";
|
||||
case 0x20000: return "dmf";
|
||||
case 0x40000: return "ptm";
|
||||
case 0x80000: return "dbm";
|
||||
case 0x100000: return "mt2";
|
||||
case 0x200000: return "amf0";
|
||||
case 0x400000: return "psm";
|
||||
case 0x80000000:return "umx";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static char *mod_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
#if MODPLUG_API_8
|
||||
struct mod_private *priv = ip_data->private;
|
||||
const char *codec;
|
||||
int type;
|
||||
|
||||
type = ModPlug_GetModuleType(priv->file);
|
||||
codec = mod_type_to_string(type);
|
||||
|
||||
return codec ? xstrdup(codec) : NULL;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static char *mod_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mod_open,
|
||||
.close = mod_close,
|
||||
.read = mod_read,
|
||||
.seek = mod_seek,
|
||||
.read_comments = mod_read_comments,
|
||||
.duration = mod_duration,
|
||||
.bitrate = mod_bitrate,
|
||||
.bitrate_current = mod_bitrate,
|
||||
.codec = mod_codec,
|
||||
.codec_profile = mod_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = {
|
||||
"mod", "s3m", "xm", "it", "669", "amf", "ams", "dbm", "dmf", "dsm",
|
||||
"far", "mdl", "med", "mtm", "okt", "ptm", "stm", "ult", "umx", "mt2",
|
||||
"psm", NULL
|
||||
};
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
699
ip/mp4.c
Normal file
699
ip/mp4.c
Normal file
@@ -0,0 +1,699 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 dnk <dnk@bjum.net>
|
||||
*
|
||||
* 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 "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../id3.h"
|
||||
#include "../file.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/mp4.h"
|
||||
#endif
|
||||
#include "../comment.h"
|
||||
#include "../utils.h"
|
||||
#include "aac.h"
|
||||
|
||||
#if USE_MPEG4IP
|
||||
#include <mp4.h>
|
||||
#else
|
||||
#include <mp4v2/mp4v2.h>
|
||||
#endif
|
||||
|
||||
#include <neaacdec.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <strings.h>
|
||||
|
||||
/* no perfect fallback, for example faac uses only 1024 samples to prime */
|
||||
#define ENCODER_DELAY_DEFAULT 2112
|
||||
|
||||
struct mp4_private {
|
||||
char *overflow_buf;
|
||||
int overflow_buf_len;
|
||||
|
||||
unsigned char channels;
|
||||
unsigned long sample_rate;
|
||||
unsigned long frame_size;
|
||||
unsigned long encoder_delay;
|
||||
unsigned long drop_samples;
|
||||
unsigned long decoded_samples;
|
||||
|
||||
NeAACDecHandle decoder; /* typedef void * */
|
||||
|
||||
struct {
|
||||
MP4FileHandle handle; /* typedef void * */
|
||||
|
||||
MP4TrackId track;
|
||||
MP4SampleId sample; /* "media sample" (AAC access unit) id */
|
||||
MP4SampleId num_samples;
|
||||
MP4Duration duration; /* audio samples, including encoder delay */
|
||||
uint32_t scale; /* sample_rate but from mp4 header */
|
||||
} mp4;
|
||||
|
||||
struct {
|
||||
unsigned long samples;
|
||||
unsigned long bytes;
|
||||
} current;
|
||||
};
|
||||
|
||||
static bool try_iTunSMPB(struct mp4_private *priv)
|
||||
{
|
||||
/* SMPB == seamless playback, older tag for gapless playback */
|
||||
MP4ItmfItemList *itmf_list = MP4ItmfGetItemsByMeaning(priv->mp4.handle, "com.apple.iTunes", "iTunSMPB");
|
||||
if (!itmf_list)
|
||||
return false;
|
||||
|
||||
MP4ItmfItem* item = itmf_list->elements;
|
||||
if (item == NULL || item->dataList.size == 0) {
|
||||
MP4ItmfItemListFree(itmf_list);
|
||||
return false;
|
||||
}
|
||||
|
||||
char *pos = item->dataList.elements[0].value;
|
||||
(void)strtol(pos, &pos, 16); // pad
|
||||
unsigned long delay = strtol(pos, &pos, 16);
|
||||
(void)strtol(pos, &pos, 16); // remainder
|
||||
unsigned long samples = strtol(pos, &pos, 16);
|
||||
priv->encoder_delay = delay;
|
||||
priv->mp4.duration = delay + samples;
|
||||
|
||||
MP4ItmfItemListFree(itmf_list);
|
||||
return true;
|
||||
}
|
||||
|
||||
static MP4TrackId mp4_get_track(MP4FileHandle *handle)
|
||||
{
|
||||
int i, num_tracks;
|
||||
const char *track_type;
|
||||
uint8_t obj_type;
|
||||
MP4TrackId id;
|
||||
|
||||
num_tracks = MP4GetNumberOfTracks(handle, NULL, 0);
|
||||
|
||||
for (i = 0; i < num_tracks; i++) {
|
||||
id = MP4FindTrackId(handle, i, NULL, 0);
|
||||
|
||||
track_type = MP4GetTrackType(handle, id);
|
||||
if (!track_type)
|
||||
continue;
|
||||
|
||||
if (!MP4_IS_AUDIO_TRACK_TYPE(track_type))
|
||||
continue;
|
||||
|
||||
/* MP4GetTrackAudioType */
|
||||
obj_type = MP4GetTrackEsdsObjectTypeId(handle, id);
|
||||
if (obj_type == MP4_INVALID_AUDIO_TYPE)
|
||||
continue;
|
||||
|
||||
if (obj_type == MP4_MPEG4_AUDIO_TYPE) {
|
||||
obj_type = MP4GetTrackAudioMpeg4Type(handle, id);
|
||||
|
||||
if (MP4_IS_MPEG4_AAC_AUDIO_TYPE(obj_type))
|
||||
return id;
|
||||
} else {
|
||||
if (MP4_IS_AAC_AUDIO_TYPE(obj_type))
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return MP4_INVALID_TRACK_ID;
|
||||
}
|
||||
|
||||
static void mp4_get_channel_map(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv = ip_data->private;
|
||||
unsigned char *aac_data = NULL;
|
||||
unsigned int aac_data_len = 0;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
int i;
|
||||
|
||||
ip_data->channel_map[0] = CHANNEL_POSITION_INVALID;
|
||||
|
||||
if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample,
|
||||
&aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0)
|
||||
return;
|
||||
|
||||
if (!aac_data)
|
||||
return;
|
||||
|
||||
NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len);
|
||||
free(aac_data);
|
||||
|
||||
/* avoid squash of first frame by starting at 1 */
|
||||
NeAACDecPostSeekReset(priv->decoder, 1);
|
||||
|
||||
if (frame_info.error != 0 || frame_info.bytesconsumed <= 0
|
||||
|| frame_info.channels > CHANNELS_MAX)
|
||||
return;
|
||||
|
||||
for (i = 0; i < frame_info.channels; i++)
|
||||
ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]);
|
||||
}
|
||||
|
||||
static void mp4_close_handle(MP4FileHandle handle)
|
||||
{
|
||||
#ifdef MP4_CLOSE_DO_NOT_COMPUTE_BITRATE
|
||||
MP4Close(handle, 0);
|
||||
#else
|
||||
MP4Close(handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int mp4_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
NeAACDecConfigurationPtr neaac_cfg;
|
||||
unsigned char *buf;
|
||||
unsigned int buf_size;
|
||||
int rc = -IP_ERROR_FILE_FORMAT;
|
||||
|
||||
const struct mp4_private priv_init = {
|
||||
.frame_size = 1024,
|
||||
.decoded_samples = 0,
|
||||
.decoder = NULL
|
||||
};
|
||||
|
||||
/* http://sourceforge.net/forum/message.php?msg_id=3578887 */
|
||||
if (ip_data->remote)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
/* kindly ask mp4v2 to not spam stderr */
|
||||
MP4LogSetLevel(MP4_LOG_NONE);
|
||||
|
||||
/* init private struct */
|
||||
priv = xnew(struct mp4_private, 1);
|
||||
*priv = priv_init;
|
||||
ip_data->private = priv;
|
||||
|
||||
priv->decoder = NeAACDecOpen();
|
||||
|
||||
/* set decoder config */
|
||||
neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder);
|
||||
neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
|
||||
neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */
|
||||
NeAACDecSetConfiguration(priv->decoder, neaac_cfg);
|
||||
|
||||
/* open mpeg-4 file */
|
||||
#ifdef MP4_DETAILS_ALL
|
||||
priv->mp4.handle = MP4Read(ip_data->filename, 0);
|
||||
#else
|
||||
priv->mp4.handle = MP4Read(ip_data->filename);
|
||||
#endif
|
||||
if (!priv->mp4.handle) {
|
||||
d_print("MP4Read failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* find aac audio track */
|
||||
priv->mp4.track = mp4_get_track(priv->mp4.handle);
|
||||
if (priv->mp4.track == MP4_INVALID_TRACK_ID) {
|
||||
d_print("MP4FindTrackId failed\n");
|
||||
if (MP4GetNumberOfTracks(priv->mp4.handle, MP4_AUDIO_TRACK_TYPE, 0) > 0)
|
||||
rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track);
|
||||
priv->mp4.sample = 1;
|
||||
priv->mp4.duration = MP4GetTrackDuration(priv->mp4.handle, priv->mp4.track);
|
||||
priv->mp4.scale = MP4GetTrackTimeScale(priv->mp4.handle, priv->mp4.track);
|
||||
if (priv->mp4.scale == 0) {
|
||||
rc = -IP_ERROR_INTERNAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (MP4GetTrackNumberOfEdits(priv->mp4.handle, priv->mp4.track) == 1) {
|
||||
priv->encoder_delay = MP4GetTrackEditMediaStart(priv->mp4.handle, priv->mp4.track, 1); // usually 2048
|
||||
|
||||
/* The Edit List reliably gives the original duration excluding encoder delay (but
|
||||
* typically only in ms precision). We can convert this to samples and check whether
|
||||
* the sample accurate TrackDuration includes encoder delay. By my understanding it
|
||||
* should not, but in the files with an Edit List I looked at it did.
|
||||
*
|
||||
* Could just skip the rest of this block and still be mostly compatible...
|
||||
*/
|
||||
uint32_t movie_scale = MP4GetTimeScale(priv->mp4.handle); // usually 1000
|
||||
MP4Duration movie_duration = MP4GetTrackEditDuration(priv->mp4.handle, priv->mp4.track, 1); // usually in ms
|
||||
MP4Duration orig_samples_low_prec = movie_duration * priv->mp4.scale / movie_scale;
|
||||
MP4Duration total_samples_low_prec = priv->encoder_delay + orig_samples_low_prec;
|
||||
|
||||
if (abs_delta(priv->mp4.duration, orig_samples_low_prec) < 100)
|
||||
priv->mp4.duration += priv->encoder_delay;
|
||||
else if (abs_delta(priv->mp4.duration, total_samples_low_prec) > 100)
|
||||
priv->mp4.duration = total_samples_low_prec;
|
||||
} else if (try_iTunSMPB(priv)) {
|
||||
// nothing
|
||||
} else {
|
||||
priv->encoder_delay = ENCODER_DELAY_DEFAULT;
|
||||
priv->mp4.duration += ENCODER_DELAY_DEFAULT;
|
||||
}
|
||||
priv->drop_samples = priv->encoder_delay;
|
||||
|
||||
buf = NULL;
|
||||
buf_size = 0;
|
||||
mp4AudioSpecificConfig mp4ASC = {0};
|
||||
if (!MP4GetTrackESConfiguration(priv->mp4.handle, priv->mp4.track, &buf, &buf_size)) {
|
||||
/* failed to get mpeg-4 audio config... this is ok.
|
||||
* NeAACDecInit2() will simply use default values instead.
|
||||
*/
|
||||
buf = NULL;
|
||||
buf_size = 0;
|
||||
} else if (NeAACDecAudioSpecificConfig(buf, buf_size, &mp4ASC) >= 0) {
|
||||
if (mp4ASC.frameLengthFlag == 1)
|
||||
priv->frame_size = 960;
|
||||
if (mp4ASC.sbr_present_flag == 1 || mp4ASC.forceUpSampling)
|
||||
priv->frame_size *= 2;
|
||||
}
|
||||
|
||||
/* init decoder according to mpeg-4 audio config */
|
||||
if (NeAACDecInit2(priv->decoder, buf, buf_size, &priv->sample_rate, &priv->channels) < 0) {
|
||||
free(buf);
|
||||
goto out;
|
||||
}
|
||||
|
||||
free(buf);
|
||||
|
||||
d_print("sample rate %luhz, channels %u\n", priv->sample_rate, priv->channels);
|
||||
|
||||
ip_data->sf = sf_rate(priv->sample_rate) | sf_channels(priv->channels) | sf_bits(16) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
mp4_get_channel_map(ip_data);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
if (priv->mp4.handle)
|
||||
mp4_close_handle(priv->mp4.handle);
|
||||
if (priv->decoder)
|
||||
NeAACDecClose(priv->decoder);
|
||||
free(priv);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mp4_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
if (priv->mp4.handle)
|
||||
mp4_close_handle(priv->mp4.handle);
|
||||
|
||||
if (priv->decoder)
|
||||
NeAACDecClose(priv->decoder);
|
||||
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* returns -1 on fatal errors
|
||||
* returns -2 on non-fatal errors
|
||||
* 0 on eof
|
||||
* number of bytes put in 'buffer' on success */
|
||||
static int decode_one_frame(struct input_plugin_data *ip_data, void *buffer, int count)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
unsigned char *aac_data = NULL;
|
||||
unsigned int aac_data_len = 0;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
char *sample_buf;
|
||||
int bytes;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
BUG_ON(priv->overflow_buf_len);
|
||||
|
||||
if (priv->mp4.sample > priv->mp4.num_samples)
|
||||
return 0; /* EOF */
|
||||
|
||||
if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample,
|
||||
&aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0) {
|
||||
d_print("error reading mp4 sample %d\n", priv->mp4.sample);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
priv->mp4.sample++;
|
||||
|
||||
if (!aac_data) {
|
||||
d_print("aac_data == NULL\n");
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
sample_buf = NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len);
|
||||
free(aac_data);
|
||||
|
||||
if (!sample_buf || frame_info.bytesconsumed <= 0) {
|
||||
d_print("fatal error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (frame_info.error != 0) {
|
||||
d_print("frame error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
|
||||
return -2;
|
||||
}
|
||||
|
||||
priv->current.samples += frame_info.samples;
|
||||
priv->current.bytes += frame_info.bytesconsumed;
|
||||
|
||||
/* gapless handling */
|
||||
|
||||
int frame_samples = frame_info.samples / frame_info.channels;
|
||||
priv->decoded_samples += frame_samples;
|
||||
|
||||
if (priv->drop_samples) {
|
||||
int skip = min_u(priv->drop_samples, frame_samples);
|
||||
priv->drop_samples -= skip;
|
||||
frame_samples -= skip;
|
||||
frame_info.samples = frame_samples * frame_info.channels;
|
||||
memmove(sample_buf, sample_buf + (skip * frame_info.channels * 2), frame_info.samples * 2);
|
||||
}
|
||||
if (priv->decoded_samples > priv->mp4.duration) {
|
||||
int extra_samples = (priv->decoded_samples - priv->mp4.duration) * frame_info.channels;
|
||||
if (frame_info.samples >= extra_samples)
|
||||
frame_info.samples -= extra_samples;
|
||||
}
|
||||
|
||||
if (frame_info.samples <= 0)
|
||||
return -2;
|
||||
|
||||
if (frame_info.channels != priv->channels || frame_info.samplerate != priv->sample_rate) {
|
||||
d_print("invalid channel or sample_rate count\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* 16-bit samples */
|
||||
bytes = frame_info.samples * 2;
|
||||
|
||||
if (bytes > count) {
|
||||
/* decoded too much; keep overflow. */
|
||||
priv->overflow_buf = sample_buf + count;
|
||||
priv->overflow_buf_len = bytes - count;
|
||||
memcpy(buffer, sample_buf, count);
|
||||
return count;
|
||||
} else {
|
||||
memcpy(buffer, sample_buf, bytes);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int mp4_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
int rc;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
/* use overflow from previous call (if any) */
|
||||
if (priv->overflow_buf_len > 0) {
|
||||
int len = priv->overflow_buf_len;
|
||||
|
||||
if (len > count)
|
||||
len = count;
|
||||
|
||||
memcpy(buffer, priv->overflow_buf, len);
|
||||
priv->overflow_buf += len;
|
||||
priv->overflow_buf_len -= len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
do {
|
||||
rc = decode_one_frame(ip_data, buffer, count);
|
||||
} while (rc == -2);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int mp4_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
MP4SampleId sample;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
sample = MP4GetSampleIdFromTime(priv->mp4.handle, priv->mp4.track,
|
||||
(MP4Timestamp)(offset * (double)priv->mp4.scale), 1);
|
||||
if (sample == MP4_INVALID_SAMPLE_ID)
|
||||
return -IP_ERROR_INTERNAL;
|
||||
|
||||
priv->mp4.sample = sample;
|
||||
priv->decoded_samples = (sample - 1) * priv->frame_size;
|
||||
|
||||
if (priv->decoded_samples < priv->encoder_delay)
|
||||
priv->drop_samples = priv->encoder_delay - priv->decoded_samples;
|
||||
else
|
||||
priv->drop_samples = 0;
|
||||
|
||||
NeAACDecPostSeekReset(priv->decoder, sample);
|
||||
|
||||
d_print("seeking to sample %d\n", sample);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mp4_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
#if USE_MPEG4IP
|
||||
uint16_t meta_num, meta_total;
|
||||
uint8_t val;
|
||||
char *str;
|
||||
/*uint8_t *ustr;
|
||||
uint32_t size;*/
|
||||
#else
|
||||
const MP4Tags *tags;
|
||||
MP4ItmfItemList* itmf_list;
|
||||
#endif
|
||||
GROWING_KEYVALS(c);
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
#if USE_MPEG4IP
|
||||
/* MP4GetMetadata* provides malloced pointers, and the data
|
||||
* is in UTF-8 (or at least it should be). */
|
||||
if (MP4GetMetadataArtist(priv->mp4.handle, &str))
|
||||
comments_add(&c, "artist", str);
|
||||
if (MP4GetMetadataAlbum(priv->mp4.handle, &str))
|
||||
comments_add(&c, "album", str);
|
||||
if (MP4GetMetadataName(priv->mp4.handle, &str))
|
||||
comments_add(&c, "title", str);
|
||||
if (MP4GetMetadataGenre(priv->mp4.handle, &str))
|
||||
comments_add(&c, "genre", str);
|
||||
if (MP4GetMetadataYear(priv->mp4.handle, &str))
|
||||
comments_add(&c, "date", str);
|
||||
|
||||
if (MP4GetMetadataCompilation(priv->mp4.handle, &val))
|
||||
comments_add_const(&c, "compilation", val ? "yes" : "no");
|
||||
#if 0
|
||||
if (MP4GetBytesProperty(priv->mp4.handle, "moov.udta.meta.ilst.aART.data", &ustr, &size)) {
|
||||
char *xstr;
|
||||
|
||||
/* What's this?
|
||||
* This is the result from lack of documentation.
|
||||
* It's supposed to return just a string, but it
|
||||
* returns an additional 16 bytes of junk at the
|
||||
* beginning. Could be a bug. Could be intentional.
|
||||
* Hopefully this works around it:
|
||||
*/
|
||||
if (ustr[0] == 0 && size > 16) {
|
||||
ustr += 16;
|
||||
size -= 16;
|
||||
}
|
||||
xstr = xnew(char, size + 1);
|
||||
memcpy(xstr, ustr, size);
|
||||
xstr[size] = 0;
|
||||
comments_add(&c, "albumartist", xstr);
|
||||
free(xstr);
|
||||
}
|
||||
#endif
|
||||
if (MP4GetMetadataTrack(priv->mp4.handle, &meta_num, &meta_total)) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", meta_num);
|
||||
comments_add_const(&c, "tracknumber", buf);
|
||||
}
|
||||
if (MP4GetMetadataDisk(priv->mp4.handle, &meta_num, &meta_total)) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", meta_num);
|
||||
comments_add_const(&c, "discnumber", buf);
|
||||
}
|
||||
|
||||
#else /* !USE_MPEG4IP, new interface */
|
||||
|
||||
tags = MP4TagsAlloc();
|
||||
|
||||
MP4TagsFetch(tags, priv->mp4.handle);
|
||||
|
||||
if (tags->artist)
|
||||
comments_add_const(&c, "artist", tags->artist);
|
||||
if (tags->albumArtist)
|
||||
comments_add_const(&c, "albumartist", tags->albumArtist);
|
||||
if (tags->sortArtist)
|
||||
comments_add_const(&c, "artistsort", tags->sortArtist);
|
||||
if (tags->sortAlbumArtist)
|
||||
comments_add_const(&c, "albumartistsort", tags->sortAlbumArtist);
|
||||
if (tags->sortAlbum)
|
||||
comments_add_const(&c, "albumsort", tags->sortAlbum);
|
||||
if (tags->album)
|
||||
comments_add_const(&c, "album", tags->album);
|
||||
if (tags->name)
|
||||
comments_add_const(&c, "title", tags->name);
|
||||
if (tags->genre) {
|
||||
comments_add_const(&c, "genre", tags->genre);
|
||||
} else if (tags->genreType) {
|
||||
char const *genre = id3_get_genre(*tags->genreType - 1);
|
||||
if (genre)
|
||||
comments_add_const(&c, "genre", genre);
|
||||
}
|
||||
if (tags->releaseDate && strcmp(tags->releaseDate, "0") != 0)
|
||||
comments_add_const(&c, "date", tags->releaseDate);
|
||||
if (tags->compilation)
|
||||
comments_add_const(&c, "compilation", *tags->compilation ? "yes" : "no");
|
||||
if (tags->track) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", tags->track->index);
|
||||
comments_add_const(&c, "tracknumber", buf);
|
||||
}
|
||||
if (tags->disk) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", tags->disk->index);
|
||||
comments_add_const(&c, "discnumber", buf);
|
||||
}
|
||||
if (tags->tempo) {
|
||||
char buf[6];
|
||||
snprintf(buf, 6, "%u", *tags->tempo);
|
||||
comments_add_const(&c, "bpm", buf);
|
||||
}
|
||||
|
||||
MP4TagsFree(tags);
|
||||
|
||||
itmf_list = MP4ItmfGetItemsByMeaning(priv->mp4.handle, "com.apple.iTunes", NULL);
|
||||
if (itmf_list) {
|
||||
int i;
|
||||
for (i = 0; i < itmf_list->size; i++) {
|
||||
MP4ItmfItem* item = &itmf_list->elements[i];
|
||||
if (item->dataList.size < 1)
|
||||
continue;
|
||||
if (item->dataList.size > 1)
|
||||
d_print("ignore multiple values for tag %s\n", item->name);
|
||||
else {
|
||||
MP4ItmfData* data = &item->dataList.elements[0];
|
||||
char *val = xstrndup(data->value, data->valueSize);
|
||||
comments_add(&c, item->name, val);
|
||||
}
|
||||
}
|
||||
MP4ItmfItemListFree(itmf_list);
|
||||
}
|
||||
#endif
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mp4_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
return priv->mp4.duration / priv->mp4.scale;
|
||||
}
|
||||
|
||||
static long mp4_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv = ip_data->private;
|
||||
long bitrate = MP4GetTrackBitRate(priv->mp4.handle, priv->mp4.track);
|
||||
return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long mp4_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv = ip_data->private;
|
||||
long bitrate = -1;
|
||||
if (priv->current.samples > 0) {
|
||||
priv->current.samples /= priv->channels;
|
||||
bitrate = (8 * priv->current.bytes * priv->sample_rate) / priv->current.samples;
|
||||
priv->current.samples = 0;
|
||||
priv->current.bytes = 0;
|
||||
}
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static char *mp4_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("aac");
|
||||
}
|
||||
|
||||
static const char *object_type_to_str(uint8_t obj_type)
|
||||
{
|
||||
switch (obj_type) {
|
||||
case MP4_MPEG4_AAC_MAIN_AUDIO_TYPE: return "Main";
|
||||
case MP4_MPEG4_AAC_LC_AUDIO_TYPE: return "LC";
|
||||
case MP4_MPEG4_AAC_SSR_AUDIO_TYPE: return "SSR";
|
||||
case MP4_MPEG4_AAC_LTP_AUDIO_TYPE: return "LTP";
|
||||
#ifdef MP4_MPEG4_AAC_HE_AUDIO_TYPE
|
||||
case MP4_MPEG4_AAC_HE_AUDIO_TYPE: return "HE";
|
||||
#endif
|
||||
case MP4_MPEG4_AAC_SCALABLE_AUDIO_TYPE: return "Scalable";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *mp4_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mp4_private *priv = ip_data->private;
|
||||
const char *profile;
|
||||
uint8_t obj_type;
|
||||
|
||||
obj_type = MP4GetTrackEsdsObjectTypeId(priv->mp4.handle, priv->mp4.track);
|
||||
if (obj_type == MP4_MPEG4_AUDIO_TYPE)
|
||||
obj_type = MP4GetTrackAudioMpeg4Type(priv->mp4.handle, priv->mp4.track);
|
||||
|
||||
profile = object_type_to_str(obj_type);
|
||||
|
||||
return profile ? xstrdup(profile) : NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mp4_open,
|
||||
.close = mp4_close,
|
||||
.read = mp4_read,
|
||||
.seek = mp4_seek,
|
||||
.read_comments = mp4_read_comments,
|
||||
.duration = mp4_duration,
|
||||
.bitrate = mp4_bitrate,
|
||||
.bitrate_current = mp4_current_bitrate,
|
||||
.codec = mp4_codec,
|
||||
.codec_profile = mp4_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "mp4", "m4a", "m4b", NULL };
|
||||
const char * const ip_mime_types[] = { /*"audio/mp4", "audio/mp4a-latm",*/ NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
464
ip/mpc.c
Normal file
464
ip/mpc.c
Normal file
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
|
||||
*
|
||||
* Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
|
||||
*
|
||||
* 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 "../ape.h"
|
||||
#include "../comment.h"
|
||||
#include "../file.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../read_wrapper.h"
|
||||
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/mpc.h"
|
||||
#endif
|
||||
|
||||
#if MPC_SV8
|
||||
#include <mpc/mpcdec.h>
|
||||
#define callback_t mpc_reader
|
||||
#define get_ip_data(d) (d)->data
|
||||
#else
|
||||
#include <mpcdec/mpcdec.h>
|
||||
#define MPC_FALSE FALSE
|
||||
#define MPC_TRUE TRUE
|
||||
#define callback_t void
|
||||
#define get_ip_data(d) (d)
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct mpc_private {
|
||||
#if MPC_SV8
|
||||
mpc_demux *decoder;
|
||||
#else
|
||||
mpc_decoder decoder;
|
||||
#endif
|
||||
mpc_reader reader;
|
||||
mpc_streaminfo info;
|
||||
|
||||
off_t file_size;
|
||||
|
||||
int samples_pos;
|
||||
int samples_avail;
|
||||
|
||||
/* mpcdec/mpcdec.h
|
||||
*
|
||||
* the api doc says this is pcm samples per mpc frame
|
||||
* but it's really pcm _frames_ per mpc frame
|
||||
* MPC_FRAME_LENGTH = 36 * 32 (1152)
|
||||
*
|
||||
* this is wrong, it should be 2 * MPC_FRAME_LENGTH (2304)
|
||||
* MPC_DECODER_BUFFER_LENGTH = 4 * MPC_FRAME_LENGTH (4608)
|
||||
*
|
||||
* use MPC_DECODER_BUFFER_LENGTH just to be sure it works
|
||||
*/
|
||||
MPC_SAMPLE_FORMAT samples[MPC_DECODER_BUFFER_LENGTH];
|
||||
|
||||
struct {
|
||||
unsigned long samples;
|
||||
unsigned long bits;
|
||||
} current;
|
||||
};
|
||||
|
||||
/* callbacks */
|
||||
static mpc_int32_t read_impl(callback_t *data, void *ptr, mpc_int32_t size)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
int rc;
|
||||
|
||||
rc = read_wrapper(ip_data, ptr, size);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
if (rc == 0) {
|
||||
errno = 0;
|
||||
return 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static mpc_bool_t seek_impl(callback_t *data, mpc_int32_t offset)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
|
||||
if (lseek(ip_data->fd, offset, SEEK_SET) == -1)
|
||||
return MPC_FALSE;
|
||||
return MPC_TRUE;
|
||||
}
|
||||
|
||||
static mpc_int32_t tell_impl(callback_t *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
|
||||
return lseek(ip_data->fd, 0, SEEK_CUR);
|
||||
}
|
||||
|
||||
static mpc_int32_t get_size_impl(callback_t *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
return priv->file_size;
|
||||
}
|
||||
|
||||
static mpc_bool_t canseek_impl(callback_t *data)
|
||||
{
|
||||
struct input_plugin_data *ip_data = get_ip_data(data);
|
||||
|
||||
return !ip_data->remote;
|
||||
}
|
||||
|
||||
static int mpc_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv;
|
||||
|
||||
const struct mpc_private priv_init = {
|
||||
.file_size = -1,
|
||||
/* set up an mpc_reader linked to our function implementations */
|
||||
.reader = {
|
||||
.read = read_impl,
|
||||
.seek = seek_impl,
|
||||
.tell = tell_impl,
|
||||
.get_size = get_size_impl,
|
||||
.canseek = canseek_impl,
|
||||
.data = ip_data
|
||||
}
|
||||
};
|
||||
|
||||
priv = xnew(struct mpc_private, 1);
|
||||
*priv = priv_init;
|
||||
|
||||
if (!ip_data->remote) {
|
||||
priv->file_size = lseek(ip_data->fd, 0, SEEK_END);
|
||||
lseek(ip_data->fd, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
/* must be before mpc_streaminfo_read() */
|
||||
ip_data->private = priv;
|
||||
|
||||
/* read file's streaminfo data */
|
||||
#if MPC_SV8
|
||||
priv->decoder = mpc_demux_init(&priv->reader);
|
||||
if (!priv->decoder) {
|
||||
#else
|
||||
mpc_streaminfo_init(&priv->info);
|
||||
if (mpc_streaminfo_read(&priv->info, &priv->reader) != ERROR_CODE_OK) {
|
||||
#endif
|
||||
free(priv);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
|
||||
#if MPC_SV8
|
||||
mpc_demux_get_info(priv->decoder, &priv->info);
|
||||
#else
|
||||
/* instantiate a decoder with our file reader */
|
||||
mpc_decoder_setup(&priv->decoder, &priv->reader);
|
||||
if (!mpc_decoder_initialize(&priv->decoder, &priv->info)) {
|
||||
free(priv);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
#endif
|
||||
|
||||
priv->samples_avail = 0;
|
||||
priv->samples_pos = 0;
|
||||
|
||||
ip_data->sf = sf_rate(priv->info.sample_freq) | sf_channels(priv->info.channels) |
|
||||
sf_bits(16) | sf_signed(1);
|
||||
channel_map_init_waveex(priv->info.channels, 0, ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpc_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
#if MPC_SV8
|
||||
mpc_demux_exit(priv->decoder);
|
||||
#endif
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int scale(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
const MPC_SAMPLE_FORMAT *samples;
|
||||
const int clip_min = (unsigned)-1 << (16 - 1);
|
||||
const int clip_max = (1 << (16 - 1)) - 1;
|
||||
const int float_scale = 1 << (16 - 1);
|
||||
int i, sample_count;
|
||||
|
||||
/* number of bytes to 16-bit samples */
|
||||
sample_count = count / 2;
|
||||
if (sample_count > priv->samples_avail)
|
||||
sample_count = priv->samples_avail;
|
||||
|
||||
/* scale 32-bit samples to 16-bit */
|
||||
samples = priv->samples + priv->samples_pos;
|
||||
for (i = 0; i < sample_count; i++) {
|
||||
int val;
|
||||
|
||||
val = samples[i] * float_scale;
|
||||
if (val < clip_min) {
|
||||
val = clip_min;
|
||||
} else if (val > clip_max) {
|
||||
val = clip_max;
|
||||
}
|
||||
|
||||
buffer[i * 2 + 0] = val & 0xff;
|
||||
buffer[i * 2 + 1] = val >> 8;
|
||||
}
|
||||
|
||||
priv->samples_pos += sample_count;
|
||||
priv->samples_avail -= sample_count;
|
||||
if (priv->samples_avail == 0)
|
||||
priv->samples_pos = 0;
|
||||
|
||||
/* number of 16-bit samples to bytes */
|
||||
return sample_count * 2;
|
||||
}
|
||||
|
||||
static int mpc_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
#if MPC_SV8
|
||||
mpc_status status;
|
||||
mpc_frame_info frame;
|
||||
int samples;
|
||||
|
||||
frame.buffer = priv->samples;
|
||||
|
||||
while (priv->samples_avail == 0) {
|
||||
status = mpc_demux_decode(priv->decoder, &frame);
|
||||
|
||||
if (status != MPC_STATUS_OK) {
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
if (frame.bits == -1) {
|
||||
/* EOF */
|
||||
return 0;
|
||||
}
|
||||
|
||||
samples = frame.samples;
|
||||
priv->samples_avail = samples * priv->info.channels;
|
||||
|
||||
priv->current.samples += frame.samples;
|
||||
priv->current.bits += frame.bits;
|
||||
}
|
||||
#else
|
||||
|
||||
if (priv->samples_avail == 0) {
|
||||
uint32_t acc = 0, bits = 0;
|
||||
uint32_t status = mpc_decoder_decode(&priv->decoder, priv->samples, &acc, &bits);
|
||||
|
||||
if (status == (uint32_t)(-1)) {
|
||||
/* right ret val? */
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
if (status == 0) {
|
||||
/* EOF */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* status seems to be number of _frames_
|
||||
* the api documentation is wrong
|
||||
*/
|
||||
priv->samples_avail = status * priv->info.channels;
|
||||
|
||||
priv->current.samples += status;
|
||||
priv->current.bits += bits;
|
||||
}
|
||||
#endif
|
||||
|
||||
return scale(ip_data, buffer, count);
|
||||
}
|
||||
|
||||
static int mpc_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
priv->samples_pos = 0;
|
||||
priv->samples_avail = 0;
|
||||
|
||||
#if MPC_SV8
|
||||
if (mpc_demux_seek_second(priv->decoder, offset) == MPC_STATUS_OK)
|
||||
#else
|
||||
if (mpc_decoder_seek_seconds(&priv->decoder, offset))
|
||||
#endif
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const char *gain_to_str(int gain)
|
||||
{
|
||||
static char buf[16];
|
||||
#if MPC_SV8
|
||||
float g = MPC_OLD_GAIN_REF - gain / 256.f;
|
||||
sprintf(buf, "%.2f", g);
|
||||
#else
|
||||
int b, a = gain / 100;
|
||||
|
||||
if (gain < 0) {
|
||||
b = -gain % 100;
|
||||
} else {
|
||||
b = gain % 100;
|
||||
}
|
||||
sprintf(buf, "%d.%02d", a, b);
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char *peak_to_str(unsigned int peak)
|
||||
{
|
||||
static char buf[16];
|
||||
#if MPC_SV8
|
||||
sprintf(buf, "%.5f", peak / 256.f / 100.f);
|
||||
#else
|
||||
sprintf(buf, "%d.%05d", peak / 32767, peak % 32767);
|
||||
#endif
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int mpc_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
int count, i;
|
||||
APETAG(ape);
|
||||
|
||||
count = ape_read_tags(&ape, ip_data->fd, 1);
|
||||
if (count < 0)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
char *k, *v;
|
||||
k = ape_get_comment(&ape, &v);
|
||||
if (!k)
|
||||
break;
|
||||
comments_add(&c, k, v);
|
||||
free(k);
|
||||
}
|
||||
|
||||
out:
|
||||
if (priv->info.gain_title && priv->info.peak_title) {
|
||||
comments_add_const(&c, "replaygain_track_gain", gain_to_str(priv->info.gain_title));
|
||||
comments_add_const(&c, "replaygain_track_peak", peak_to_str(priv->info.peak_title));
|
||||
}
|
||||
if (priv->info.gain_album && priv->info.peak_album) {
|
||||
comments_add_const(&c, "replaygain_album_gain", gain_to_str(priv->info.gain_album));
|
||||
comments_add_const(&c, "replaygain_album_peak", peak_to_str(priv->info.peak_album));
|
||||
}
|
||||
keyvals_terminate(&c);
|
||||
|
||||
*comments = c.keyvals;
|
||||
ape_free(&ape);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpc_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
|
||||
/* priv->info.pcm_samples seems to be number of frames
|
||||
* priv->info.frames is _not_ pcm frames
|
||||
*/
|
||||
#if MPC_SV8
|
||||
return mpc_streaminfo_get_length(&priv->info);
|
||||
#else
|
||||
return priv->info.pcm_samples / priv->info.sample_freq;
|
||||
#endif
|
||||
}
|
||||
|
||||
static long mpc_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
if (priv->info.average_bitrate)
|
||||
return (long) (priv->info.average_bitrate + 0.5);
|
||||
if (priv->info.bitrate)
|
||||
return priv->info.bitrate;
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long mpc_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
long bitrate = -1;
|
||||
if (priv->current.samples > 0) {
|
||||
bitrate = (priv->info.sample_freq * priv->current.bits) / priv->current.samples;
|
||||
priv->current.samples = 0;
|
||||
priv->current.bits = 0;
|
||||
}
|
||||
return bitrate;
|
||||
|
||||
}
|
||||
|
||||
static char *mpc_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
switch (priv->info.stream_version) {
|
||||
case 7:
|
||||
return xstrdup("mpc7");
|
||||
case 8:
|
||||
return xstrdup("mpc8");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *mpc_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct mpc_private *priv = ip_data->private;
|
||||
const char *profile = priv->info.profile_name;
|
||||
char *s = NULL;
|
||||
|
||||
if (profile && profile[0]) {
|
||||
int i;
|
||||
|
||||
/* remove single quotes */
|
||||
while (*profile == '\'')
|
||||
profile++;
|
||||
s = xstrdup(profile);
|
||||
for (i = strlen(s) - 1; s[i] == '\'' && i >= 0; i--)
|
||||
s[i] = '\0';
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = mpc_open,
|
||||
.close = mpc_close,
|
||||
.read = mpc_read,
|
||||
.seek = mpc_seek,
|
||||
.read_comments = mpc_read_comments,
|
||||
.duration = mpc_duration,
|
||||
.bitrate = mpc_bitrate,
|
||||
.bitrate_current = mpc_current_bitrate,
|
||||
.codec = mpc_codec,
|
||||
.codec_profile = mpc_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char *const ip_extensions[] = { "mpc", "mpp", "mp+", NULL };
|
||||
const char *const ip_mime_types[] = { "audio/x-musepack", NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
872
ip/nomad.c
Normal file
872
ip/nomad.c
Normal file
@@ -0,0 +1,872 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Gapless decoding added by Chun-Yu Shei <cshei AT cs.indiana.edu>
|
||||
*/
|
||||
|
||||
/*
|
||||
* Xing code copied from xmms-mad plugin.
|
||||
* Lame code copied from mpd
|
||||
*/
|
||||
|
||||
#include "nomad.h"
|
||||
#include "../id3.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../misc.h"
|
||||
|
||||
#include <mad.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define INPUT_BUFFER_SIZE (5 * 8192)
|
||||
#define SEEK_IDX_INTERVAL 15
|
||||
|
||||
/* the number of samples of silence the decoder inserts at start */
|
||||
#define DECODERDELAY 529
|
||||
|
||||
#define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')
|
||||
#define INFO_MAGIC (('I' << 24) | ('n' << 16) | ('f' << 8) | 'o')
|
||||
|
||||
struct seek_idx_entry {
|
||||
off_t offset;
|
||||
mad_timer_t timer;
|
||||
};
|
||||
|
||||
struct nomad {
|
||||
struct mad_stream stream;
|
||||
struct mad_frame frame;
|
||||
struct mad_synth synth;
|
||||
mad_timer_t timer;
|
||||
unsigned long cur_frame;
|
||||
off_t input_offset;
|
||||
/* MAD_BUFFER_GUARD zeros are required at the end of the stream to decode the last frame
|
||||
ref: http://www.mars.org/mailman/public/mad-dev/2001-May/000262.html */
|
||||
unsigned char input_buffer[INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD];
|
||||
int i;
|
||||
unsigned int has_xing : 1;
|
||||
unsigned int has_lame : 1;
|
||||
unsigned int seen_first_frame : 1;
|
||||
unsigned int readEOF : 1;
|
||||
int start_drop_frames;
|
||||
int start_drop_samples;
|
||||
int end_drop_samples;
|
||||
int end_drop_frames;
|
||||
|
||||
struct nomad_xing xing;
|
||||
struct nomad_lame lame;
|
||||
|
||||
struct {
|
||||
int size;
|
||||
struct seek_idx_entry *table;
|
||||
} seek_idx;
|
||||
|
||||
struct {
|
||||
unsigned long long int bitrate_sum;
|
||||
unsigned long nr_frames;
|
||||
} current;
|
||||
|
||||
struct nomad_info info;
|
||||
void *datasource;
|
||||
int datasource_fd;
|
||||
struct nomad_callbacks cbs;
|
||||
};
|
||||
|
||||
static inline int scale(mad_fixed_t sample)
|
||||
{
|
||||
sample += 1L << (MAD_F_FRACBITS - 16);
|
||||
if (sample >= MAD_F_ONE) {
|
||||
sample = MAD_F_ONE - 1;
|
||||
} else if (sample < -MAD_F_ONE) {
|
||||
sample = -MAD_F_ONE;
|
||||
}
|
||||
return sample >> (MAD_F_FRACBITS - 15);
|
||||
}
|
||||
|
||||
static inline double timer_to_seconds(mad_timer_t timer)
|
||||
{
|
||||
signed long ms;
|
||||
|
||||
ms = mad_timer_count(timer, MAD_UNITS_MILLISECONDS);
|
||||
return (double)ms / 1000.0;
|
||||
}
|
||||
|
||||
static int parse_lame(struct nomad *nomad, struct mad_bitptr ptr, int bitlen)
|
||||
{
|
||||
int i, adj = 0;
|
||||
unsigned int version_major, version_minor;
|
||||
float val;
|
||||
|
||||
/* Unlike the xing header, the lame tag has a fixed length. Fail if
|
||||
* not all 36 bytes (288 bits) are there. */
|
||||
if (bitlen < 288) return 0;
|
||||
|
||||
for (i = 0; i < 9; i++) nomad->lame.encoder[i] = (char)mad_bit_read(&ptr, 8);
|
||||
nomad->lame.encoder[9] = '\0';
|
||||
|
||||
/* This is technically incorrect, since the encoder might not be these.
|
||||
* But there's no other way to determine if this is a lame tag, and we
|
||||
* wouldn't want to go reading a tag that's not there. */
|
||||
if (strncmp(nomad->lame.encoder, "LAME", 4) != 0 &&
|
||||
strncmp(nomad->lame.encoder, "Lavc", 4) != 0) return 0;
|
||||
|
||||
if (sscanf(nomad->lame.encoder + 4, "%u.%u", &version_major, &version_minor) != 2)
|
||||
return 0;
|
||||
|
||||
#if defined(DEBUG_LAME)
|
||||
d_print("detected LAME or Lavc version %s\n", nomad->lame.encoder);
|
||||
#endif
|
||||
|
||||
i = mad_bit_read(&ptr, 4);
|
||||
#if defined(DEBUG_LAME)
|
||||
d_print("LAME tag revision: %d\n", i);
|
||||
#endif
|
||||
nomad->lame.vbr_method = mad_bit_read(&ptr, 4);
|
||||
|
||||
/* ReplayGain in LAME tag was added in 3.94 */
|
||||
if (version_major > 3 || (version_major == 3 && version_minor >= 94)) {
|
||||
/* lowpass */
|
||||
mad_bit_read(&ptr, 8);
|
||||
|
||||
/* The reference volume was changed from the 83dB used in the
|
||||
* ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older
|
||||
* versions, since everyone else uses 89dB instead of 83dB.
|
||||
* Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so
|
||||
* it's impossible to make the proper adjustment for 3.95.
|
||||
* Fortunately, 3.95 was only out for about a day before 3.95.1 was
|
||||
* released. -- tmz */
|
||||
if (version_major < 3 || (version_major == 3 && version_minor < 95))
|
||||
adj = 6;
|
||||
|
||||
val = mad_bit_read(&ptr, 32) / (float) (1 << 23);
|
||||
/* peak value of 0.0 means lame didn't calculate the peak at all
|
||||
* (--replaygain-fast), even silence has a value > 0.0 */
|
||||
if (val)
|
||||
nomad->lame.peak = val;
|
||||
for (i = 0; i < 2; i++) {
|
||||
int gain, gain_type;
|
||||
gain_type = replaygain_decode(mad_bit_read(&ptr, 16), &gain);
|
||||
val = gain / 10.f + adj;
|
||||
if (gain_type == 1)
|
||||
nomad->lame.trackGain = val;
|
||||
/* LAME currently doesn't store any album gain!
|
||||
else if (gain_type == 2)
|
||||
nomad->lame.albumGain = val;
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* 4 encoding flags
|
||||
* 4 ATH type
|
||||
* 8 minimal bitrate (if ABR -> specified bitrate)
|
||||
*/
|
||||
mad_bit_read(&ptr, 16);
|
||||
} else
|
||||
mad_bit_read(&ptr, 88);
|
||||
|
||||
nomad->lame.encoderDelay = mad_bit_read(&ptr, 12);
|
||||
nomad->lame.encoderPadding = mad_bit_read(&ptr, 12);
|
||||
#if defined(DEBUG_LAME)
|
||||
if (adj > 0)
|
||||
d_print("adjusted gains by %+d dB (old LAME)\n", adj);
|
||||
if (!isnan(nomad->lame.peak))
|
||||
d_print("peak: %f\n", nomad->lame.peak);
|
||||
if (!isnan(nomad->lame.trackGain))
|
||||
d_print("trackGain: %+.1f dB\n", nomad->lame.trackGain);
|
||||
if (!isnan(nomad->lame.albumGain))
|
||||
d_print("albumGain: %+.1f dB\n", nomad->lame.albumGain);
|
||||
d_print("encoderDelay: %d, encoderPadding: %d\n", nomad->lame.encoderDelay, nomad->lame.encoderPadding);
|
||||
#endif
|
||||
|
||||
mad_bit_read(&ptr, 96);
|
||||
|
||||
nomad->start_drop_frames = 1; /* XING/LAME header is an empty frame */
|
||||
nomad->start_drop_samples = nomad->lame.encoderDelay + DECODERDELAY;
|
||||
nomad->end_drop_samples = nomad->lame.encoderPadding - DECODERDELAY;
|
||||
|
||||
nomad->has_lame = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* format:
|
||||
*
|
||||
* 4 "Xing"
|
||||
* 4 flags
|
||||
* 4 frames (optional)
|
||||
* 4 bytes (optional)
|
||||
* 100 TOC (optional)
|
||||
* 4 scale (optional)
|
||||
*/
|
||||
static int xing_parse(struct nomad *nomad)
|
||||
{
|
||||
struct mad_bitptr ptr = nomad->stream.anc_ptr;
|
||||
struct mad_bitptr start = ptr;
|
||||
int oldbitlen = nomad->stream.anc_bitlen;
|
||||
int bitlen = nomad->stream.anc_bitlen;
|
||||
int bitsleft;
|
||||
unsigned xing_id;
|
||||
|
||||
nomad->has_xing = 0;
|
||||
nomad->has_lame = 0;
|
||||
if (bitlen < 64)
|
||||
return -1;
|
||||
xing_id = mad_bit_read(&ptr, 32);
|
||||
if (xing_id != XING_MAGIC && xing_id != INFO_MAGIC) {
|
||||
/*
|
||||
* Due to an unfortunate historical accident, a Xing VBR tag
|
||||
* may be misplaced in a stream with CRC protection. We check
|
||||
* for this by assuming the tag began two octets prior and the
|
||||
* high bits of the following flags field are always zero.
|
||||
*/
|
||||
if (xing_id != (((XING_MAGIC+0UL) << 16) & 0xffffffffL) &&
|
||||
xing_id != (((INFO_MAGIC+0UL) << 16) & 0xffffffffL))
|
||||
return -1;
|
||||
xing_id >>= 16;
|
||||
ptr = start;
|
||||
mad_bit_skip(&ptr, 16);
|
||||
bitlen += 16;
|
||||
}
|
||||
nomad->xing.is_info = ((xing_id & 0x0000ffffL) == (INFO_MAGIC & 0x0000ffffL));
|
||||
nomad->xing.flags = mad_bit_read(&ptr, 32);
|
||||
bitlen -= 64;
|
||||
if (nomad->xing.flags & XING_FRAMES) {
|
||||
if (bitlen < 32)
|
||||
return -1;
|
||||
nomad->xing.nr_frames = mad_bit_read(&ptr, 32);
|
||||
bitlen -= 32;
|
||||
}
|
||||
if (nomad->xing.flags & XING_BYTES) {
|
||||
if (bitlen < 32)
|
||||
return -1;
|
||||
nomad->xing.bytes = mad_bit_read(&ptr, 32);
|
||||
bitlen -= 32;
|
||||
}
|
||||
if (nomad->xing.flags & XING_TOC) {
|
||||
int i;
|
||||
|
||||
if (bitlen < 800)
|
||||
return -1;
|
||||
for (i = 0; i < 100; i++)
|
||||
nomad->xing.toc[i] = mad_bit_read(&ptr, 8);
|
||||
bitlen -= 800;
|
||||
}
|
||||
if (nomad->xing.flags & XING_SCALE) {
|
||||
if (bitlen < 32)
|
||||
return -1;
|
||||
nomad->xing.scale = mad_bit_read(&ptr, 32);
|
||||
bitlen -= 32;
|
||||
}
|
||||
|
||||
/* Make sure we consume no less than 120 bytes (960 bits) in hopes that
|
||||
* the LAME tag is found there, and not right after the Xing header */
|
||||
bitsleft = 960 - (oldbitlen - bitlen);
|
||||
if (bitsleft < 0) return -1;
|
||||
else if (bitsleft > 0) {
|
||||
mad_bit_read(&ptr, bitsleft);
|
||||
bitlen -= bitsleft;
|
||||
}
|
||||
|
||||
nomad->has_xing = 1;
|
||||
#if defined(DEBUG_XING)
|
||||
if (nomad->xing.flags & XING_FRAMES)
|
||||
d_print("frames: %d (xing)\n", nomad->xing.nr_frames);
|
||||
#endif
|
||||
|
||||
parse_lame(nomad, ptr, bitlen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* returns:
|
||||
* 0: eof
|
||||
* -1: error
|
||||
* >0: ok
|
||||
*/
|
||||
static int fill_buffer(struct nomad *nomad)
|
||||
{
|
||||
if (nomad->stream.buffer == NULL || nomad->stream.error == MAD_ERROR_BUFLEN) {
|
||||
ssize_t read_size, remaining, len;
|
||||
unsigned char *read_start;
|
||||
|
||||
if (nomad->stream.next_frame != NULL) {
|
||||
remaining = nomad->stream.bufend - nomad->stream.next_frame;
|
||||
memmove(nomad->input_buffer, nomad->stream.next_frame, remaining);
|
||||
read_start = nomad->input_buffer + remaining;
|
||||
read_size = INPUT_BUFFER_SIZE - remaining;
|
||||
} else {
|
||||
read_size = INPUT_BUFFER_SIZE;
|
||||
read_start = nomad->input_buffer;
|
||||
remaining = 0;
|
||||
}
|
||||
read_size = nomad->cbs.read(nomad->datasource, read_start, read_size);
|
||||
if (read_size == -1) {
|
||||
if (errno != EAGAIN)
|
||||
d_print("read error on bitstream (%d:%s)\n", errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (read_size == 0) {
|
||||
if (!nomad->readEOF) {
|
||||
memset(nomad->input_buffer + remaining, 0, MAD_BUFFER_GUARD);
|
||||
remaining += MAD_BUFFER_GUARD;
|
||||
d_print("hit end of stream, appended MAD_BUFFER_GUARD zeros\n");
|
||||
nomad->readEOF = 1;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
len = read_size + remaining;
|
||||
|
||||
nomad->input_offset += read_size;
|
||||
|
||||
mad_stream_buffer(&nomad->stream, nomad->input_buffer, len);
|
||||
nomad->stream.error = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void handle_lost_sync(struct nomad *nomad)
|
||||
{
|
||||
unsigned long frame;
|
||||
int size;
|
||||
|
||||
frame = nomad->cur_frame;
|
||||
if (frame == 0) {
|
||||
/* cur_frame is not set when scanning file */
|
||||
frame = nomad->info.nr_frames;
|
||||
}
|
||||
|
||||
size = id3_tag_size((const char *)nomad->stream.this_frame,
|
||||
nomad->stream.bufend - nomad->stream.this_frame);
|
||||
if (size > 0) {
|
||||
d_print("frame %ld, skipping ID3 tag (%d bytes)\n", frame, size);
|
||||
mad_stream_skip(&nomad->stream, size);
|
||||
} else {
|
||||
d_print("frame %ld\n", frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Builds a seek index as the file is decoded
|
||||
* NOTE: increases nomad->timer (current position)
|
||||
*/
|
||||
static void build_seek_index(struct nomad *nomad)
|
||||
{
|
||||
mad_timer_t timer_now = nomad->timer;
|
||||
off_t offset;
|
||||
int idx;
|
||||
|
||||
mad_timer_add(&nomad->timer, nomad->frame.header.duration);
|
||||
|
||||
if (nomad->has_xing)
|
||||
return;
|
||||
|
||||
if (nomad->timer.seconds < (nomad->seek_idx.size + 1) * SEEK_IDX_INTERVAL)
|
||||
return;
|
||||
|
||||
/* offset = ftell() */
|
||||
offset = nomad->input_offset;
|
||||
/* subtract by buffer length to get offset to start of buffer */
|
||||
offset -= (nomad->stream.bufend - nomad->input_buffer);
|
||||
/* then add offset to the current frame */
|
||||
offset += (nomad->stream.this_frame - nomad->input_buffer);
|
||||
|
||||
idx = nomad->seek_idx.size;
|
||||
|
||||
nomad->seek_idx.table = xrenew(struct seek_idx_entry, nomad->seek_idx.table, idx + 1);
|
||||
nomad->seek_idx.table[idx].offset = offset;
|
||||
nomad->seek_idx.table[idx].timer = timer_now;
|
||||
|
||||
nomad->seek_idx.size++;
|
||||
}
|
||||
|
||||
static void calc_frames_fast(struct nomad *nomad)
|
||||
{
|
||||
if (nomad->has_xing && (nomad->xing.flags & XING_FRAMES) && nomad->xing.nr_frames) {
|
||||
nomad->info.nr_frames = nomad->xing.nr_frames;
|
||||
mad_timer_multiply(&nomad->timer, nomad->info.nr_frames);
|
||||
} else {
|
||||
nomad->info.nr_frames = nomad->info.filesize /
|
||||
(nomad->stream.next_frame - nomad->stream.this_frame);
|
||||
mad_timer_multiply(&nomad->timer, nomad->info.nr_frames);
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_bitrate_fast(struct nomad *nomad)
|
||||
{
|
||||
nomad->info.vbr = nomad->has_xing ? !nomad->xing.is_info : 0;
|
||||
|
||||
if (nomad->has_lame && nomad->lame.vbr_method == 1)
|
||||
nomad->info.vbr = 0;
|
||||
|
||||
if (nomad->has_xing && (nomad->xing.flags & XING_BYTES) && nomad->xing.bytes)
|
||||
nomad->info.avg_bitrate = (nomad->xing.bytes * 8.0) / nomad->info.duration;
|
||||
else
|
||||
nomad->info.avg_bitrate = nomad->frame.header.bitrate;
|
||||
}
|
||||
|
||||
/*
|
||||
* fields
|
||||
* nomad->info.avg_bitrate and
|
||||
* nomad->info.vbr
|
||||
* are only estimated
|
||||
*/
|
||||
static int scan(struct nomad *nomad)
|
||||
{
|
||||
struct mad_header *header = &nomad->frame.header;
|
||||
|
||||
while (1) {
|
||||
int rc;
|
||||
|
||||
rc = fill_buffer(nomad);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
if (rc == 0)
|
||||
break;
|
||||
|
||||
if (mad_frame_decode(&nomad->frame, &nomad->stream) == -1) {
|
||||
if (nomad->stream.error == MAD_ERROR_BUFLEN)
|
||||
continue;
|
||||
if (!MAD_RECOVERABLE(nomad->stream.error)) {
|
||||
d_print("unrecoverable frame level error.\n");
|
||||
return -1;
|
||||
}
|
||||
if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
|
||||
handle_lost_sync(nomad);
|
||||
continue;
|
||||
}
|
||||
|
||||
build_seek_index(nomad);
|
||||
|
||||
// first valid frame
|
||||
nomad->info.sample_rate = header->samplerate;
|
||||
nomad->info.channels = MAD_NCHANNELS(header);
|
||||
nomad->info.layer = header->layer;
|
||||
nomad->info.dual_channel = header->mode == MAD_MODE_DUAL_CHANNEL;
|
||||
nomad->info.joint_stereo = header->mode == MAD_MODE_JOINT_STEREO;
|
||||
|
||||
xing_parse(nomad);
|
||||
calc_frames_fast(nomad);
|
||||
break;
|
||||
}
|
||||
if (nomad->info.nr_frames == 0) {
|
||||
d_print("error: not an mp3 file!\n");
|
||||
return -NOMAD_ERROR_FILE_FORMAT;
|
||||
}
|
||||
nomad->info.duration = timer_to_seconds(nomad->timer);
|
||||
calc_bitrate_fast(nomad);
|
||||
nomad->cur_frame = 0;
|
||||
nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET);
|
||||
nomad->input_offset = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode(struct nomad *nomad)
|
||||
{
|
||||
int rc;
|
||||
|
||||
start:
|
||||
rc = fill_buffer(nomad);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
if (rc == 0)
|
||||
return 1;
|
||||
|
||||
if (mad_frame_decode(&nomad->frame, &nomad->stream)) {
|
||||
if (nomad->stream.error == MAD_ERROR_BUFLEN)
|
||||
goto start;
|
||||
if (!MAD_RECOVERABLE(nomad->stream.error)) {
|
||||
d_print("unrecoverable frame level error.\n");
|
||||
return -1;
|
||||
}
|
||||
if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
|
||||
handle_lost_sync(nomad);
|
||||
goto start;
|
||||
}
|
||||
nomad->cur_frame++;
|
||||
nomad->current.bitrate_sum += nomad->frame.header.bitrate;
|
||||
nomad->current.nr_frames++;
|
||||
if (nomad->info.filesize != -1) {
|
||||
build_seek_index(nomad);
|
||||
} else {
|
||||
mad_timer_add(&nomad->timer, nomad->frame.header.duration);
|
||||
}
|
||||
mad_synth_frame(&nomad->synth, &nomad->frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void init_mad(struct nomad *nomad)
|
||||
{
|
||||
mad_stream_init(&nomad->stream);
|
||||
nomad->stream.options |= MAD_OPTION_IGNORECRC;
|
||||
mad_frame_init(&nomad->frame);
|
||||
mad_synth_init(&nomad->synth);
|
||||
mad_timer_reset(&nomad->timer);
|
||||
nomad->cur_frame = 0;
|
||||
nomad->i = -1;
|
||||
nomad->input_offset = 0;
|
||||
nomad->seen_first_frame = 0;
|
||||
nomad->readEOF = 0;
|
||||
}
|
||||
|
||||
static void free_mad(struct nomad *nomad)
|
||||
{
|
||||
mad_stream_finish(&nomad->stream);
|
||||
mad_frame_finish(&nomad->frame);
|
||||
mad_synth_finish(nomad->synth);
|
||||
}
|
||||
|
||||
static int do_open(struct nomad *nomad)
|
||||
{
|
||||
int rc;
|
||||
|
||||
init_mad(nomad);
|
||||
nomad->info.filesize = nomad->cbs.lseek(nomad->datasource, 0, SEEK_END);
|
||||
if (nomad->info.filesize != -1)
|
||||
nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET);
|
||||
if (nomad->info.filesize == -1) {
|
||||
rc = decode(nomad);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
if (rc == 1)
|
||||
goto eof;
|
||||
nomad->info.sample_rate = nomad->frame.header.samplerate;
|
||||
nomad->info.channels = MAD_NCHANNELS(&nomad->frame.header);
|
||||
nomad->info.layer = nomad->frame.header.layer;
|
||||
nomad->info.dual_channel = nomad->frame.header.mode == MAD_MODE_DUAL_CHANNEL;
|
||||
nomad->info.joint_stereo = nomad->frame.header.mode == MAD_MODE_JOINT_STEREO;
|
||||
|
||||
/* unknown */
|
||||
nomad->info.duration = -1.0;
|
||||
nomad->info.nr_frames = -1;
|
||||
nomad->info.vbr = -1;
|
||||
nomad->info.avg_bitrate = -1;
|
||||
} else {
|
||||
rc = scan(nomad);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
if (rc == 1)
|
||||
goto eof;
|
||||
free_mad(nomad);
|
||||
init_mad(nomad);
|
||||
}
|
||||
d_print("\n frames: %d, br: %d b/s, sr: %d Hz, ch: %d, layer: %d, joint stereo: %d\n"
|
||||
" dual channel: %d, vbr: %d, duration: %g s, xing: %d\n",
|
||||
nomad->info.nr_frames, nomad->info.avg_bitrate,
|
||||
nomad->info.sample_rate, nomad->info.channels,
|
||||
nomad->info.layer, nomad->info.joint_stereo,
|
||||
nomad->info.dual_channel, nomad->info.vbr,
|
||||
nomad->info.duration,
|
||||
nomad->has_xing);
|
||||
#if defined(DEBUG_XING)
|
||||
if (nomad->has_xing)
|
||||
d_print("xing: flags: 0x%x, frames: %d, bytes: %d, scale: %d\n",
|
||||
nomad->xing.flags,
|
||||
nomad->xing.nr_frames,
|
||||
nomad->xing.bytes,
|
||||
nomad->xing.scale);
|
||||
#endif
|
||||
return 0;
|
||||
error:
|
||||
nomad_close(nomad);
|
||||
return rc;
|
||||
eof:
|
||||
nomad_close(nomad);
|
||||
return -NOMAD_ERROR_FILE_FORMAT;
|
||||
}
|
||||
|
||||
int nomad_open_callbacks(struct nomad **nomadp, void *datasource, struct nomad_callbacks *cbs)
|
||||
{
|
||||
struct nomad *nomad;
|
||||
|
||||
const struct nomad nomad_init = {
|
||||
.datasource = datasource,
|
||||
.cbs = {
|
||||
.read = cbs->read,
|
||||
.lseek = cbs->lseek,
|
||||
.close = cbs->close
|
||||
}
|
||||
};
|
||||
|
||||
nomad = xnew(struct nomad, 1);
|
||||
*nomad = nomad_init;
|
||||
nomad->lame.peak = nomad->lame.trackGain = nomad->lame.albumGain = strtof("NAN", NULL);
|
||||
*nomadp = nomad;
|
||||
/* on error do_open calls nomad_close */
|
||||
return do_open(nomad);
|
||||
}
|
||||
|
||||
void nomad_close(struct nomad *nomad)
|
||||
{
|
||||
free_mad(nomad);
|
||||
nomad->cbs.close(nomad->datasource);
|
||||
free(nomad->seek_idx.table);
|
||||
free(nomad);
|
||||
}
|
||||
|
||||
int nomad_read(struct nomad *nomad, char *buffer, int count)
|
||||
{
|
||||
int i, j, size, psize, to;
|
||||
|
||||
if (nomad->i == -1) {
|
||||
int rc;
|
||||
|
||||
next_frame:
|
||||
rc = decode(nomad);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (rc == 1)
|
||||
return 0;
|
||||
nomad->i = 0;
|
||||
}
|
||||
|
||||
if (nomad->has_lame) {
|
||||
/* skip samples at start for gapless playback */
|
||||
if (nomad->start_drop_frames) {
|
||||
nomad->start_drop_frames--;
|
||||
/* XING header is an empty frame we want to skip */
|
||||
if (!nomad->seen_first_frame) {
|
||||
nomad->cur_frame--;
|
||||
nomad->seen_first_frame = 1;
|
||||
}
|
||||
#if defined(DEBUG_LAME)
|
||||
d_print("skipped a frame at start\n");
|
||||
#endif
|
||||
goto next_frame;
|
||||
}
|
||||
if (nomad->start_drop_samples) {
|
||||
if (nomad->start_drop_samples < nomad->synth.pcm.length) {
|
||||
nomad->i += nomad->start_drop_samples;
|
||||
nomad->start_drop_samples = 0;
|
||||
/* Take advantage of the fact that this block is only executed once per file, and
|
||||
calculate the # of samples/frames to skip at the end. Note that synth.pcm.length
|
||||
is needed for the calculation. */
|
||||
nomad->end_drop_frames = nomad->end_drop_samples / nomad->synth.pcm.length;
|
||||
nomad->end_drop_samples = nomad->end_drop_samples % nomad->synth.pcm.length;
|
||||
#if defined(DEBUG_LAME)
|
||||
d_print("skipped %d samples at start\n", nomad->i);
|
||||
d_print("will skip %d samples and %d frame(s) at end\n",
|
||||
nomad->end_drop_samples, nomad->end_drop_frames);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
nomad->start_drop_samples -= nomad->synth.pcm.length;
|
||||
#if defined(DEBUG_LAME)
|
||||
d_print("skipped %d samples at start and moving to next frame\n", nomad->synth.pcm.length);
|
||||
#endif
|
||||
goto next_frame;
|
||||
}
|
||||
}
|
||||
/* skip samples/frames at end for gapless playback */
|
||||
if (nomad->cur_frame == (nomad->xing.nr_frames + 1 - nomad->end_drop_frames)) {
|
||||
#if defined(DEBUG_LAME)
|
||||
d_print("skipped %d frame(s) at end\n", nomad->end_drop_frames);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
psize = nomad->info.channels * 16 / 8;
|
||||
size = (nomad->synth.pcm.length - nomad->i) * psize;
|
||||
|
||||
if (size > count) {
|
||||
to = nomad->i + count / psize;
|
||||
} else {
|
||||
to = nomad->synth.pcm.length;
|
||||
}
|
||||
j = 0;
|
||||
for (i = nomad->i; i < to; i++) {
|
||||
short sample;
|
||||
|
||||
/* skip samples/frames at end for gapless playback */
|
||||
if (nomad->has_lame
|
||||
&& nomad->end_drop_samples
|
||||
&& (nomad->cur_frame == (nomad->xing.nr_frames - nomad->end_drop_frames))
|
||||
&& i == (nomad->synth.pcm.length - nomad->end_drop_samples)) {
|
||||
nomad->i = -1;
|
||||
#if defined(DEBUG_LAME)
|
||||
d_print("skipped %d samples at end of frame %d\n", nomad->end_drop_samples, (int)nomad->cur_frame);
|
||||
#endif
|
||||
return j;
|
||||
}
|
||||
sample = scale(nomad->synth.pcm.samples[0][i]);
|
||||
buffer[j++] = (sample >> 0) & 0xff;
|
||||
buffer[j++] = (sample >> 8) & 0xff;
|
||||
|
||||
if (nomad->info.channels == 2) {
|
||||
sample = scale(nomad->synth.pcm.samples[1][i]);
|
||||
buffer[j++] = (sample >> 0) & 0xff;
|
||||
buffer[j++] = (sample >> 8) & 0xff;
|
||||
}
|
||||
}
|
||||
if (to != nomad->synth.pcm.length) {
|
||||
nomad->i = i;
|
||||
} else {
|
||||
nomad->i = -1;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
static int nomad_time_seek_accurate(struct nomad *nomad, double pos)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* seek to beginning of file and search frame-by-frame */
|
||||
if (nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET) == -1)
|
||||
return -1;
|
||||
|
||||
/* XING header should NOT be counted - if we're here, we know it's present */
|
||||
nomad->cur_frame = -1;
|
||||
|
||||
while (timer_to_seconds(nomad->timer) < pos) {
|
||||
rc = fill_buffer(nomad);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
if (rc == 0)
|
||||
return 1;
|
||||
|
||||
if (mad_header_decode(&nomad->frame.header, &nomad->stream)) {
|
||||
if (nomad->stream.error == MAD_ERROR_BUFLEN)
|
||||
continue;
|
||||
if (!MAD_RECOVERABLE(nomad->stream.error)) {
|
||||
d_print("unrecoverable frame level error.\n");
|
||||
return -1;
|
||||
}
|
||||
if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
|
||||
handle_lost_sync(nomad);
|
||||
continue;
|
||||
}
|
||||
nomad->cur_frame++;
|
||||
mad_timer_add(&nomad->timer, nomad->frame.header.duration);
|
||||
}
|
||||
#if defined(DEBUG_LAME)
|
||||
d_print("seeked to %g = %g\n", pos, timer_to_seconds(nomad->timer));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nomad_time_seek(struct nomad *nomad, double pos)
|
||||
{
|
||||
off_t offset = 0;
|
||||
|
||||
if (pos < 0.0 || pos > nomad->info.duration) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (nomad->info.filesize == -1) {
|
||||
errno = ESPIPE;
|
||||
return -1;
|
||||
}
|
||||
free_mad(nomad);
|
||||
init_mad(nomad);
|
||||
|
||||
/* if file has a LAME header, perform frame-accurate seek for gapless playback */
|
||||
if (nomad->has_lame) {
|
||||
return nomad_time_seek_accurate(nomad, pos);
|
||||
} else if (nomad->has_xing) {
|
||||
/* calculate seek offset */
|
||||
/* seek to truncate(pos / duration * 100) / 100 * duration */
|
||||
double k, tmp_pos;
|
||||
int ki;
|
||||
|
||||
k = pos / nomad->info.duration * 100.0;
|
||||
ki = k;
|
||||
tmp_pos = ((double)ki) / 100.0 * nomad->info.duration;
|
||||
nomad->timer.seconds = (signed int)tmp_pos;
|
||||
nomad->timer.fraction = (tmp_pos - (double)nomad->timer.seconds) * MAD_TIMER_RESOLUTION;
|
||||
#if defined(DEBUG_XING)
|
||||
d_print("seeking to %g = %g %d%%\n",
|
||||
pos,
|
||||
timer_to_seconds(nomad->timer),
|
||||
ki);
|
||||
#endif
|
||||
offset = ((unsigned long long)nomad->xing.toc[ki] * nomad->xing.bytes) / 256;
|
||||
} else if (nomad->seek_idx.size > 0) {
|
||||
int idx = (int)(pos / SEEK_IDX_INTERVAL) - 1;
|
||||
|
||||
if (idx > nomad->seek_idx.size - 1)
|
||||
idx = nomad->seek_idx.size - 1;
|
||||
|
||||
if (idx >= 0) {
|
||||
offset = nomad->seek_idx.table[idx].offset;
|
||||
nomad->timer = nomad->seek_idx.table[idx].timer;
|
||||
}
|
||||
}
|
||||
if (nomad->cbs.lseek(nomad->datasource, offset, SEEK_SET) == -1)
|
||||
return -1;
|
||||
|
||||
nomad->input_offset = offset;
|
||||
while (timer_to_seconds(nomad->timer) < pos) {
|
||||
int rc;
|
||||
|
||||
rc = fill_buffer(nomad);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
if (rc == 0)
|
||||
return 0;
|
||||
|
||||
if (mad_header_decode(&nomad->frame.header, &nomad->stream) == 0) {
|
||||
build_seek_index(nomad);
|
||||
} else {
|
||||
if (!MAD_RECOVERABLE(nomad->stream.error) && nomad->stream.error != MAD_ERROR_BUFLEN) {
|
||||
d_print("unrecoverable frame level error.\n");
|
||||
return -1;
|
||||
}
|
||||
if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
|
||||
handle_lost_sync(nomad);
|
||||
}
|
||||
}
|
||||
#if defined(DEBUG_XING)
|
||||
if (nomad->has_xing)
|
||||
d_print("seeked to %g = %g\n", pos, timer_to_seconds(nomad->timer));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct nomad_xing *nomad_xing(struct nomad *nomad)
|
||||
{
|
||||
return nomad->has_xing ? &nomad->xing : NULL;
|
||||
}
|
||||
|
||||
const struct nomad_lame *nomad_lame(struct nomad *nomad)
|
||||
{
|
||||
return nomad->has_lame ? &nomad->lame : NULL;
|
||||
}
|
||||
|
||||
const struct nomad_info *nomad_info(struct nomad *nomad)
|
||||
{
|
||||
return &nomad->info;
|
||||
}
|
||||
|
||||
long nomad_current_bitrate(struct nomad *nomad)
|
||||
{
|
||||
long bitrate = -1;
|
||||
if (nomad->current.nr_frames > 0) {
|
||||
bitrate = nomad->current.bitrate_sum / nomad->current.nr_frames;
|
||||
nomad->current.bitrate_sum = 0;
|
||||
nomad->current.nr_frames = 0;
|
||||
}
|
||||
return bitrate;
|
||||
}
|
||||
103
ip/nomad.h
Normal file
103
ip/nomad.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004 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/>.
|
||||
*/
|
||||
|
||||
#ifndef CMUS_NOMAD_H
|
||||
#define CMUS_NOMAD_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifndef __GNUC__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
/* default callbacks use read, lseek, close */
|
||||
struct nomad_callbacks {
|
||||
ssize_t (*read)(void *datasource, void *buffer, size_t count);
|
||||
off_t (*lseek)(void *datasource, off_t offset, int whence);
|
||||
int (*close)(void *datasource);
|
||||
};
|
||||
|
||||
enum {
|
||||
XING_FRAMES = 0x00000001L,
|
||||
XING_BYTES = 0x00000002L,
|
||||
XING_TOC = 0x00000004L,
|
||||
XING_SCALE = 0x00000008L
|
||||
};
|
||||
|
||||
struct nomad_xing {
|
||||
unsigned int is_info : 1;
|
||||
unsigned int flags;
|
||||
unsigned int nr_frames;
|
||||
unsigned int bytes;
|
||||
unsigned int scale;
|
||||
unsigned char toc[100];
|
||||
};
|
||||
|
||||
struct nomad_lame {
|
||||
char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */
|
||||
int vbr_method; /* VBR method */
|
||||
float peak; /* replaygain peak */
|
||||
float trackGain; /* replaygain track gain */
|
||||
float albumGain; /* replaygain album gain */
|
||||
int encoderDelay; /* # of added samples at start of mp3 */
|
||||
int encoderPadding; /* # of added samples at end of mp3 */
|
||||
};
|
||||
|
||||
/* always 16-bit signed little-endian */
|
||||
struct nomad_info {
|
||||
double duration;
|
||||
int sample_rate;
|
||||
int channels;
|
||||
int nr_frames;
|
||||
int layer;
|
||||
/* guessed */
|
||||
int vbr;
|
||||
/* guessed */
|
||||
int avg_bitrate;
|
||||
/* -1 if file not seekable */
|
||||
off_t filesize;
|
||||
unsigned int joint_stereo : 1;
|
||||
unsigned int dual_channel : 1;
|
||||
};
|
||||
|
||||
enum {
|
||||
NOMAD_ERROR_SUCCESS,
|
||||
NOMAD_ERROR_ERRNO,
|
||||
NOMAD_ERROR_FILE_FORMAT
|
||||
};
|
||||
|
||||
struct nomad;
|
||||
|
||||
/* -NOMAD_ERROR_ERRNO -NOMAD_ERROR_FILE_FORMAT */
|
||||
int nomad_open_callbacks(struct nomad **nomadp, void *datasource,
|
||||
struct nomad_callbacks *cbs);
|
||||
|
||||
void nomad_close(struct nomad *nomad);
|
||||
|
||||
/* -NOMAD_ERROR_ERRNO */
|
||||
int nomad_read(struct nomad *nomad, char *buffer, int count);
|
||||
|
||||
/* -NOMAD_ERROR_ERRNO */
|
||||
int nomad_time_seek(struct nomad *nomad, double pos);
|
||||
|
||||
const struct nomad_xing *nomad_xing(struct nomad *nomad);
|
||||
const struct nomad_lame *nomad_lame(struct nomad *nomad);
|
||||
const struct nomad_info *nomad_info(struct nomad *nomad);
|
||||
long nomad_current_bitrate(struct nomad *nomad);
|
||||
|
||||
#endif
|
||||
344
ip/opus.c
Normal file
344
ip/opus.c
Normal file
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* Copyright 2008-2014 Various Authors
|
||||
* Copyright 2012 Tuncer Ayaz
|
||||
*
|
||||
* 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 "../xmalloc.h"
|
||||
#include "../read_wrapper.h"
|
||||
#include "../debug.h"
|
||||
#include "../comment.h"
|
||||
|
||||
#include <opusfile.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define SAMPLING_RATE 48000
|
||||
#define CHANNELS 2
|
||||
|
||||
struct opus_private {
|
||||
OggOpusFile *of;
|
||||
int current_link;
|
||||
};
|
||||
|
||||
static int read_func(void *datasource, unsigned char *ptr, int size)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
return read_wrapper(ip_data, ptr, size);
|
||||
}
|
||||
|
||||
static int seek_func(void *datasource, opus_int64 offset, int whence)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
return lseek(ip_data->fd, offset, whence);
|
||||
}
|
||||
|
||||
static int close_func(void *datasource)
|
||||
{
|
||||
struct input_plugin_data *ip_data;
|
||||
int rc;
|
||||
|
||||
ip_data = datasource;
|
||||
rc = close(ip_data->fd);
|
||||
ip_data->fd = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static opus_int64 tell_func(void *datasource)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
return lseek(ip_data->fd, 0, SEEK_CUR);
|
||||
}
|
||||
|
||||
static OpusFileCallbacks callbacks = {
|
||||
.read = read_func,
|
||||
.seek = seek_func,
|
||||
.tell = tell_func,
|
||||
.close = close_func
|
||||
};
|
||||
|
||||
static int opus_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct opus_private *priv;
|
||||
int rc;
|
||||
void *source;
|
||||
|
||||
priv = xnew(struct opus_private, 1);
|
||||
priv->current_link = -1;
|
||||
priv->of = NULL;
|
||||
|
||||
source = op_fdopen(&callbacks, ip_data->fd, "r");
|
||||
if (source == NULL) {
|
||||
free(priv);
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
|
||||
priv->of = op_open_callbacks(source, &callbacks, NULL, 0, &rc);
|
||||
if (rc != 0) {
|
||||
d_print("op_open_callbacks failed: %d:%s\n", rc, strerror(rc));
|
||||
free(priv);
|
||||
/* ogg is a container format, so it is likely to contain
|
||||
* something else if it isn't opus */
|
||||
return -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
}
|
||||
ip_data->private = priv;
|
||||
|
||||
ip_data->sf = sf_rate(SAMPLING_RATE)
|
||||
| sf_channels(CHANNELS)
|
||||
| sf_bits(16)
|
||||
| sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int opus_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct opus_private *priv = ip_data->private;
|
||||
/* this closes ip_data->fd! */
|
||||
op_free(priv->of);
|
||||
ip_data->fd = -1;
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* -n
|
||||
* indicates error
|
||||
* 0
|
||||
* indicates EOF
|
||||
* n
|
||||
* indicates actual number of bytes read
|
||||
*/
|
||||
static int opus_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct opus_private *priv;
|
||||
int samples, current_link, rc;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
/* samples = number of samples read per channel */
|
||||
samples = op_read_stereo(priv->of, (void*)buffer,
|
||||
count / sizeof(opus_int16));
|
||||
if (samples < 0) {
|
||||
switch (samples) {
|
||||
case OP_HOLE:
|
||||
errno = EAGAIN;
|
||||
rc = -1;
|
||||
break;
|
||||
case OP_EREAD:
|
||||
errno = EINVAL;
|
||||
rc = -1;
|
||||
break;
|
||||
case OP_EFAULT:
|
||||
errno = EINVAL;
|
||||
rc = -1;
|
||||
break;
|
||||
case OP_EIMPL:
|
||||
rc = -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
break;
|
||||
case OP_EINVAL:
|
||||
errno = EINVAL;
|
||||
rc = -1;
|
||||
break;
|
||||
case OP_ENOTFORMAT:
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
break;
|
||||
case OP_EBADHEADER:
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
break;
|
||||
case OP_EVERSION:
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
break;
|
||||
case OP_EBADPACKET:
|
||||
errno = EINVAL;
|
||||
rc = -1;
|
||||
break;
|
||||
case OP_EBADLINK:
|
||||
errno = EINVAL;
|
||||
rc = -1;
|
||||
break;
|
||||
case OP_EBADTIMESTAMP:
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
break;
|
||||
default:
|
||||
d_print("error: %d\n", samples);
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
} else if (samples == 0) {
|
||||
/* EOF or buffer too small */
|
||||
rc = 0;
|
||||
} else {
|
||||
current_link = op_current_link(priv->of);
|
||||
if (current_link < 0) {
|
||||
d_print("error: %d\n", current_link);
|
||||
rc = -1;
|
||||
} else {
|
||||
if (ip_data->remote && current_link != priv->current_link) {
|
||||
ip_data->metadata_changed = 1;
|
||||
priv->current_link = current_link;
|
||||
}
|
||||
|
||||
/* bytes = samples * channels * sample_size */
|
||||
rc = samples * CHANNELS * sizeof(opus_int16);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int opus_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct opus_private *priv;
|
||||
int rc;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
rc = op_pcm_seek(priv->of, offset * SAMPLING_RATE);
|
||||
switch (rc) {
|
||||
case OP_ENOSEEK:
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
case OP_EINVAL:
|
||||
return -IP_ERROR_INTERNAL;
|
||||
case OP_EREAD:
|
||||
return -IP_ERROR_INTERNAL;
|
||||
case OP_EFAULT:
|
||||
return -IP_ERROR_INTERNAL;
|
||||
case OP_EBADLINK:
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int opus_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
GROWING_KEYVALS(c);
|
||||
struct opus_private *priv;
|
||||
const OpusTags *ot;
|
||||
const OpusHead *head;
|
||||
int i;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
head = op_head(priv->of, -1);
|
||||
if(head != NULL) {
|
||||
char *val = xmalloc0(12); // 11 max int digits + NULL
|
||||
|
||||
snprintf(val, 12, "%d", head->output_gain);
|
||||
keyvals_add(&c, "output_gain", val);
|
||||
}
|
||||
|
||||
ot = op_tags(priv->of, -1);
|
||||
if (ot == NULL) {
|
||||
d_print("ot == NULL\n");
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < ot->comments; i++) {
|
||||
const char *str = ot->user_comments[i];
|
||||
const char *eq = strchr(str, '=');
|
||||
char *key;
|
||||
|
||||
if (!eq) {
|
||||
d_print("invalid comment: '%s' ('=' expected)\n", str);
|
||||
continue;
|
||||
}
|
||||
|
||||
key = xstrndup(str, eq - str);
|
||||
comments_add_const(&c, key, eq + 1);
|
||||
free(key);
|
||||
}
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int opus_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct opus_private *priv;
|
||||
ogg_int64_t samples;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
samples = op_pcm_total(priv->of, -1);
|
||||
if (samples < 0)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
|
||||
return samples / SAMPLING_RATE;
|
||||
}
|
||||
|
||||
static long opus_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct opus_private *priv;
|
||||
opus_int32 bitrate;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
bitrate = op_bitrate(priv->of, -1);
|
||||
if (bitrate < 0)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
else
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static long opus_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct opus_private *priv;
|
||||
opus_int32 bitrate;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
bitrate = op_bitrate_instant(priv->of);
|
||||
if (bitrate < 0)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
else
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static char *opus_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("opus");
|
||||
}
|
||||
|
||||
static char *opus_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = opus_open,
|
||||
.close = opus_close,
|
||||
.read = opus_read,
|
||||
.seek = opus_seek,
|
||||
.read_comments = opus_read_comments,
|
||||
.duration = opus_duration,
|
||||
.bitrate = opus_bitrate,
|
||||
.bitrate_current = opus_current_bitrate,
|
||||
.codec = opus_codec,
|
||||
.codec_profile = opus_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "opus", NULL };
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
414
ip/vorbis.c
Normal file
414
ip/vorbis.c
Normal file
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-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 "../xmalloc.h"
|
||||
#include "../read_wrapper.h"
|
||||
#include "../debug.h"
|
||||
#ifdef HAVE_CONFIG
|
||||
#include "../config/tremor.h"
|
||||
#endif
|
||||
#include "../comment.h"
|
||||
|
||||
#ifdef CONFIG_TREMOR
|
||||
#include <tremor/ivorbisfile.h>
|
||||
#else
|
||||
#include <vorbis/vorbisfile.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
|
||||
struct vorbis_private {
|
||||
OggVorbis_File vf;
|
||||
int current_section;
|
||||
};
|
||||
|
||||
/* http://www.xiph.org/vorbis/doc/vorbisfile/callbacks.html */
|
||||
|
||||
static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
int rc;
|
||||
|
||||
rc = read_wrapper(ip_data, ptr, size * nmemb);
|
||||
if (rc == -1) {
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
if (rc == 0) {
|
||||
errno = 0;
|
||||
return 0;
|
||||
}
|
||||
return rc / size;
|
||||
}
|
||||
|
||||
static int seek_func(void *datasource, ogg_int64_t offset, int whence)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
|
||||
if (lseek(ip_data->fd, offset, whence) == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int close_func(void *datasource)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
int rc;
|
||||
|
||||
rc = close(ip_data->fd);
|
||||
ip_data->fd = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static long tell_func(void *datasource)
|
||||
{
|
||||
struct input_plugin_data *ip_data = datasource;
|
||||
off_t off;
|
||||
|
||||
off = lseek(ip_data->fd, 0, SEEK_CUR);
|
||||
return (off == -1) ? -1 : off;
|
||||
}
|
||||
|
||||
/*
|
||||
* typedef struct {
|
||||
* size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource);
|
||||
* int (*seek_func) (void *datasource, ogg_int64_t offset, int whence);
|
||||
* int (*close_func) (void *datasource);
|
||||
* long (*tell_func) (void *datasource);
|
||||
* } ov_callbacks;
|
||||
*/
|
||||
static ov_callbacks callbacks = {
|
||||
.read_func = read_func,
|
||||
.seek_func = seek_func,
|
||||
.close_func = close_func,
|
||||
.tell_func = tell_func
|
||||
};
|
||||
|
||||
/* http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9 */
|
||||
static void channel_map_init_vorbis(int channels, channel_position_t *map)
|
||||
{
|
||||
switch (channels) {
|
||||
case 8:
|
||||
channel_map_init_vorbis(7, map);
|
||||
map[5] = CHANNEL_POSITION_REAR_LEFT;
|
||||
map[6] = CHANNEL_POSITION_REAR_RIGHT;
|
||||
map[7] = CHANNEL_POSITION_LFE;
|
||||
break;
|
||||
case 7:
|
||||
channel_map_init_vorbis(3, map);
|
||||
map[3] = CHANNEL_POSITION_SIDE_LEFT;
|
||||
map[4] = CHANNEL_POSITION_SIDE_RIGHT;
|
||||
map[5] = CHANNEL_POSITION_REAR_CENTER;
|
||||
map[6] = CHANNEL_POSITION_LFE;
|
||||
break;
|
||||
case 6:
|
||||
map[5] = CHANNEL_POSITION_LFE;
|
||||
/* Fall through */
|
||||
case 5:
|
||||
map[3] = CHANNEL_POSITION_REAR_LEFT;
|
||||
map[4] = CHANNEL_POSITION_REAR_RIGHT;
|
||||
/* Fall through */
|
||||
case 3:
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_CENTER;
|
||||
map[2] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
break;
|
||||
case 4:
|
||||
map[2] = CHANNEL_POSITION_REAR_LEFT;
|
||||
map[3] = CHANNEL_POSITION_REAR_RIGHT;
|
||||
/* Fall through */
|
||||
case 2:
|
||||
map[0] = CHANNEL_POSITION_FRONT_LEFT;
|
||||
map[1] = CHANNEL_POSITION_FRONT_RIGHT;
|
||||
break;
|
||||
case 1:
|
||||
map[0] = CHANNEL_POSITION_MONO;
|
||||
break;
|
||||
default:
|
||||
map[0] = CHANNEL_POSITION_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int vorbis_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vorbis_private *priv;
|
||||
vorbis_info *vi;
|
||||
int rc;
|
||||
|
||||
priv = xnew(struct vorbis_private, 1);
|
||||
priv->current_section = -1;
|
||||
memset(&priv->vf, 0, sizeof(priv->vf));
|
||||
|
||||
rc = ov_open_callbacks(ip_data, &priv->vf, NULL, 0, callbacks);
|
||||
if (rc != 0) {
|
||||
d_print("ov_open failed: %d\n", rc);
|
||||
free(priv);
|
||||
/* ogg is a container format, so it is likely to contain
|
||||
* something else if it isn't vorbis */
|
||||
return -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
}
|
||||
ip_data->private = priv;
|
||||
|
||||
vi = ov_info(&priv->vf, -1);
|
||||
ip_data->sf = sf_rate(vi->rate) | sf_channels(vi->channels) | sf_bits(16) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
channel_map_init_vorbis(vi->channels, ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vorbis_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vorbis_private *priv;
|
||||
int rc;
|
||||
|
||||
priv = ip_data->private;
|
||||
/* this closes ip_data->fd! */
|
||||
rc = ov_clear(&priv->vf);
|
||||
ip_data->fd = -1;
|
||||
if (rc)
|
||||
d_print("ov_clear returned %d\n", rc);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int vorbis_endian(void)
|
||||
{
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
return 1;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* OV_HOLE
|
||||
* indicates there was an interruption in the data.
|
||||
* (one of: garbage between pages, loss of sync followed by recapture,
|
||||
* or a corrupt page)
|
||||
* OV_EBADLINK
|
||||
* indicates that an invalid stream section was supplied to libvorbisfile,
|
||||
* or the requested link is corrupt.
|
||||
* 0
|
||||
* indicates EOF
|
||||
* n
|
||||
* indicates actual number of bytes read. ov_read() will decode at most
|
||||
* one vorbis packet per invocation, so the value returned will generally
|
||||
* be less than length.
|
||||
*/
|
||||
static int vorbis_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct vorbis_private *priv;
|
||||
int rc;
|
||||
int current_section;
|
||||
|
||||
priv = ip_data->private;
|
||||
#ifdef CONFIG_TREMOR
|
||||
/* Tremor can only handle signed 16 bit data */
|
||||
rc = ov_read(&priv->vf, buffer, count, ¤t_section);
|
||||
#else
|
||||
rc = ov_read(&priv->vf, buffer, count, vorbis_endian(), 2, 1, ¤t_section);
|
||||
#endif
|
||||
|
||||
if (ip_data->remote && current_section != priv->current_section) {
|
||||
ip_data->metadata_changed = 1;
|
||||
priv->current_section = current_section;
|
||||
}
|
||||
|
||||
switch (rc) {
|
||||
case OV_HOLE:
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
case OV_EBADLINK:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
case OV_EINVAL:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
case 0:
|
||||
if (errno) {
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
/* return -IP_ERROR_INTERNAL; */
|
||||
}
|
||||
/* EOF */
|
||||
return 0;
|
||||
default:
|
||||
if (rc < 0) {
|
||||
d_print("error: %d\n", rc);
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
static int vorbis_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct vorbis_private *priv;
|
||||
int rc;
|
||||
|
||||
priv = ip_data->private;
|
||||
|
||||
#ifdef CONFIG_TREMOR
|
||||
rc = ov_time_seek(&priv->vf, offset * 1000);
|
||||
#else
|
||||
rc = ov_time_seek(&priv->vf, offset);
|
||||
#endif
|
||||
switch (rc) {
|
||||
case OV_ENOSEEK:
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
case OV_EINVAL:
|
||||
return -IP_ERROR_INTERNAL;
|
||||
case OV_EREAD:
|
||||
return -IP_ERROR_INTERNAL;
|
||||
case OV_EFAULT:
|
||||
return -IP_ERROR_INTERNAL;
|
||||
case OV_EBADLINK:
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vorbis_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
GROWING_KEYVALS(c);
|
||||
struct vorbis_private *priv;
|
||||
vorbis_comment *vc;
|
||||
int i;
|
||||
|
||||
priv = ip_data->private;
|
||||
vc = ov_comment(&priv->vf, -1);
|
||||
if (vc == NULL) {
|
||||
d_print("vc == NULL\n");
|
||||
*comments = keyvals_new(0);
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < vc->comments; i++) {
|
||||
const char *str = vc->user_comments[i];
|
||||
const char *eq = strchr(str, '=');
|
||||
char *key;
|
||||
|
||||
if (!eq) {
|
||||
d_print("invalid comment: '%s' ('=' expected)\n", str);
|
||||
continue;
|
||||
}
|
||||
|
||||
key = xstrndup(str, eq - str);
|
||||
comments_add_const(&c, key, eq + 1);
|
||||
free(key);
|
||||
}
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vorbis_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vorbis_private *priv;
|
||||
int duration;
|
||||
|
||||
priv = ip_data->private;
|
||||
duration = ov_time_total(&priv->vf, -1);
|
||||
if (duration == OV_EINVAL)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
#ifdef CONFIG_TREMOR
|
||||
duration = (duration + 500) / 1000;
|
||||
#endif
|
||||
return duration;
|
||||
}
|
||||
|
||||
static long vorbis_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vorbis_private *priv = ip_data->private;
|
||||
long bitrate = ov_bitrate(&priv->vf, -1);
|
||||
if (bitrate == OV_EINVAL || bitrate == OV_FALSE)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static long vorbis_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vorbis_private *priv = ip_data->private;
|
||||
return ov_bitrate_instant(&priv->vf);
|
||||
}
|
||||
|
||||
static char *vorbis_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("vorbis");
|
||||
}
|
||||
|
||||
static const long rate_mapping_44[2][12] = {
|
||||
{ 32000, 48000, 60000, 70000, 80000, 86000, 96000, 110000, 120000, 140000, 160000, 239920 },
|
||||
{ 45000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 499821 }
|
||||
};
|
||||
|
||||
static char *vorbis_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vorbis_private *priv = ip_data->private;
|
||||
vorbis_info *vi = ov_info(&priv->vf, -1);
|
||||
long b = vi->bitrate_nominal;
|
||||
char buf[64];
|
||||
|
||||
if (b <= 0)
|
||||
return NULL;
|
||||
|
||||
if (vi->channels > 2 || vi->rate < 44100) {
|
||||
sprintf(buf, "%ldkbps", b / 1000);
|
||||
} else {
|
||||
const long *map = rate_mapping_44[vi->channels - 1];
|
||||
float q;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 12-1; i++) {
|
||||
if (b >= map[i] && b < map[i+1])
|
||||
break;
|
||||
}
|
||||
/* This is used even if upper / lower bitrate are set
|
||||
* because it gives a good approximation. */
|
||||
q = (i - 1) + (float) (b - map[i]) / (map[i+1] - map[i]);
|
||||
sprintf(buf, "q%g", roundf(q * 100.f) / 100.f);
|
||||
}
|
||||
|
||||
return xstrdup(buf);
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = vorbis_open,
|
||||
.close = vorbis_close,
|
||||
.read = vorbis_read,
|
||||
.seek = vorbis_seek,
|
||||
.read_comments = vorbis_read_comments,
|
||||
.duration = vorbis_duration,
|
||||
.bitrate = vorbis_bitrate,
|
||||
.bitrate_current = vorbis_current_bitrate,
|
||||
.codec = vorbis_codec,
|
||||
.codec_profile = vorbis_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "ogg", "oga", "ogx", NULL };
|
||||
const char * const ip_mime_types[] = { "application/ogg", "audio/x-ogg", NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
189
ip/vtx.c
Normal file
189
ip/vtx.c
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2014 Boris Timofeev <mashin87@gmail.com>
|
||||
*
|
||||
* 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 "../comment.h"
|
||||
#include "../debug.h"
|
||||
#include "../ip.h"
|
||||
#include "../utils.h"
|
||||
#include "../xmalloc.h"
|
||||
#include <ayemu.h>
|
||||
#include <string.h>
|
||||
|
||||
struct vtx_private {
|
||||
ayemu_ay_t ay;
|
||||
ayemu_ay_reg_frame_t regs;
|
||||
ayemu_vtx_t *vtx;
|
||||
int pos;
|
||||
int left;
|
||||
};
|
||||
|
||||
static const int sample_rate = 44100;
|
||||
static const int channels = 2;
|
||||
static const int bits = 16;
|
||||
|
||||
static int vtx_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vtx_private *priv;
|
||||
priv = xnew(struct vtx_private, 1);
|
||||
ip_data->private = priv;
|
||||
|
||||
priv->vtx = ayemu_vtx_load_from_file(ip_data->filename);
|
||||
if (!priv->vtx) {
|
||||
d_print("error: failed to open file %s\n", ip_data->filename);
|
||||
free(priv);
|
||||
return -IP_ERROR_INTERNAL;
|
||||
}
|
||||
|
||||
ayemu_init(&priv->ay);
|
||||
ayemu_set_sound_format(&priv->ay, sample_rate, channels, bits);
|
||||
ayemu_set_chip_type(&priv->ay, priv->vtx->chiptype, NULL);
|
||||
ayemu_set_chip_freq(&priv->ay, priv->vtx->chipFreq);
|
||||
ayemu_set_stereo(&priv->ay, priv->vtx->stereo, NULL);
|
||||
|
||||
ip_data->sf = sf_bits(bits) | sf_rate(sample_rate) | sf_channels(channels) | sf_signed(1);
|
||||
ip_data->sf |= sf_host_endian();
|
||||
channel_map_init_stereo(ip_data->channel_map);
|
||||
|
||||
priv->pos = 0;
|
||||
priv->left = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vtx_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vtx_private *priv = ip_data->private;
|
||||
|
||||
ayemu_vtx_free(priv->vtx);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vtx_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct vtx_private *priv = ip_data->private;
|
||||
int need = count;
|
||||
int donow = 0;
|
||||
|
||||
while (need > 0) {
|
||||
if (priv->left > 0) {
|
||||
donow = min_i(need, priv->left);
|
||||
buffer = ayemu_gen_sound(&priv->ay, (char *)buffer, donow);
|
||||
priv->left -= donow;
|
||||
need -= donow;
|
||||
} else {
|
||||
if (priv->pos >= priv->vtx->frames)
|
||||
return 0;
|
||||
ayemu_vtx_getframe(priv->vtx, priv->pos++, priv->regs);
|
||||
ayemu_set_regs(&priv->ay, priv->regs);
|
||||
priv->left = (sample_rate / priv->vtx->playerFreq) * (channels * bits / 8);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int vtx_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct vtx_private *priv = ip_data->private;
|
||||
int sample = sample_rate * offset;
|
||||
int samples_per_frame = sample_rate / priv->vtx->playerFreq;
|
||||
priv->pos = sample / samples_per_frame;
|
||||
if (priv->pos >= priv->vtx->frames) {
|
||||
return 0;
|
||||
}
|
||||
ayemu_vtx_getframe(priv->vtx, priv->pos, priv->regs);
|
||||
priv->left = samples_per_frame - (sample % samples_per_frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vtx_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
|
||||
{
|
||||
struct vtx_private *priv = ip_data->private;
|
||||
GROWING_KEYVALS(c);
|
||||
const char *str;
|
||||
|
||||
str = priv->vtx->author;
|
||||
if (str && str[0])
|
||||
comments_add_const(&c, "artist", str);
|
||||
str = priv->vtx->from;
|
||||
if (str && str[0])
|
||||
comments_add_const(&c, "album", str);
|
||||
str = priv->vtx->title;
|
||||
if (str && str[0])
|
||||
comments_add_const(&c, "title", str);
|
||||
int year = priv->vtx->year;
|
||||
if (year > 0) {
|
||||
char buf[16] = {0};
|
||||
snprintf(buf, sizeof buf, "%d", year);
|
||||
comments_add_const(&c, "date", buf);
|
||||
}
|
||||
str = priv->vtx->comment;
|
||||
if (str && str[0])
|
||||
comments_add_const(&c, "comment", str);
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vtx_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct vtx_private *priv = ip_data->private;
|
||||
|
||||
return (int)(priv->vtx->frames / priv->vtx->playerFreq);
|
||||
}
|
||||
|
||||
static long vtx_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static long vtx_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static char *vtx_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("vtx");
|
||||
}
|
||||
|
||||
static char *vtx_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = vtx_open,
|
||||
.close = vtx_close,
|
||||
.read = vtx_read,
|
||||
.seek = vtx_seek,
|
||||
.read_comments = vtx_read_comments,
|
||||
.duration = vtx_duration,
|
||||
.bitrate = vtx_bitrate,
|
||||
.bitrate_current = vtx_current_bitrate,
|
||||
.codec = vtx_codec,
|
||||
.codec_profile = vtx_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = {"vtx", NULL};
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
417
ip/wav.c
Normal file
417
ip/wav.c
Normal file
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-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 "../file.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../debug.h"
|
||||
#include "../utils.h"
|
||||
#include "../comment.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define WAVE_FORMAT_PCM 0x0001U
|
||||
#define WAVE_FORMAT_EXTENSIBLE 0xfffeU
|
||||
|
||||
#define WAVE_WRONG_HEADER 1
|
||||
|
||||
struct wav_private {
|
||||
off_t pcm_start;
|
||||
unsigned int pcm_size;
|
||||
unsigned int pos;
|
||||
|
||||
/* size of one second of data */
|
||||
unsigned int sec_size;
|
||||
|
||||
unsigned int frame_size;
|
||||
};
|
||||
|
||||
static int read_chunk_header(int fd, char *name, unsigned int *size)
|
||||
{
|
||||
int rc;
|
||||
char buf[8];
|
||||
|
||||
rc = read_all(fd, buf, 8);
|
||||
if (rc == -1)
|
||||
return -IP_ERROR_ERRNO;
|
||||
if (rc != 8)
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
*size = read_le32(buf + 4);
|
||||
memmove(name, buf, 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_named_chunk_header(int fd, const char *name, unsigned int *size)
|
||||
{
|
||||
int rc;
|
||||
char buf[4];
|
||||
|
||||
rc = read_chunk_header(fd, buf, size);
|
||||
if (rc)
|
||||
return rc;
|
||||
if (memcmp(buf, name, 4))
|
||||
return WAVE_WRONG_HEADER;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int find_chunk(int fd, const char *name, unsigned int *size)
|
||||
{
|
||||
int rc;
|
||||
|
||||
do {
|
||||
rc = read_named_chunk_header(fd, name, size);
|
||||
if (rc != WAVE_WRONG_HEADER)
|
||||
return rc;
|
||||
d_print("seeking %u\n", *size);
|
||||
if (lseek(fd, *size, SEEK_CUR) == -1) {
|
||||
d_print("seek failed\n");
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int wav_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wav_private *priv;
|
||||
char buf[4];
|
||||
char *fmt;
|
||||
int rc;
|
||||
unsigned int riff_size, fmt_size;
|
||||
int save;
|
||||
|
||||
d_print("file: %s\n", ip_data->filename);
|
||||
priv = xnew(struct wav_private, 1);
|
||||
ip_data->private = priv;
|
||||
rc = read_named_chunk_header(ip_data->fd, "RIFF", &riff_size);
|
||||
if (rc == WAVE_WRONG_HEADER)
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
if (rc)
|
||||
goto error_exit;
|
||||
rc = read_all(ip_data->fd, buf, 4);
|
||||
if (rc == -1) {
|
||||
rc = -IP_ERROR_ERRNO;
|
||||
goto error_exit;
|
||||
}
|
||||
if (rc != 4 || memcmp(buf, "WAVE", 4) != 0) {
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
rc = find_chunk(ip_data->fd, "fmt ", &fmt_size);
|
||||
if (rc)
|
||||
goto error_exit;
|
||||
if (fmt_size < 16) {
|
||||
d_print("size of \"fmt \" chunk is invalid (%u)\n", fmt_size);
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto error_exit;
|
||||
}
|
||||
fmt = xnew(char, fmt_size);
|
||||
rc = read_all(ip_data->fd, fmt, fmt_size);
|
||||
if (rc == -1) {
|
||||
save = errno;
|
||||
free(fmt);
|
||||
errno = save;
|
||||
rc = -IP_ERROR_ERRNO;
|
||||
goto error_exit;
|
||||
}
|
||||
if (rc != fmt_size) {
|
||||
save = errno;
|
||||
free(fmt);
|
||||
errno = save;
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto error_exit;
|
||||
}
|
||||
{
|
||||
unsigned int format_tag, channels, rate, bits, channel_mask = 0;
|
||||
|
||||
format_tag = read_le16(fmt + 0);
|
||||
channels = read_le16(fmt + 2);
|
||||
rate = read_le32(fmt + 4);
|
||||
/* 4 bytes, bytes per second */
|
||||
/* 2 bytes, bytes per sample */
|
||||
bits = read_le16(fmt + 14);
|
||||
if (format_tag == WAVE_FORMAT_EXTENSIBLE) {
|
||||
unsigned int ext_size, valid_bits;
|
||||
if (fmt_size < 18) {
|
||||
free(fmt);
|
||||
d_print("size of \"fmt \" chunk is invalid (%u)\n", fmt_size);
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto error_exit;
|
||||
}
|
||||
ext_size = read_le16(fmt + 16);
|
||||
if (ext_size < 22) {
|
||||
free(fmt);
|
||||
d_print("size of \"fmt \" chunk extension is invalid (%u)\n", ext_size);
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto error_exit;
|
||||
}
|
||||
valid_bits = read_le16(fmt + 18);
|
||||
if (valid_bits != bits) {
|
||||
free(fmt);
|
||||
d_print("padded samples are not supported (%u != %u)\n", bits, valid_bits);
|
||||
rc = -IP_ERROR_FILE_FORMAT;
|
||||
goto error_exit;
|
||||
}
|
||||
channel_mask = read_le32(fmt + 20);
|
||||
format_tag = read_le16(fmt + 24);
|
||||
/* ignore rest of extension tag */
|
||||
}
|
||||
free(fmt);
|
||||
|
||||
if (format_tag != WAVE_FORMAT_PCM) {
|
||||
d_print("unsupported format tag %u, should be 1\n", format_tag);
|
||||
rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
|
||||
goto error_exit;
|
||||
}
|
||||
if ((bits != 8 && bits != 16 && bits != 24 && bits != 32) || channels < 1) {
|
||||
rc = -IP_ERROR_SAMPLE_FORMAT;
|
||||
goto error_exit;
|
||||
}
|
||||
ip_data->sf = sf_channels(channels) | sf_rate(rate) | sf_bits(bits) |
|
||||
sf_signed(bits > 8);
|
||||
channel_map_init_waveex(channels, channel_mask, ip_data->channel_map);
|
||||
}
|
||||
|
||||
rc = find_chunk(ip_data->fd, "data", &priv->pcm_size);
|
||||
if (rc)
|
||||
goto error_exit;
|
||||
priv->pcm_start = lseek(ip_data->fd, 0, SEEK_CUR);
|
||||
if (priv->pcm_start == -1) {
|
||||
rc = -IP_ERROR_ERRNO;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
priv->sec_size = sf_get_second_size(ip_data->sf);
|
||||
priv->frame_size = sf_get_frame_size(ip_data->sf);
|
||||
priv->pos = 0;
|
||||
|
||||
d_print("pcm start: %u\n", (unsigned int)priv->pcm_start);
|
||||
d_print("pcm size: %u\n", priv->pcm_size);
|
||||
d_print("\n");
|
||||
d_print("sr: %d, ch: %d, bits: %d, signed: %d\n", sf_get_rate(ip_data->sf),
|
||||
sf_get_channels(ip_data->sf), sf_get_bits(ip_data->sf),
|
||||
sf_get_signed(ip_data->sf));
|
||||
|
||||
/* clamp pcm_size to full frames (file might be corrupt or truncated) */
|
||||
priv->pcm_size -= priv->pcm_size % sf_get_frame_size(ip_data->sf);
|
||||
return 0;
|
||||
error_exit:
|
||||
save = errno;
|
||||
free(priv);
|
||||
errno = save;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int wav_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wav_private *priv;
|
||||
|
||||
priv = ip_data->private;
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wav_read(struct input_plugin_data *ip_data, char *buffer, int _count)
|
||||
{
|
||||
struct wav_private *priv = ip_data->private;
|
||||
unsigned int count = _count;
|
||||
int rc;
|
||||
|
||||
if (priv->pos == priv->pcm_size) {
|
||||
/* eof */
|
||||
return 0;
|
||||
}
|
||||
if (count > priv->pcm_size - priv->pos)
|
||||
count = priv->pcm_size - priv->pos;
|
||||
rc = read(ip_data->fd, buffer, count);
|
||||
if (rc == -1) {
|
||||
d_print("read error\n");
|
||||
return -IP_ERROR_ERRNO;
|
||||
}
|
||||
if (rc == 0) {
|
||||
d_print("eof\n");
|
||||
return 0;
|
||||
}
|
||||
priv->pos += rc;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int wav_seek(struct input_plugin_data *ip_data, double _offset)
|
||||
{
|
||||
struct wav_private *priv = ip_data->private;
|
||||
unsigned int offset;
|
||||
|
||||
offset = (unsigned int)(_offset * (double)priv->sec_size + 0.5);
|
||||
/* align to frame size */
|
||||
offset -= offset % priv->frame_size;
|
||||
priv->pos = offset;
|
||||
if (lseek(ip_data->fd, priv->pcm_start + offset, SEEK_SET) == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct {
|
||||
const char *old;
|
||||
const char *new;
|
||||
} key_map[] = {
|
||||
{ "IART", "artist" },
|
||||
{ "ICMT", "comment" },
|
||||
{ "ICOP", "copyright" },
|
||||
{ "ICRD", "date" },
|
||||
{ "IGNR", "genre" },
|
||||
{ "INAM", "title" },
|
||||
{ "IPRD", "album" },
|
||||
{ "IPRT", "tracknumber" },
|
||||
{ "ISFT", "software" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const char *lookup_key(const char *key)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; key_map[i].old; i++) {
|
||||
if (!strcasecmp(key, key_map[i].old))
|
||||
return key_map[i].new;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int wav_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
GROWING_KEYVALS(c);
|
||||
struct wav_private *priv;
|
||||
unsigned int size;
|
||||
char id[4+1];
|
||||
int rc = 0;
|
||||
|
||||
priv = ip_data->private;
|
||||
id[4] = '\0';
|
||||
|
||||
if (lseek(ip_data->fd, 12, SEEK_SET) == -1) {
|
||||
rc = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
rc = read_chunk_header(ip_data->fd, id, &size);
|
||||
if (rc)
|
||||
break;
|
||||
if (strcmp(id, "data") == 0) {
|
||||
rc = 0;
|
||||
break;
|
||||
} else if (strcmp(id, "LIST") == 0) {
|
||||
char buf[4];
|
||||
rc = read_all(ip_data->fd, buf, 4);
|
||||
if (rc == -1)
|
||||
break;
|
||||
if (memcmp(buf, "INFO", 4) == 0)
|
||||
continue;
|
||||
size -= 4;
|
||||
} else {
|
||||
const char *key = lookup_key(id);
|
||||
if (key) {
|
||||
char *val = xnew(char, size + 1);
|
||||
rc = read_all(ip_data->fd, val, size);
|
||||
if (rc == -1) {
|
||||
free(val);
|
||||
break;
|
||||
}
|
||||
val[rc] = '\0';
|
||||
comments_add(&c, key, val);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (lseek(ip_data->fd, size, SEEK_CUR) == -1) {
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
lseek(ip_data->fd, priv->pcm_start, SEEK_SET);
|
||||
|
||||
keyvals_terminate(&c);
|
||||
|
||||
if (rc && c.count == 0) {
|
||||
keyvals_free(c.keyvals);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wav_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wav_private *priv;
|
||||
int duration;
|
||||
|
||||
priv = ip_data->private;
|
||||
duration = priv->pcm_size / priv->sec_size;
|
||||
return duration;
|
||||
}
|
||||
|
||||
static long wav_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
sample_format_t sf = ip_data->sf;
|
||||
return sf_get_bits(sf) * sf_get_rate(sf) * sf_get_channels(sf);
|
||||
}
|
||||
|
||||
static char *wav_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
char buf[16];
|
||||
snprintf(buf, 16, "pcm_%c%u%s",
|
||||
sf_get_signed(ip_data->sf) ? 's' : 'u',
|
||||
sf_get_bits(ip_data->sf),
|
||||
sf_get_bigendian(ip_data->sf) ? "be" : "le");
|
||||
|
||||
return xstrdup(buf);
|
||||
}
|
||||
|
||||
static char *wav_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = wav_open,
|
||||
.close = wav_close,
|
||||
.read = wav_read,
|
||||
.seek = wav_seek,
|
||||
.read_comments = wav_read_comments,
|
||||
.duration = wav_duration,
|
||||
.bitrate = wav_bitrate,
|
||||
.bitrate_current = wav_bitrate,
|
||||
.codec = wav_codec,
|
||||
.codec_profile = wav_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "wav", NULL };
|
||||
const char * const ip_mime_types[] = { NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
432
ip/wavpack.c
Normal file
432
ip/wavpack.c
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2007 Johannes Weißl
|
||||
*
|
||||
* 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 "../ape.h"
|
||||
#include "../id3.h"
|
||||
#include "../xmalloc.h"
|
||||
#include "../read_wrapper.h"
|
||||
#include "../debug.h"
|
||||
#include "../buffer.h"
|
||||
#include "../comment.h"
|
||||
|
||||
#include <wavpack/wavpack.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#define WV_CHANNEL_MAX 2
|
||||
|
||||
struct wavpack_file {
|
||||
int fd;
|
||||
off_t len;
|
||||
int push_back_byte;
|
||||
};
|
||||
|
||||
struct wavpack_private {
|
||||
WavpackContext *wpc;
|
||||
int32_t samples[CHUNK_SIZE * WV_CHANNEL_MAX];
|
||||
struct wavpack_file wv_file;
|
||||
struct wavpack_file wvc_file;
|
||||
unsigned int has_wvc : 1;
|
||||
};
|
||||
|
||||
/* http://www.wavpack.com/lib_use.txt */
|
||||
|
||||
static int32_t read_bytes(void *data, void *ptr, int32_t count)
|
||||
{
|
||||
struct wavpack_file *file = data;
|
||||
int32_t rc, n = 0;
|
||||
|
||||
if (file->push_back_byte != EOF) {
|
||||
char *p = ptr;
|
||||
*p = (char) file->push_back_byte;
|
||||
ptr = p + 1;
|
||||
file->push_back_byte = EOF;
|
||||
count--;
|
||||
n++;
|
||||
}
|
||||
|
||||
rc = read(file->fd, ptr, count);
|
||||
if (rc == -1) {
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
if (rc == 0) {
|
||||
errno = 0;
|
||||
return 0;
|
||||
}
|
||||
return rc + n;
|
||||
}
|
||||
|
||||
static uint32_t get_pos(void *data)
|
||||
{
|
||||
struct wavpack_file *file = data;
|
||||
|
||||
return lseek(file->fd, 0, SEEK_CUR);
|
||||
}
|
||||
|
||||
static int set_pos_rel(void *data, int32_t delta, int mode)
|
||||
{
|
||||
struct wavpack_file *file = data;
|
||||
|
||||
if (lseek(file->fd, delta, mode) == -1)
|
||||
return -1;
|
||||
|
||||
file->push_back_byte = EOF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_pos_abs(void *data, uint32_t pos)
|
||||
{
|
||||
return set_pos_rel(data, pos, SEEK_SET);
|
||||
}
|
||||
|
||||
static int push_back_byte(void *data, int c)
|
||||
{
|
||||
struct wavpack_file *file = data;
|
||||
|
||||
if (file->push_back_byte != EOF) {
|
||||
d_print("error: only one byte push back possible!\n");
|
||||
return EOF;
|
||||
}
|
||||
file->push_back_byte = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
static uint32_t get_length(void *data)
|
||||
{
|
||||
struct wavpack_file *file = data;
|
||||
return file->len;
|
||||
}
|
||||
|
||||
static int can_seek(void *data)
|
||||
{
|
||||
struct wavpack_file *file = data;
|
||||
return file->len != -1;
|
||||
}
|
||||
|
||||
static int32_t write_bytes(void *data, void *ptr, int32_t count)
|
||||
{
|
||||
/* we shall not write any bytes */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* typedef struct {
|
||||
* int32_t (*read_bytes)(void *id, void *data, int32_t bcount);
|
||||
* uint32_t (*get_pos)(void *id);
|
||||
* int (*set_pos_abs)(void *id, uint32_t pos);
|
||||
* int (*set_pos_rel)(void *id, int32_t delta, int mode);
|
||||
* int (*push_back_byte)(void *id, int c);
|
||||
* uint32_t (*get_length)(void *id);
|
||||
* int (*can_seek)(void *id);
|
||||
*
|
||||
* // this callback is for writing edited tags only
|
||||
* int32_t (*write_bytes)(void *id, void *data, int32_t bcount);
|
||||
* } WavpackStreamReader;
|
||||
*/
|
||||
static WavpackStreamReader callbacks = {
|
||||
.read_bytes = read_bytes,
|
||||
.get_pos = get_pos,
|
||||
.set_pos_abs = set_pos_abs,
|
||||
.set_pos_rel = set_pos_rel,
|
||||
.push_back_byte = push_back_byte,
|
||||
.get_length = get_length,
|
||||
.can_seek = can_seek,
|
||||
.write_bytes = write_bytes
|
||||
};
|
||||
|
||||
static int wavpack_open(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wavpack_private *priv;
|
||||
struct stat st;
|
||||
char msg[80];
|
||||
int channel_mask = 0;
|
||||
|
||||
const struct wavpack_private priv_init = {
|
||||
.wv_file = {
|
||||
.fd = ip_data->fd,
|
||||
.push_back_byte = EOF
|
||||
}
|
||||
};
|
||||
|
||||
priv = xnew(struct wavpack_private, 1);
|
||||
*priv = priv_init;
|
||||
if (!ip_data->remote && fstat(ip_data->fd, &st) == 0) {
|
||||
char *filename_wvc;
|
||||
|
||||
priv->wv_file.len = st.st_size;
|
||||
|
||||
filename_wvc = xnew(char, strlen(ip_data->filename) + 2);
|
||||
sprintf(filename_wvc, "%sc", ip_data->filename);
|
||||
if (stat(filename_wvc, &st) == 0) {
|
||||
priv->wvc_file.fd = open(filename_wvc, O_RDONLY);
|
||||
if (priv->wvc_file.fd != -1) {
|
||||
priv->wvc_file.len = st.st_size;
|
||||
priv->wvc_file.push_back_byte = EOF;
|
||||
priv->has_wvc = 1;
|
||||
d_print("use correction file: %s\n", filename_wvc);
|
||||
}
|
||||
}
|
||||
free(filename_wvc);
|
||||
} else
|
||||
priv->wv_file.len = -1;
|
||||
ip_data->private = priv;
|
||||
|
||||
*msg = '\0';
|
||||
|
||||
priv->wpc = WavpackOpenFileInputEx(&callbacks, &priv->wv_file,
|
||||
priv->has_wvc ? &priv->wvc_file : NULL, msg,
|
||||
OPEN_NORMALIZE, 0);
|
||||
|
||||
if (!priv->wpc) {
|
||||
d_print("WavpackOpenFileInputEx failed: %s\n", msg);
|
||||
free(priv);
|
||||
return -IP_ERROR_FILE_FORMAT;
|
||||
}
|
||||
|
||||
ip_data->sf = sf_rate(WavpackGetSampleRate(priv->wpc))
|
||||
| sf_channels(WavpackGetReducedChannels(priv->wpc))
|
||||
| sf_bits(WavpackGetBitsPerSample(priv->wpc))
|
||||
| sf_signed(1);
|
||||
channel_mask = WavpackGetChannelMask(priv->wpc);
|
||||
channel_map_init_waveex(sf_get_channels(ip_data->sf), channel_mask, ip_data->channel_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wavpack_close(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wavpack_private *priv;
|
||||
|
||||
priv = ip_data->private;
|
||||
priv->wpc = WavpackCloseFile(priv->wpc);
|
||||
if (priv->has_wvc)
|
||||
close(priv->wvc_file.fd);
|
||||
free(priv);
|
||||
ip_data->private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* from wv_engine.cpp (C) 2006 by Peter Lemenkov <lemenkov@newmail.ru> */
|
||||
static char *format_samples(int bps, char *dst, int32_t *src, uint32_t count)
|
||||
{
|
||||
int32_t temp;
|
||||
|
||||
switch (bps) {
|
||||
case 1:
|
||||
while (count--)
|
||||
*dst++ = *src++ + 128;
|
||||
break;
|
||||
case 2:
|
||||
while (count--) {
|
||||
*dst++ = (char) (temp = *src++);
|
||||
*dst++ = (char) (temp >> 8);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
while (count--) {
|
||||
*dst++ = (char) (temp = *src++);
|
||||
*dst++ = (char) (temp >> 8);
|
||||
*dst++ = (char) (temp >> 16);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
while (count--) {
|
||||
*dst++ = (char) (temp = *src++);
|
||||
*dst++ = (char) (temp >> 8);
|
||||
*dst++ = (char) (temp >> 16);
|
||||
*dst++ = (char) (temp >> 24);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
static int wavpack_read(struct input_plugin_data *ip_data, char *buffer, int count)
|
||||
{
|
||||
struct wavpack_private *priv;
|
||||
int rc, bps, sample_count, channels;
|
||||
|
||||
priv = ip_data->private;
|
||||
channels = sf_get_channels(ip_data->sf);
|
||||
bps = WavpackGetBytesPerSample(priv->wpc);
|
||||
|
||||
sample_count = count / bps;
|
||||
|
||||
rc = WavpackUnpackSamples(priv->wpc, priv->samples, sample_count / channels);
|
||||
format_samples(bps, buffer, priv->samples, rc * channels);
|
||||
return rc * channels * bps;
|
||||
}
|
||||
|
||||
static int wavpack_seek(struct input_plugin_data *ip_data, double offset)
|
||||
{
|
||||
struct wavpack_private *priv = ip_data->private;
|
||||
|
||||
if (!WavpackSeekSample(priv->wpc, WavpackGetSampleRate(priv->wpc) * offset))
|
||||
return -IP_ERROR_INTERNAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wavpack_read_comments(struct input_plugin_data *ip_data,
|
||||
struct keyval **comments)
|
||||
{
|
||||
struct id3tag id3;
|
||||
APETAG(ape);
|
||||
GROWING_KEYVALS(c);
|
||||
int fd, rc, save, i;
|
||||
|
||||
fd = open(ip_data->filename, O_RDONLY);
|
||||
if (fd == -1)
|
||||
return -1;
|
||||
d_print("filename: %s\n", ip_data->filename);
|
||||
|
||||
id3_init(&id3);
|
||||
rc = id3_read_tags(&id3, fd, ID3_V1);
|
||||
save = errno;
|
||||
close(fd);
|
||||
errno = save;
|
||||
if (rc) {
|
||||
if (rc == -1) {
|
||||
d_print("error: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
d_print("corrupted tag?\n");
|
||||
goto next;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_ID3_KEYS; i++) {
|
||||
char *val = id3_get_comment(&id3, i);
|
||||
if (val)
|
||||
comments_add(&c, id3_key_names[i], val);
|
||||
}
|
||||
|
||||
next:
|
||||
id3_free(&id3);
|
||||
|
||||
rc = ape_read_tags(&ape, ip_data->fd, 1);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
char *k, *v;
|
||||
k = ape_get_comment(&ape, &v);
|
||||
if (!k)
|
||||
break;
|
||||
comments_add(&c, k, v);
|
||||
free(k);
|
||||
}
|
||||
|
||||
out:
|
||||
ape_free(&ape);
|
||||
|
||||
keyvals_terminate(&c);
|
||||
*comments = c.keyvals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wavpack_duration(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wavpack_private *priv;
|
||||
int duration;
|
||||
|
||||
priv = ip_data->private;
|
||||
duration = WavpackGetNumSamples(priv->wpc) /
|
||||
WavpackGetSampleRate(priv->wpc);
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
static long wavpack_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wavpack_private *priv = ip_data->private;
|
||||
double bitrate = WavpackGetAverageBitrate(priv->wpc, 1);
|
||||
if (!bitrate)
|
||||
return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
|
||||
return (long) (bitrate + 0.5);
|
||||
}
|
||||
|
||||
static long wavpack_current_bitrate(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wavpack_private *priv = ip_data->private;
|
||||
return WavpackGetInstantBitrate(priv->wpc);
|
||||
}
|
||||
|
||||
static char *wavpack_codec(struct input_plugin_data *ip_data)
|
||||
{
|
||||
return xstrdup("wavpack");
|
||||
}
|
||||
|
||||
static char *wavpack_codec_profile(struct input_plugin_data *ip_data)
|
||||
{
|
||||
struct wavpack_private *priv = ip_data->private;
|
||||
int m = WavpackGetMode(priv->wpc);
|
||||
char buf[32];
|
||||
|
||||
buf[0] = '\0';
|
||||
|
||||
if (m & MODE_FAST)
|
||||
strcat(buf, "fast");
|
||||
#ifdef MODE_VERY_HIGH
|
||||
else if (m & MODE_VERY_HIGH)
|
||||
strcat(buf, "very high");
|
||||
#endif
|
||||
else if (m & MODE_HIGH)
|
||||
strcat(buf, "high");
|
||||
else
|
||||
strcat(buf, "normal");
|
||||
|
||||
if (m & MODE_HYBRID)
|
||||
strcat(buf, " hybrid");
|
||||
|
||||
#ifdef MODE_XMODE
|
||||
if ((m & MODE_EXTRA) && (m & MODE_XMODE)) {
|
||||
char xmode[] = " x0";
|
||||
xmode[2] = ((m & MODE_XMODE) >> 12) + '0';
|
||||
strcat(buf, xmode);
|
||||
}
|
||||
#endif
|
||||
|
||||
return xstrdup(buf);
|
||||
}
|
||||
|
||||
const struct input_plugin_ops ip_ops = {
|
||||
.open = wavpack_open,
|
||||
.close = wavpack_close,
|
||||
.read = wavpack_read,
|
||||
.seek = wavpack_seek,
|
||||
.read_comments = wavpack_read_comments,
|
||||
.duration = wavpack_duration,
|
||||
.bitrate = wavpack_bitrate,
|
||||
.bitrate_current = wavpack_current_bitrate,
|
||||
.codec = wavpack_codec,
|
||||
.codec_profile = wavpack_codec_profile
|
||||
};
|
||||
|
||||
const int ip_priority = 50;
|
||||
const char * const ip_extensions[] = { "wv", NULL };
|
||||
const char * const ip_mime_types[] = { "audio/x-wavpack", NULL };
|
||||
const struct input_plugin_opt ip_options[] = { { NULL } };
|
||||
const unsigned ip_abi_version = IP_ABI_VERSION;
|
||||
Reference in New Issue
Block a user