push
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user