0aee49bdf8
This updates the copyright header to all be the same, which is pretty much an update of where to mail request for a copy of the GPL and the years of the MPD project. This also puts all committers under 'The Music Player Project' umbrella. These entries should go individually in the AUTHORS file, for consistancy.
596 lines
13 KiB
C
596 lines
13 KiB
C
/*
|
|
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
|
* http://www.musicpd.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, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "../decoder_api.h"
|
|
#include "../path.h"
|
|
#include "../utils.h"
|
|
|
|
#include <wavpack/wavpack.h>
|
|
#include <glib.h>
|
|
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#undef G_LOG_DOMAIN
|
|
#define G_LOG_DOMAIN "wavpack"
|
|
|
|
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
|
|
#define CHUNK_SIZE 1020
|
|
|
|
#define ERRORLEN 80
|
|
|
|
static struct {
|
|
const char *name;
|
|
enum tag_type type;
|
|
} tagtypes[] = {
|
|
{ "artist", TAG_ITEM_ARTIST },
|
|
{ "album", TAG_ITEM_ALBUM },
|
|
{ "title", TAG_ITEM_TITLE },
|
|
{ "track", TAG_ITEM_TRACK },
|
|
{ "name", TAG_ITEM_NAME },
|
|
{ "genre", TAG_ITEM_GENRE },
|
|
{ "date", TAG_ITEM_DATE },
|
|
{ "composer", TAG_ITEM_COMPOSER },
|
|
{ "performer", TAG_ITEM_PERFORMER },
|
|
{ "comment", TAG_ITEM_COMMENT },
|
|
{ "disc", TAG_ITEM_DISC },
|
|
};
|
|
|
|
/** A pointer type for format converter function. */
|
|
typedef void (*format_samples_t)(
|
|
int bytes_per_sample,
|
|
void *buffer, uint32_t count
|
|
);
|
|
|
|
/*
|
|
* This function has been borrowed from the tiny player found on
|
|
* wavpack.com. Modifications were required because mpd only handles
|
|
* max 24-bit samples.
|
|
*/
|
|
static void
|
|
format_samples_int(int bytes_per_sample, void *buffer, uint32_t count)
|
|
{
|
|
int32_t *src = buffer;
|
|
|
|
switch (bytes_per_sample) {
|
|
case 1: {
|
|
uchar *dst = buffer;
|
|
/*
|
|
* The asserts like the following one are because we do the
|
|
* formatting of samples within a single buffer. The size
|
|
* of the output samples never can be greater than the size
|
|
* of the input ones. Otherwise we would have an overflow.
|
|
*/
|
|
assert_static(sizeof(*dst) <= sizeof(*src));
|
|
|
|
/* pass through and align 8-bit samples */
|
|
while (count--) {
|
|
*dst++ = *src++;
|
|
}
|
|
break;
|
|
}
|
|
case 2: {
|
|
uint16_t *dst = buffer;
|
|
assert_static(sizeof(*dst) <= sizeof(*src));
|
|
|
|
/* pass through and align 16-bit samples */
|
|
while (count--) {
|
|
*dst++ = *src++;
|
|
}
|
|
break;
|
|
}
|
|
case 3:
|
|
/* do nothing */
|
|
break;
|
|
case 4: {
|
|
uint32_t *dst = buffer;
|
|
assert_static(sizeof(*dst) <= sizeof(*src));
|
|
|
|
/* downsample to 24-bit */
|
|
while (count--) {
|
|
*dst++ = *src++ >> 8;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function converts floating point sample data to 24-bit integer.
|
|
*/
|
|
static void
|
|
format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer,
|
|
uint32_t count)
|
|
{
|
|
int32_t *dst = buffer;
|
|
float *src = buffer;
|
|
assert_static(sizeof(*dst) <= sizeof(*src));
|
|
|
|
while (count--) {
|
|
*dst++ = (int32_t)(*src++ + 0.5f);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This does the main decoding thing.
|
|
* Requires an already opened WavpackContext.
|
|
*/
|
|
static void
|
|
wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
|
|
struct replay_gain_info *replay_gain_info)
|
|
{
|
|
struct audio_format audio_format;
|
|
format_samples_t format_samples;
|
|
char chunk[CHUNK_SIZE];
|
|
int samples_requested, samples_got;
|
|
float total_time, current_time;
|
|
int bytes_per_sample, output_sample_size;
|
|
int position;
|
|
|
|
audio_format.sample_rate = WavpackGetSampleRate(wpc);
|
|
audio_format.channels = WavpackGetReducedChannels(wpc);
|
|
audio_format.bits = WavpackGetBitsPerSample(wpc);
|
|
|
|
/* round bitwidth to 8-bit units */
|
|
audio_format.bits = (audio_format.bits + 7) & (~7);
|
|
/* mpd handles max 24-bit samples */
|
|
if (audio_format.bits > 24) {
|
|
audio_format.bits = 24;
|
|
}
|
|
|
|
if (!audio_format_valid(&audio_format)) {
|
|
g_warning("Invalid audio format: %u:%u:%u\n",
|
|
audio_format.sample_rate,
|
|
audio_format.bits,
|
|
audio_format.channels);
|
|
return;
|
|
}
|
|
|
|
if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) {
|
|
format_samples = format_samples_float;
|
|
} else {
|
|
format_samples = format_samples_int;
|
|
}
|
|
|
|
total_time = WavpackGetNumSamples(wpc);
|
|
total_time /= audio_format.sample_rate;
|
|
bytes_per_sample = WavpackGetBytesPerSample(wpc);
|
|
output_sample_size = audio_format_frame_size(&audio_format);
|
|
|
|
/* wavpack gives us all kind of samples in a 32-bit space */
|
|
samples_requested = sizeof(chunk) / (4 * audio_format.channels);
|
|
|
|
decoder_initialized(decoder, &audio_format, can_seek, total_time);
|
|
|
|
position = 0;
|
|
|
|
do {
|
|
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
|
|
if (can_seek) {
|
|
int where;
|
|
|
|
where = decoder_seek_where(decoder);
|
|
where *= audio_format.sample_rate;
|
|
if (WavpackSeekSample(wpc, where)) {
|
|
position = where;
|
|
decoder_command_finished(decoder);
|
|
} else {
|
|
decoder_seek_error(decoder);
|
|
}
|
|
} else {
|
|
decoder_seek_error(decoder);
|
|
}
|
|
}
|
|
|
|
if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) {
|
|
break;
|
|
}
|
|
|
|
samples_got = WavpackUnpackSamples(
|
|
wpc, (int32_t *)chunk, samples_requested
|
|
);
|
|
if (samples_got > 0) {
|
|
int bitrate = (int)(WavpackGetInstantBitrate(wpc) /
|
|
1000 + 0.5);
|
|
position += samples_got;
|
|
current_time = position;
|
|
current_time /= audio_format.sample_rate;
|
|
|
|
format_samples(
|
|
bytes_per_sample, chunk,
|
|
samples_got * audio_format.channels
|
|
);
|
|
|
|
decoder_data(
|
|
decoder, NULL, chunk,
|
|
samples_got * output_sample_size,
|
|
current_time, bitrate,
|
|
replay_gain_info
|
|
);
|
|
}
|
|
} while (samples_got > 0);
|
|
}
|
|
|
|
/**
|
|
* Locate and parse a floating point tag. Returns true if it was
|
|
* found.
|
|
*/
|
|
static bool
|
|
wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r)
|
|
{
|
|
char buffer[64];
|
|
int ret;
|
|
|
|
ret = WavpackGetTagItem(wpc, key, buffer, sizeof(buffer));
|
|
if (ret <= 0)
|
|
return false;
|
|
|
|
*value_r = atof(buffer);
|
|
return true;
|
|
}
|
|
|
|
static struct replay_gain_info *
|
|
wavpack_replaygain(WavpackContext *wpc)
|
|
{
|
|
struct replay_gain_info *replay_gain_info;
|
|
bool found = false;
|
|
|
|
replay_gain_info = replay_gain_info_new();
|
|
|
|
found |= wavpack_tag_float(
|
|
wpc, "replaygain_track_gain",
|
|
&replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain
|
|
);
|
|
found |= wavpack_tag_float(
|
|
wpc, "replaygain_track_peak",
|
|
&replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak
|
|
);
|
|
found |= wavpack_tag_float(
|
|
wpc, "replaygain_album_gain",
|
|
&replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain
|
|
);
|
|
found |= wavpack_tag_float(
|
|
wpc, "replaygain_album_peak",
|
|
&replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak
|
|
);
|
|
|
|
if (found) {
|
|
return replay_gain_info;
|
|
}
|
|
|
|
replay_gain_info_free(replay_gain_info);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Reads metainfo from the specified file.
|
|
*/
|
|
static struct tag *
|
|
wavpack_tagdup(const char *fname)
|
|
{
|
|
WavpackContext *wpc;
|
|
struct tag *tag;
|
|
char error[ERRORLEN];
|
|
char *s;
|
|
int size, allocated_size;
|
|
|
|
wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
|
|
if (wpc == NULL) {
|
|
g_warning(
|
|
"failed to open WavPack file \"%s\": %s\n",
|
|
fname, error
|
|
);
|
|
return NULL;
|
|
}
|
|
|
|
tag = tag_new();
|
|
tag->time = WavpackGetNumSamples(wpc);
|
|
tag->time /= WavpackGetSampleRate(wpc);
|
|
|
|
allocated_size = 0;
|
|
s = NULL;
|
|
|
|
for (unsigned i = 0; i < G_N_ELEMENTS(tagtypes); ++i) {
|
|
size = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0);
|
|
if (size > 0) {
|
|
++size; /* EOS */
|
|
|
|
if (s == NULL) {
|
|
s = g_malloc(size);
|
|
allocated_size = size;
|
|
} else if (size > allocated_size) {
|
|
char *t = (char *)g_realloc(s, size);
|
|
allocated_size = size;
|
|
s = t;
|
|
}
|
|
|
|
WavpackGetTagItem(wpc, tagtypes[i].name, s, size);
|
|
tag_add_item(tag, tagtypes[i].type, s);
|
|
}
|
|
}
|
|
|
|
g_free(s);
|
|
|
|
WavpackCloseFile(wpc);
|
|
|
|
return tag;
|
|
}
|
|
|
|
/*
|
|
* mpd input_stream <=> WavpackStreamReader wrapper callbacks
|
|
*/
|
|
|
|
/* This struct is needed for per-stream last_byte storage. */
|
|
struct wavpack_input {
|
|
struct decoder *decoder;
|
|
struct input_stream *is;
|
|
/* Needed for push_back_byte() */
|
|
int last_byte;
|
|
};
|
|
|
|
/**
|
|
* Little wrapper for struct wavpack_input to cast from void *.
|
|
*/
|
|
static struct wavpack_input *
|
|
wpin(void *id)
|
|
{
|
|
assert(id);
|
|
return id;
|
|
}
|
|
|
|
static int32_t
|
|
wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
|
|
{
|
|
uint8_t *buf = (uint8_t *)data;
|
|
int32_t i = 0;
|
|
|
|
if (wpin(id)->last_byte != EOF) {
|
|
*buf++ = wpin(id)->last_byte;
|
|
wpin(id)->last_byte = EOF;
|
|
--bcount;
|
|
++i;
|
|
}
|
|
|
|
/* wavpack fails if we return a partial read, so we just wait
|
|
until the buffer is full */
|
|
while (bcount > 0) {
|
|
size_t nbytes = decoder_read(
|
|
wpin(id)->decoder, wpin(id)->is, buf, bcount
|
|
);
|
|
if (nbytes == 0) {
|
|
/* EOF, error or a decoder command */
|
|
break;
|
|
}
|
|
|
|
i += nbytes;
|
|
bcount -= nbytes;
|
|
buf += nbytes;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static uint32_t
|
|
wavpack_input_get_pos(void *id)
|
|
{
|
|
return wpin(id)->is->offset;
|
|
}
|
|
|
|
static int
|
|
wavpack_input_set_pos_abs(void *id, uint32_t pos)
|
|
{
|
|
return input_stream_seek(wpin(id)->is, pos, SEEK_SET) ? 0 : -1;
|
|
}
|
|
|
|
static int
|
|
wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
|
|
{
|
|
return input_stream_seek(wpin(id)->is, delta, mode) ? 0 : -1;
|
|
}
|
|
|
|
static int
|
|
wavpack_input_push_back_byte(void *id, int c)
|
|
{
|
|
if (wpin(id)->last_byte == EOF) {
|
|
wpin(id)->last_byte = c;
|
|
return c;
|
|
} else {
|
|
return EOF;
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
wavpack_input_get_length(void *id)
|
|
{
|
|
if (wpin(id)->is->size < 0)
|
|
return 0;
|
|
|
|
return wpin(id)->is->size;
|
|
}
|
|
|
|
static int
|
|
wavpack_input_can_seek(void *id)
|
|
{
|
|
return wpin(id)->is->seekable;
|
|
}
|
|
|
|
static WavpackStreamReader mpd_is_reader = {
|
|
.read_bytes = wavpack_input_read_bytes,
|
|
.get_pos = wavpack_input_get_pos,
|
|
.set_pos_abs = wavpack_input_set_pos_abs,
|
|
.set_pos_rel = wavpack_input_set_pos_rel,
|
|
.push_back_byte = wavpack_input_push_back_byte,
|
|
.get_length = wavpack_input_get_length,
|
|
.can_seek = wavpack_input_can_seek,
|
|
.write_bytes = NULL /* no need to write edited tags */
|
|
};
|
|
|
|
static void
|
|
wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder,
|
|
struct input_stream *is)
|
|
{
|
|
isp->decoder = decoder;
|
|
isp->is = is;
|
|
isp->last_byte = EOF;
|
|
}
|
|
|
|
static bool
|
|
wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc,
|
|
struct wavpack_input *wpi)
|
|
{
|
|
char *utf8url;
|
|
char *wvc_url = NULL;
|
|
bool ret;
|
|
char first_byte;
|
|
size_t nbytes;
|
|
|
|
/*
|
|
* As we use dc->utf8url, this function will be bad for
|
|
* single files. utf8url is not absolute file path :/
|
|
*/
|
|
utf8url = decoder_get_uri(decoder);
|
|
if (utf8url == NULL) {
|
|
return false;
|
|
}
|
|
|
|
wvc_url = g_strconcat(utf8url, "c", NULL);
|
|
g_free(utf8url);
|
|
|
|
ret = input_stream_open(is_wvc, wvc_url);
|
|
g_free(wvc_url);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* And we try to buffer in order to get know
|
|
* about a possible 404 error.
|
|
*/
|
|
nbytes = decoder_read(
|
|
decoder, is_wvc, &first_byte, sizeof(first_byte)
|
|
);
|
|
if (nbytes == 0) {
|
|
input_stream_close(is_wvc);
|
|
return false;
|
|
}
|
|
|
|
/* push it back */
|
|
wavpack_input_init(wpi, decoder, is_wvc);
|
|
wpi->last_byte = first_byte;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Decodes a stream.
|
|
*/
|
|
static void
|
|
wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
|
|
{
|
|
char error[ERRORLEN];
|
|
WavpackContext *wpc;
|
|
struct input_stream is_wvc;
|
|
int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE;
|
|
struct wavpack_input isp, isp_wvc;
|
|
bool can_seek = is->seekable;
|
|
|
|
if (wavpack_open_wvc(decoder, &is_wvc, &isp_wvc)) {
|
|
open_flags |= OPEN_WVC;
|
|
can_seek &= is_wvc.seekable;
|
|
}
|
|
|
|
if (!can_seek) {
|
|
open_flags |= OPEN_STREAMING;
|
|
}
|
|
|
|
wavpack_input_init(&isp, decoder, is);
|
|
wpc = WavpackOpenFileInputEx(
|
|
&mpd_is_reader, &isp,
|
|
open_flags & OPEN_WVC ? &isp_wvc : NULL,
|
|
error, open_flags, 23
|
|
);
|
|
|
|
if (wpc == NULL) {
|
|
g_warning("failed to open WavPack stream: %s\n", error);
|
|
return;
|
|
}
|
|
|
|
wavpack_decode(decoder, wpc, can_seek, NULL);
|
|
|
|
WavpackCloseFile(wpc);
|
|
if (open_flags & OPEN_WVC) {
|
|
input_stream_close(&is_wvc);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Decodes a file.
|
|
*/
|
|
static void
|
|
wavpack_filedecode(struct decoder *decoder, const char *fname)
|
|
{
|
|
char error[ERRORLEN];
|
|
WavpackContext *wpc;
|
|
struct replay_gain_info *replay_gain_info;
|
|
|
|
wpc = WavpackOpenFileInput(
|
|
fname, error,
|
|
OPEN_TAGS | OPEN_WVC | OPEN_2CH_MAX | OPEN_NORMALIZE, 23
|
|
);
|
|
if (wpc == NULL) {
|
|
g_warning(
|
|
"failed to open WavPack file \"%s\": %s\n",
|
|
fname, error
|
|
);
|
|
return;
|
|
}
|
|
|
|
replay_gain_info = wavpack_replaygain(wpc);
|
|
|
|
wavpack_decode(decoder, wpc, true, replay_gain_info);
|
|
|
|
if (replay_gain_info) {
|
|
replay_gain_info_free(replay_gain_info);
|
|
}
|
|
|
|
WavpackCloseFile(wpc);
|
|
}
|
|
|
|
static char const *const wavpack_suffixes[] = {
|
|
"wv",
|
|
NULL
|
|
};
|
|
|
|
static char const *const wavpack_mime_types[] = {
|
|
"audio/x-wavpack",
|
|
NULL
|
|
};
|
|
|
|
const struct decoder_plugin wavpack_plugin = {
|
|
.name = "wavpack",
|
|
.stream_decode = wavpack_streamdecode,
|
|
.file_decode = wavpack_filedecode,
|
|
.tag_dup = wavpack_tagdup,
|
|
.suffixes = wavpack_suffixes,
|
|
.mime_types = wavpack_mime_types
|
|
};
|