renamed src/inputPlugins/ to src/decoder/

These plugins are not input plugins, they are decoder plugins.  No
CamelCase in the directory name.
This commit is contained in:
Max Kellermann
2008-10-26 11:29:25 +01:00
parent cbc71191f0
commit e11355f47d
16 changed files with 14 additions and 14 deletions

323
src/decoder/_flac_common.c Normal file
View File

@@ -0,0 +1,323 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* Common data structures and functions used by FLAC and OggFLAC
* (c) 2005 by Eric Wong <normalperson@yhbt.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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "_flac_common.h"
#include "../log.h"
#include <FLAC/format.h>
#include <FLAC/metadata.h>
void init_FlacData(FlacData * data, struct decoder * decoder,
InputStream * inStream)
{
data->time = 0;
data->position = 0;
data->bitRate = 0;
data->decoder = decoder;
data->inStream = inStream;
data->replayGainInfo = NULL;
data->tag = NULL;
}
static int flacFindVorbisCommentFloat(const FLAC__StreamMetadata * block,
const char *cmnt, float *fl)
{
int offset =
FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt);
if (offset >= 0) {
size_t pos = strlen(cmnt) + 1; /* 1 is for '=' */
int len = block->data.vorbis_comment.comments[offset].length
- pos;
if (len > 0) {
unsigned char tmp;
unsigned char *p = &(block->data.vorbis_comment.
comments[offset].entry[pos]);
tmp = p[len];
p[len] = '\0';
*fl = (float)atof((char *)p);
p[len] = tmp;
return 1;
}
}
return 0;
}
/* replaygain stuff by AliasMrJones */
static void flacParseReplayGain(const FLAC__StreamMetadata * block,
FlacData * data)
{
int found = 0;
if (data->replayGainInfo)
freeReplayGainInfo(data->replayGainInfo);
data->replayGainInfo = newReplayGainInfo();
found |= flacFindVorbisCommentFloat(block, "replaygain_album_gain",
&data->replayGainInfo->albumGain);
found |= flacFindVorbisCommentFloat(block, "replaygain_album_peak",
&data->replayGainInfo->albumPeak);
found |= flacFindVorbisCommentFloat(block, "replaygain_track_gain",
&data->replayGainInfo->trackGain);
found |= flacFindVorbisCommentFloat(block, "replaygain_track_peak",
&data->replayGainInfo->trackPeak);
if (!found) {
freeReplayGainInfo(data->replayGainInfo);
data->replayGainInfo = NULL;
}
}
/* tracknumber is used in VCs, MPD uses "track" ..., all the other
* tag names match */
static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
static unsigned int commentMatchesAddToTag(const
FLAC__StreamMetadata_VorbisComment_Entry
* entry, unsigned int itemType,
struct tag ** tag)
{
const char *str;
size_t slen;
int vlen;
switch (itemType) {
case TAG_ITEM_TRACK:
str = VORBIS_COMMENT_TRACK_KEY;
break;
case TAG_ITEM_DISC:
str = VORBIS_COMMENT_DISC_KEY;
break;
default:
str = mpdTagItemKeys[itemType];
}
slen = strlen(str);
vlen = entry->length - slen - 1;
if ((vlen > 0) && (0 == strncasecmp(str, (char *)entry->entry, slen))
&& (*(entry->entry + slen) == '=')) {
if (!*tag)
*tag = tag_new();
tag_add_item_n(*tag, itemType,
(char *)(entry->entry + slen + 1), vlen);
return 1;
}
return 0;
}
struct tag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block,
struct tag * tag)
{
unsigned int i, j;
FLAC__StreamMetadata_VorbisComment_Entry *comments;
comments = block->data.vorbis_comment.comments;
for (i = block->data.vorbis_comment.num_comments; i != 0; --i) {
for (j = TAG_NUM_OF_ITEM_TYPES; j--;) {
if (commentMatchesAddToTag(comments, j, &tag))
break;
}
comments++;
}
return tag;
}
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
FlacData * data)
{
const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info);
switch (block->type) {
case FLAC__METADATA_TYPE_STREAMINFO:
data->audio_format.bits = (int8_t)si->bits_per_sample;
data->audio_format.sample_rate = si->sample_rate;
data->audio_format.channels = (int8_t)si->channels;
data->total_time = ((float)si->total_samples) / (si->sample_rate);
break;
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
flacParseReplayGain(block, data);
default:
break;
}
}
void flac_error_common_cb(const char *plugin,
const FLAC__StreamDecoderErrorStatus status,
mpd_unused FlacData * data)
{
if (decoder_get_command(data->decoder) == DECODE_COMMAND_STOP)
return;
switch (status) {
case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
ERROR("%s lost sync\n", plugin);
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
ERROR("bad %s header\n", plugin);
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
ERROR("%s crc mismatch\n", plugin);
break;
default:
ERROR("unknown %s error\n", plugin);
}
}
static void flac_convert_stereo16(int16_t *dest,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
for (; position < end; ++position) {
*dest++ = buf[0][position];
*dest++ = buf[1][position];
}
}
static void
flac_convert_16(int16_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
/**
* Note: this function also handles 24 bit files!
*/
static void
flac_convert_32(int32_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
static void
flac_convert_8(int8_t *dest,
unsigned int num_channels,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
unsigned int c_chan;
for (; position < end; ++position)
for (c_chan = 0; c_chan < num_channels; c_chan++)
*dest++ = buf[c_chan][position];
}
static void flac_convert(unsigned char *dest,
unsigned int num_channels,
unsigned int bytes_per_sample,
const FLAC__int32 * const buf[],
unsigned int position, unsigned int end)
{
switch (bytes_per_sample) {
case 2:
if (num_channels == 2)
flac_convert_stereo16((int16_t*)dest, buf,
position, end);
else
flac_convert_16((int16_t*)dest, num_channels, buf,
position, end);
break;
case 4:
flac_convert_32((int32_t*)dest, num_channels, buf,
position, end);
break;
case 1:
flac_convert_8((int8_t*)dest, num_channels, buf,
position, end);
break;
}
}
FLAC__StreamDecoderWriteStatus
flac_common_write(FlacData *data, const FLAC__Frame * frame,
const FLAC__int32 *const buf[])
{
unsigned int c_samp;
const unsigned int num_channels = frame->header.channels;
const unsigned int bytes_per_sample =
audio_format_sample_size(&data->audio_format);
const unsigned int bytes_per_channel =
bytes_per_sample * frame->header.channels;
const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel;
unsigned int num_samples;
enum decoder_command cmd;
if (bytes_per_sample != 1 && bytes_per_sample != 2 &&
bytes_per_sample != 4)
/* exotic unsupported bit rate */
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
for (c_samp = 0; c_samp < frame->header.blocksize;
c_samp += num_samples) {
num_samples = frame->header.blocksize - c_samp;
if (num_samples > max_samples)
num_samples = max_samples;
flac_convert(data->chunk,
num_channels, bytes_per_sample, buf,
c_samp, c_samp + num_samples);
cmd = decoder_data(data->decoder, data->inStream,
1, data->chunk,
num_samples * bytes_per_channel,
data->time, data->bitRate,
data->replayGainInfo);
switch (cmd) {
case DECODE_COMMAND_NONE:
case DECODE_COMMAND_START:
break;
case DECODE_COMMAND_STOP:
return
FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
case DECODE_COMMAND_SEEK:
return
FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

168
src/decoder/_flac_common.h Normal file
View File

@@ -0,0 +1,168 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* Common data structures and functions used by FLAC and OggFLAC
* (c) 2005 by Eric Wong <normalperson@yhbt.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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _FLAC_COMMON_H
#define _FLAC_COMMON_H
#include "../decoder_api.h"
#include <FLAC/export.h>
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
# include <FLAC/seekable_stream_decoder.h>
# define flac_decoder FLAC__SeekableStreamDecoder
# define flac_new() FLAC__seekable_stream_decoder_new()
# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0)
# define flac_get_decode_position(x,y) \
FLAC__seekable_stream_decoder_get_decode_position(x,y)
# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x)
# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x)
# define flac_process_metadata(x) \
FLAC__seekable_stream_decoder_process_until_end_of_metadata(x)
# define flac_seek_absolute(x,y) \
FLAC__seekable_stream_decoder_seek_absolute(x,y)
# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x)
# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x)
# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
typedef unsigned flac_read_status_size_t;
# define flac_read_status FLAC__SeekableStreamDecoderReadStatus
# define flac_read_status_continue \
FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
# define flac_read_status_abort \
FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus
# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus
# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
# define flac_tell_status_error \
FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
# define flac_tell_status_unsupported \
FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus
# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
# define flac_length_status_error \
FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
# define flac_length_status_unsupported \
FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
# ifdef HAVE_OGGFLAC
# include <OggFLAC/seekable_stream_decoder.h>
# endif
#else /* FLAC_API_VERSION_CURRENT > 7 */
/*
* OggFLAC support is handled by our flac_plugin already, and
* thus we *can* always have it if libFLAC was compiled with it
*/
# include "_ogg_common.h"
# include <FLAC/stream_decoder.h>
# define flac_decoder FLAC__StreamDecoder
# define flac_new() FLAC__stream_decoder_new()
# define flac_init(a,b,c,d,e,f,g,h,i,j) \
(FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
== FLAC__STREAM_DECODER_INIT_STATUS_OK)
# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \
(FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \
== FLAC__STREAM_DECODER_INIT_STATUS_OK)
# define flac_get_decode_position(x,y) \
FLAC__stream_decoder_get_decode_position(x,y)
# define flac_get_state(x) FLAC__stream_decoder_get_state(x)
# define flac_process_single(x) FLAC__stream_decoder_process_single(x)
# define flac_process_metadata(x) \
FLAC__stream_decoder_process_until_end_of_metadata(x)
# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y)
# define flac_finish(x) FLAC__stream_decoder_finish(x)
# define flac_delete(x) FLAC__stream_decoder_delete(x)
# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM
typedef size_t flac_read_status_size_t;
# define flac_read_status FLAC__StreamDecoderReadStatus
# define flac_read_status_continue \
FLAC__STREAM_DECODER_READ_STATUS_CONTINUE
# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM
# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT
# define flac_seek_status FLAC__StreamDecoderSeekStatus
# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK
# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR
# define flac_seek_status_unsupported \
FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED
# define flac_tell_status FLAC__StreamDecoderTellStatus
# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK
# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR
# define flac_tell_status_unsupported \
FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED
# define flac_length_status FLAC__StreamDecoderLengthStatus
# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK
# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR
# define flac_length_status_unsupported \
FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
#include <FLAC/metadata.h>
#define FLAC_CHUNK_SIZE 4080
typedef struct {
unsigned char chunk[FLAC_CHUNK_SIZE];
float time;
unsigned int bitRate;
struct audio_format audio_format;
float total_time;
FLAC__uint64 position;
struct decoder *decoder;
InputStream *inStream;
ReplayGainInfo *replayGainInfo;
struct tag *tag;
} FlacData;
/* initializes a given FlacData struct */
void init_FlacData(FlacData * data, struct decoder * decoder,
InputStream * inStream);
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
FlacData * data);
void flac_error_common_cb(const char *plugin,
FLAC__StreamDecoderErrorStatus status,
FlacData * data);
struct tag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block,
struct tag *tag);
FLAC__StreamDecoderWriteStatus
flac_common_write(FlacData *data, const FLAC__Frame * frame,
const FLAC__int32 *const buf[]);
#endif /* _FLAC_COMMON_H */

49
src/decoder/_ogg_common.c Normal file
View File

@@ -0,0 +1,49 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
* (c) 2005 by Eric Wong <normalperson@yhbt.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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "_ogg_common.h"
#include "_flac_common.h"
#include "../utils.h"
ogg_stream_type ogg_stream_type_detect(InputStream * inStream)
{
/* oggflac detection based on code in ogg123 and this post
* http://lists.xiph.org/pipermail/flac/2004-December/000393.html
* ogg123 trunk still doesn't have this patch as of June 2005 */
unsigned char buf[41];
size_t r;
seekInputStream(inStream, 0, SEEK_SET);
r = decoder_read(NULL, inStream, buf, sizeof(buf));
if (r > 0)
seekInputStream(inStream, 0, SEEK_SET);
if (r >= 32 && memcmp(buf, "OggS", 4) == 0 && (
(memcmp(buf+29, "FLAC", 4) == 0
&& memcmp(buf+37, "fLaC", 4) == 0)
|| (memcmp(buf+28, "FLAC", 4) == 0)
|| (memcmp(buf+28, "fLaC", 4) == 0))) {
return FLAC;
}
return VORBIS;
}

31
src/decoder/_ogg_common.h Normal file
View File

@@ -0,0 +1,31 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
* (c) 2005 by Eric Wong <normalperson@yhbt.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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _OGG_COMMON_H
#define _OGG_COMMON_H
#include "../decoder_api.h"
typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type;
ogg_stream_type ogg_stream_type_detect(InputStream * inStream);
#endif /* _OGG_COMMON_H */

602
src/decoder/aac_plugin.c Normal file
View File

@@ -0,0 +1,602 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#define AAC_MAX_CHANNELS 6
#include "../utils.h"
#include "../log.h"
#include <assert.h>
#include <faad.h>
/* all code here is either based on or copied from FAAD2's frontend code */
typedef struct {
struct decoder *decoder;
InputStream *inStream;
size_t bytesIntoBuffer;
size_t bytesConsumed;
off_t fileOffset;
unsigned char *buffer;
int atEof;
} AacBuffer;
static void aac_buffer_shift(AacBuffer * b, size_t length)
{
assert(length >= b->bytesConsumed);
assert(length <= b->bytesConsumed + b->bytesIntoBuffer);
memmove(b->buffer, b->buffer + length,
b->bytesConsumed + b->bytesIntoBuffer - length);
length -= b->bytesConsumed;
b->bytesConsumed = 0;
b->bytesIntoBuffer -= length;
}
static void fillAacBuffer(AacBuffer * b)
{
size_t bread;
if (b->bytesIntoBuffer >= FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS)
/* buffer already full */
return;
aac_buffer_shift(b, b->bytesConsumed);
if (!b->atEof) {
size_t rest = FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS -
b->bytesIntoBuffer;
bread = decoder_read(b->decoder, b->inStream,
(void *)(b->buffer + b->bytesIntoBuffer),
rest);
if (bread == 0 && inputStreamAtEOF(b->inStream))
b->atEof = 1;
b->bytesIntoBuffer += bread;
}
if ((b->bytesIntoBuffer > 3 && memcmp(b->buffer, "TAG", 3) == 0) ||
(b->bytesIntoBuffer > 11 &&
memcmp(b->buffer, "LYRICSBEGIN", 11) == 0) ||
(b->bytesIntoBuffer > 8 && memcmp(b->buffer, "APETAGEX", 8) == 0))
b->bytesIntoBuffer = 0;
}
static void advanceAacBuffer(AacBuffer * b, size_t bytes)
{
b->fileOffset += bytes;
b->bytesConsumed = bytes;
b->bytesIntoBuffer -= bytes;
}
static int adtsSampleRates[] =
{ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000, 7350, 0, 0, 0
};
/**
* Check whether the buffer head is an AAC frame, and return the frame
* length. Returns 0 if it is not a frame.
*/
static size_t adts_check_frame(AacBuffer * b)
{
if (b->bytesIntoBuffer <= 7)
return 0;
/* check syncword */
if (!((b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0)))
return 0;
return (((unsigned int)b->buffer[3] & 0x3) << 11) |
(((unsigned int)b->buffer[4]) << 3) |
(b->buffer[5] >> 5);
}
/**
* Find the next AAC frame in the buffer. Returns 0 if no frame is
* found or if not enough data is available.
*/
static size_t adts_find_frame(AacBuffer * b)
{
const unsigned char *p;
size_t frame_length;
while ((p = memchr(b->buffer, 0xff, b->bytesIntoBuffer)) != NULL) {
/* discard data before 0xff */
if (p > b->buffer)
aac_buffer_shift(b, p - b->buffer);
if (b->bytesIntoBuffer <= 7)
/* not enough data yet */
return 0;
/* is it a frame? */
frame_length = adts_check_frame(b);
if (frame_length > 0)
/* yes, it is */
return frame_length;
/* it's just some random 0xff byte; discard and and
continue searching */
aac_buffer_shift(b, 1);
}
/* nothing at all; discard the whole buffer */
aac_buffer_shift(b, b->bytesIntoBuffer);
return 0;
}
static void adtsParse(AacBuffer * b, float *length)
{
unsigned int frames, frameLength;
int sample_rate = 0;
float framesPerSec;
/* Read all frames to ensure correct time and bitrate */
for (frames = 0;; frames++) {
fillAacBuffer(b);
frameLength = adts_find_frame(b);
if (frameLength > 0) {
if (frames == 0) {
sample_rate = adtsSampleRates[(b->
buffer[2] & 0x3c)
>> 2];
}
if (frameLength > b->bytesIntoBuffer)
break;
advanceAacBuffer(b, frameLength);
} else
break;
}
framesPerSec = (float)sample_rate / 1024.0;
if (framesPerSec != 0)
*length = (float)frames / framesPerSec;
}
static void initAacBuffer(AacBuffer * b,
struct decoder *decoder, InputStream * inStream)
{
memset(b, 0, sizeof(AacBuffer));
b->decoder = decoder;
b->inStream = inStream;
b->buffer = xmalloc(FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
memset(b->buffer, 0, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
}
static void aac_parse_header(AacBuffer * b, float *length)
{
size_t fileread;
size_t tagsize;
if (length)
*length = -1;
fileread = b->inStream->size;
fillAacBuffer(b);
tagsize = 0;
if (b->bytesIntoBuffer >= 10 && !memcmp(b->buffer, "ID3", 3)) {
tagsize = (b->buffer[6] << 21) | (b->buffer[7] << 14) |
(b->buffer[8] << 7) | (b->buffer[9] << 0);
tagsize += 10;
advanceAacBuffer(b, tagsize);
fillAacBuffer(b);
}
if (length == NULL)
return;
if (b->bytesIntoBuffer >= 2 &&
(b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0)) {
adtsParse(b, length);
seekInputStream(b->inStream, tagsize, SEEK_SET);
b->bytesIntoBuffer = 0;
b->bytesConsumed = 0;
b->fileOffset = tagsize;
fillAacBuffer(b);
} else if (memcmp(b->buffer, "ADIF", 4) == 0) {
int bitRate;
int skipSize = (b->buffer[4] & 0x80) ? 9 : 0;
bitRate =
((unsigned int)(b->
buffer[4 +
skipSize] & 0x0F) << 19) | ((unsigned
int)b->
buffer[5
+
skipSize]
<< 11) |
((unsigned int)b->
buffer[6 + skipSize] << 3) | ((unsigned int)b->buffer[7 +
skipSize]
& 0xE0);
if (fileread != 0 && bitRate != 0)
*length = fileread * 8.0 / bitRate;
else
*length = fileread;
}
}
static float getAacFloatTotalTime(char *file)
{
AacBuffer b;
float length;
faacDecHandle decoder;
faacDecConfigurationPtr config;
uint32_t sample_rate;
unsigned char channels;
InputStream inStream;
long bread;
if (openInputStream(&inStream, file) < 0)
return -1;
initAacBuffer(&b, NULL, &inStream);
aac_parse_header(&b, &length);
if (length < 0) {
decoder = faacDecOpen();
config = faacDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
faacDecSetConfiguration(decoder, config);
fillAacBuffer(&b);
#ifdef HAVE_FAAD_BUFLEN_FUNCS
bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer,
&sample_rate, &channels);
#else
bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels);
#endif
if (bread >= 0 && sample_rate > 0 && channels > 0)
length = 0;
faacDecClose(decoder);
}
if (b.buffer)
free(b.buffer);
closeInputStream(&inStream);
return length;
}
static int getAacTotalTime(char *file)
{
int file_time = -1;
float length;
if ((length = getAacFloatTotalTime(file)) >= 0)
file_time = length + 0.5;
return file_time;
}
static int aac_stream_decode(struct decoder * mpd_decoder,
InputStream *inStream)
{
float file_time;
float totalTime = 0;
faacDecHandle decoder;
faacDecFrameInfo frameInfo;
faacDecConfigurationPtr config;
long bread;
struct audio_format audio_format;
uint32_t sample_rate;
unsigned char channels;
unsigned int sampleCount;
char *sampleBuffer;
size_t sampleBufferLen;
uint16_t bitRate = 0;
AacBuffer b;
int initialized = 0;
initAacBuffer(&b, mpd_decoder, inStream);
decoder = faacDecOpen();
config = faacDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX
config->downMatrix = 1;
#endif
#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR
config->dontUpSampleImplicitSBR = 0;
#endif
faacDecSetConfiguration(decoder, config);
while (b.bytesIntoBuffer < FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS &&
!b.atEof &&
decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) {
fillAacBuffer(&b);
adts_find_frame(&b);
fillAacBuffer(&b);
my_usleep(10000);
}
#ifdef HAVE_FAAD_BUFLEN_FUNCS
bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer,
&sample_rate, &channels);
#else
bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels);
#endif
if (bread < 0) {
ERROR("Error not a AAC stream.\n");
faacDecClose(decoder);
if (b.buffer)
free(b.buffer);
return -1;
}
audio_format.bits = 16;
file_time = 0.0;
advanceAacBuffer(&b, bread);
while (1) {
fillAacBuffer(&b);
adts_find_frame(&b);
fillAacBuffer(&b);
if (b.bytesIntoBuffer == 0)
break;
#ifdef HAVE_FAAD_BUFLEN_FUNCS
sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer,
b.bytesIntoBuffer);
#else
sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer);
#endif
if (frameInfo.error > 0) {
ERROR("error decoding AAC stream\n");
ERROR("faad2 error: %s\n",
faacDecGetErrorMessage(frameInfo.error));
break;
}
#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE
sample_rate = frameInfo.samplerate;
#endif
if (!initialized) {
audio_format.channels = frameInfo.channels;
audio_format.sample_rate = sample_rate;
decoder_initialized(mpd_decoder, &audio_format, totalTime);
initialized = 1;
}
advanceAacBuffer(&b, frameInfo.bytesconsumed);
sampleCount = (unsigned long)(frameInfo.samples);
if (sampleCount > 0) {
bitRate = frameInfo.bytesconsumed * 8.0 *
frameInfo.channels * sample_rate /
frameInfo.samples / 1000 + 0.5;
file_time +=
(float)(frameInfo.samples) / frameInfo.channels /
sample_rate;
}
sampleBufferLen = sampleCount * 2;
decoder_data(mpd_decoder, NULL, 0, sampleBuffer,
sampleBufferLen, file_time,
bitRate, NULL);
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
decoder_seek_error(mpd_decoder);
} else if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP)
break;
}
decoder_flush(mpd_decoder);
faacDecClose(decoder);
if (b.buffer)
free(b.buffer);
if (!initialized)
return -1;
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
decoder_seek_error(mpd_decoder);
}
return 0;
}
static int aac_decode(struct decoder * mpd_decoder, char *path)
{
float file_time;
float totalTime;
faacDecHandle decoder;
faacDecFrameInfo frameInfo;
faacDecConfigurationPtr config;
long bread;
struct audio_format audio_format;
uint32_t sample_rate;
unsigned char channels;
unsigned int sampleCount;
char *sampleBuffer;
size_t sampleBufferLen;
/*float * seekTable;
long seekTableEnd = -1;
int seekPositionFound = 0; */
uint16_t bitRate = 0;
AacBuffer b;
InputStream inStream;
int initialized = 0;
if ((totalTime = getAacFloatTotalTime(path)) < 0)
return -1;
if (openInputStream(&inStream, path) < 0)
return -1;
initAacBuffer(&b, mpd_decoder, &inStream);
aac_parse_header(&b, NULL);
decoder = faacDecOpen();
config = faacDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX
config->downMatrix = 1;
#endif
#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR
config->dontUpSampleImplicitSBR = 0;
#endif
faacDecSetConfiguration(decoder, config);
fillAacBuffer(&b);
#ifdef HAVE_FAAD_BUFLEN_FUNCS
bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer,
&sample_rate, &channels);
#else
bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels);
#endif
if (bread < 0) {
ERROR("Error not a AAC stream.\n");
faacDecClose(decoder);
if (b.buffer)
free(b.buffer);
return -1;
}
audio_format.bits = 16;
file_time = 0.0;
advanceAacBuffer(&b, bread);
while (1) {
fillAacBuffer(&b);
if (b.bytesIntoBuffer == 0)
break;
#ifdef HAVE_FAAD_BUFLEN_FUNCS
sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer,
b.bytesIntoBuffer);
#else
sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer);
#endif
if (frameInfo.error > 0) {
ERROR("error decoding AAC file: %s\n", path);
ERROR("faad2 error: %s\n",
faacDecGetErrorMessage(frameInfo.error));
break;
}
#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE
sample_rate = frameInfo.samplerate;
#endif
if (!initialized) {
audio_format.channels = frameInfo.channels;
audio_format.sample_rate = sample_rate;
decoder_initialized(mpd_decoder, &audio_format,
totalTime);
initialized = 1;
}
advanceAacBuffer(&b, frameInfo.bytesconsumed);
sampleCount = (unsigned long)(frameInfo.samples);
if (sampleCount > 0) {
bitRate = frameInfo.bytesconsumed * 8.0 *
frameInfo.channels * sample_rate /
frameInfo.samples / 1000 + 0.5;
file_time +=
(float)(frameInfo.samples) / frameInfo.channels /
sample_rate;
}
sampleBufferLen = sampleCount * 2;
decoder_data(mpd_decoder, NULL, 0, sampleBuffer,
sampleBufferLen, file_time,
bitRate, NULL);
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
decoder_seek_error(mpd_decoder);
} else if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP)
break;
}
decoder_flush(mpd_decoder);
faacDecClose(decoder);
if (b.buffer)
free(b.buffer);
if (!initialized)
return -1;
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
decoder_seek_error(mpd_decoder);
}
return 0;
}
static struct tag *aacTagDup(char *file)
{
struct tag *ret = NULL;
int file_time = getAacTotalTime(file);
if (file_time >= 0) {
if ((ret = tag_id3_load(file)) == NULL)
ret = tag_new();
ret->time = file_time;
} else {
DEBUG("aacTagDup: Failed to get total song time from: %s\n",
file);
}
return ret;
}
static const char *aac_suffixes[] = { "aac", NULL };
static const char *aac_mimeTypes[] = { "audio/aac", "audio/aacp", NULL };
struct decoder_plugin aacPlugin = {
.name = "aac",
.stream_decode = aac_stream_decode,
.file_decode = aac_decode,
.tag_dup = aacTagDup,
.stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL,
.suffixes = aac_suffixes,
.mime_types = aac_mimeTypes
};

View File

@@ -0,0 +1,147 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* libaudiofile (wave) support added by Eric Wong <normalperson@yhbt.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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#include "../log.h"
#include <sys/stat.h>
#include <audiofile.h>
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
#define CHUNK_SIZE 1020
static int getAudiofileTotalTime(char *file)
{
int total_time;
AFfilehandle af_fp = afOpenFile(file, "r", NULL);
if (af_fp == AF_NULL_FILEHANDLE) {
return -1;
}
total_time = (int)
((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK)
/ afGetRate(af_fp, AF_DEFAULT_TRACK));
afCloseFile(af_fp);
return total_time;
}
static int audiofile_decode(struct decoder * decoder, char *path)
{
int fs, frame_count;
AFfilehandle af_fp;
int bits;
struct audio_format audio_format;
float total_time;
uint16_t bitRate;
struct stat st;
int ret, current = 0;
char chunk[CHUNK_SIZE];
if (stat(path, &st) < 0) {
ERROR("failed to stat: %s\n", path);
return -1;
}
af_fp = afOpenFile(path, "r", NULL);
if (af_fp == AF_NULL_FILEHANDLE) {
ERROR("failed to open: %s\n", path);
return -1;
}
afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
AF_SAMPFMT_TWOSCOMP, 16);
afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
audio_format.bits = (uint8_t)bits;
audio_format.sample_rate =
(unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK);
audio_format.channels =
(uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK);
frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK);
total_time = ((float)frame_count / (float)audio_format.sample_rate);
bitRate = (uint16_t)(st.st_size * 8.0 / total_time / 1000.0 + 0.5);
if (audio_format.bits != 8 && audio_format.bits != 16) {
ERROR("Only 8 and 16-bit files are supported. %s is %i-bit\n",
path, audio_format.bits);
afCloseFile(af_fp);
return -1;
}
fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1);
decoder_initialized(decoder, &audio_format, total_time);
do {
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
decoder_clear(decoder);
current = decoder_seek_where(decoder) *
audio_format.sample_rate;
afSeekFrame(af_fp, AF_DEFAULT_TRACK, current);
decoder_command_finished(decoder);
}
ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk,
CHUNK_SIZE / fs);
if (ret <= 0)
break;
current += ret;
decoder_data(decoder, NULL, 1,
chunk, ret * fs,
(float)current / (float)audio_format.sample_rate,
bitRate, NULL);
} while (decoder_get_command(decoder) != DECODE_COMMAND_STOP);
decoder_flush(decoder);
afCloseFile(af_fp);
return 0;
}
static struct tag *audiofileTagDup(char *file)
{
struct tag *ret = NULL;
int total_time = getAudiofileTotalTime(file);
if (total_time >= 0) {
if (!ret)
ret = tag_new();
ret->time = total_time;
} else {
DEBUG
("audiofileTagDup: Failed to get total song time from: %s\n",
file);
}
return ret;
}
static const char *audiofileSuffixes[] = { "wav", "au", "aiff", "aif", NULL };
struct decoder_plugin audiofilePlugin = {
.name = "audiofile",
.file_decode = audiofile_decode,
.tag_dup = audiofileTagDup,
.stream_types = INPUT_PLUGIN_STREAM_FILE,
.suffixes = audiofileSuffixes,
};

419
src/decoder/ffmpeg_plugin.c Normal file
View File

@@ -0,0 +1,419 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2008 Viliam Mateicka <viliam.mateicka@gmail.com>
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#include "../log.h"
#include "../utils.h"
#include "../log.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef OLD_FFMPEG_INCLUDES
#include <avcodec.h>
#include <avformat.h>
#include <avio.h>
#else
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#endif
typedef struct {
int audioStream;
AVFormatContext *pFormatCtx;
AVCodecContext *aCodecCtx;
AVCodec *aCodec;
struct decoder *decoder;
InputStream *input;
struct tag *tag;
} BasePtrs;
typedef struct {
/** hack - see url_to_base() */
char url[8];
struct decoder *decoder;
InputStream *input;
} FopsHelper;
/**
* Convert a faked mpd:// URL to a FopsHelper structure. This is a
* hack because ffmpeg does not provide a nice API for passing a
* user-defined pointer to mpdurl_open().
*/
static FopsHelper *url_to_base(const char *url)
{
union {
const char *in;
FopsHelper *out;
} u = { .in = url };
return u.out;
}
static int mpdurl_open(URLContext *h, const char *filename,
mpd_unused int flags)
{
FopsHelper *base = url_to_base(filename);
h->priv_data = base;
h->is_streamed = (base->input->seekable ? 0 : 1);
return 0;
}
static int mpdurl_read(URLContext *h, unsigned char *buf, int size)
{
int ret;
FopsHelper *base = (FopsHelper *) h->priv_data;
while (1) {
ret = readFromInputStream(base->input, (void *)buf, size);
if (ret == 0) {
DEBUG("ret 0\n");
if (inputStreamAtEOF(base->input) ||
(base->decoder &&
decoder_get_command(base->decoder) != DECODE_COMMAND_NONE)) {
DEBUG("eof stream\n");
return ret;
} else {
my_usleep(10000);
}
} else {
break;
}
}
return ret;
}
static int64_t mpdurl_seek(URLContext *h, int64_t pos, int whence)
{
FopsHelper *base = (FopsHelper *) h->priv_data;
if (whence != AVSEEK_SIZE) { //only ftell
(void) seekInputStream(base->input, pos, whence);
}
return base->input->offset;
}
static int mpdurl_close(URLContext *h)
{
FopsHelper *base = (FopsHelper *) h->priv_data;
if (base && base->input->seekable) {
(void) seekInputStream(base->input, 0, SEEK_SET);
}
h->priv_data = 0;
return 0;
}
static URLProtocol mpdurl_fileops = {
.name = "mpd",
.url_open = mpdurl_open,
.url_read = mpdurl_read,
.url_seek = mpdurl_seek,
.url_close = mpdurl_close,
};
static int ffmpeg_init(void)
{
av_register_all();
register_protocol(&mpdurl_fileops);
return 0;
}
static int ffmpeg_helper(InputStream *input, int (*callback)(BasePtrs *ptrs),
BasePtrs *ptrs)
{
AVFormatContext *pFormatCtx;
AVCodecContext *aCodecCtx;
AVCodec *aCodec;
int ret, audioStream;
unsigned i;
FopsHelper fopshelp = {
.url = "mpd://X", /* only the mpd:// prefix matters */
};
fopshelp.input = input;
if (ptrs && ptrs->decoder) {
fopshelp.decoder = ptrs->decoder; //are we in decoding loop ?
} else {
fopshelp.decoder = NULL;
}
//ffmpeg works with ours "fileops" helper
if (av_open_input_file(&pFormatCtx, fopshelp.url, NULL, 0, NULL)!=0) {
ERROR("Open failed!\n");
return -1;
}
if (av_find_stream_info(pFormatCtx)<0) {
ERROR("Couldn't find stream info!\n");
return -1;
}
audioStream = -1;
for(i=0; i<pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO &&
audioStream < 0) {
audioStream=i;
}
}
if(audioStream==-1) {
ERROR("No audio stream inside!\n");
return -1;
}
aCodecCtx = pFormatCtx->streams[audioStream]->codec;
aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
if (!aCodec) {
ERROR("Unsupported audio codec!\n");
return -1;
}
if (avcodec_open(aCodecCtx, aCodec)<0) {
ERROR("Could not open codec!\n");
return -1;
}
if (callback) {
ptrs->audioStream = audioStream;
ptrs->pFormatCtx = pFormatCtx;
ptrs->aCodecCtx = aCodecCtx;
ptrs->aCodec = aCodec;
ret = (*callback)( ptrs );
} else {
ret = 0;
DEBUG("playable\n");
}
avcodec_close(aCodecCtx);
av_close_input_file(pFormatCtx);
return ret;
}
static bool ffmpeg_try_decode(InputStream *input)
{
int ret;
if (input->seekable) {
ret = ffmpeg_helper(input, NULL, NULL);
} else {
ret = 0;
}
return (ret == -1 ? 0 : 1);
}
static int ffmpeg_decode_internal(BasePtrs *base)
{
struct decoder *decoder = base->decoder;
AVCodecContext *aCodecCtx = base->aCodecCtx;
AVFormatContext *pFormatCtx = base->pFormatCtx;
AVPacket packet;
int len, audio_size;
int position;
struct audio_format audio_format;
int current, total_time;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
total_time = 0;
DEBUG("decoder_start\n");
if (aCodecCtx->channels > 2) {
aCodecCtx->channels = 2;
}
audio_format.bits = (uint8_t)16;
audio_format.sample_rate = (unsigned int)aCodecCtx->sample_rate;
audio_format.channels = aCodecCtx->channels;
// frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK);
// total_time = ((float)frame_count / (float)audio_format.sample_rate);
//there is some problem with this on some demux (mp3 at least)
if (pFormatCtx->duration != (int)AV_NOPTS_VALUE) {
total_time = pFormatCtx->duration / AV_TIME_BASE;
}
DEBUG("ffmpeg sample rate: %dHz %d channels\n",
aCodecCtx->sample_rate, aCodecCtx->channels);
decoder_initialized(decoder, &audio_format, total_time);
position = 0;
DEBUG("duration:%d (%d secs)\n", (int) pFormatCtx->duration,
(int) total_time);
do {
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
DEBUG("seek\n");
decoder_clear(decoder);
current = decoder_seek_where(decoder) * AV_TIME_BASE;
if (av_seek_frame(pFormatCtx, -1, current , 0) < 0) {
WARNING("seek to %d failed\n", current);
}
decoder_command_finished(decoder);
}
if (av_read_frame(pFormatCtx, &packet) >= 0) {
if(packet.stream_index == base->audioStream) {
position = av_rescale_q(packet.pts, pFormatCtx->streams[base->audioStream]->time_base,
(AVRational){1, 1});
audio_size = sizeof(audio_buf);
len = avcodec_decode_audio2(aCodecCtx,
(int16_t *)audio_buf,
&audio_size,
packet.data,
packet.size);
if(len >= 0) {
if(audio_size >= 0) {
// DEBUG("sending data %d/%d\n", audio_size, len);
decoder_data(decoder, NULL, 1,
audio_buf, audio_size,
position, //(float)current / (float)audio_format.sample_rate,
aCodecCtx->bit_rate / 1000, NULL);
}
} else {
WARNING("skiping frame!\n");
}
}
av_free_packet(&packet);
} else {
//end of file
break;
}
} while (decoder_get_command(decoder) != DECODE_COMMAND_STOP);
decoder_flush(decoder);
DEBUG("decoder finish\n");
return 0;
}
static int ffmpeg_decode(struct decoder *decoder, InputStream *input)
{
BasePtrs base;
int ret;
DEBUG("decode start\n");
base.input = input;
base.decoder = decoder;
ret = ffmpeg_helper(input, ffmpeg_decode_internal, &base);
DEBUG("decode finish\n");
return ret;
}
static int ffmpeg_tag_internal(BasePtrs *base)
{
struct tag *tag = (struct tag *) base->tag;
if (base->pFormatCtx->duration != (int)AV_NOPTS_VALUE) {
tag->time = base->pFormatCtx->duration / AV_TIME_BASE;
} else {
tag->time = 0;
}
return 0;
}
//no tag reading in ffmpeg, check if playable
static struct tag *ffmpeg_tag(char *file)
{
InputStream input;
BasePtrs base;
int ret;
struct tag *tag = NULL;
if (openInputStream(&input, file) < 0) {
ERROR("failed to open %s\n", file);
return NULL;
}
tag = tag_new();
base.tag = tag;
ret = ffmpeg_helper(&input, ffmpeg_tag_internal, &base);
if (ret != 0) {
free(tag);
tag = NULL;
}
closeInputStream(&input);
return tag;
}
/**
* ffmpeg can decode almost everything from open codecs
* and also some of propietary codecs
* its hard to tell what can ffmpeg decode
* we can later put this into configure script
* to be sure ffmpeg is used to handle
* only that files
*/
static const char *ffmpeg_Suffixes[] = {
"wma", "asf", "wmv", "mpeg", "mpg", "avi", "vob", "mov", "qt", "swf", "rm", "swf",
"mp1", "mp2", "mp3", "mp4", "m4a", "flac", "ogg", "wav", "au", "aiff", "aif", "ac3", "aac", "mpc",
NULL
};
//not sure if this is correct...
static const char *ffmpeg_Mimetypes[] = {
"video/x-ms-asf",
"audio/x-ms-wma",
"audio/x-ms-wax",
"video/x-ms-wmv",
"video/x-ms-wvx",
"video/x-ms-wm",
"video/x-ms-wmx",
"application/x-ms-wmz",
"application/x-ms-wmd",
"audio/mpeg",
NULL
};
struct decoder_plugin ffmpegPlugin = {
.name = "ffmpeg",
.init = ffmpeg_init,
.try_decode = ffmpeg_try_decode,
.stream_decode = ffmpeg_decode,
.tag_dup = ffmpeg_tag,
.stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
.suffixes = ffmpeg_Suffixes,
.mime_types = ffmpeg_Mimetypes
};

459
src/decoder/flac_plugin.c Normal file
View File

@@ -0,0 +1,459 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "_flac_common.h"
#include "../utils.h"
#include "../log.h"
#include <assert.h>
/* this code was based on flac123, from flac-tools */
static flac_read_status flacRead(mpd_unused const flac_decoder * flacDec,
FLAC__byte buf[],
flac_read_status_size_t *bytes,
void *fdata)
{
FlacData *data = (FlacData *) fdata;
size_t r;
r = decoder_read(data->decoder, data->inStream, (void *)buf, *bytes);
*bytes = r;
if (r == 0) {
if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
inputStreamAtEOF(data->inStream))
return flac_read_status_eof;
else
return flac_read_status_abort;
}
return flac_read_status_continue;
}
static flac_seek_status flacSeek(mpd_unused const flac_decoder * flacDec,
FLAC__uint64 offset,
void *fdata)
{
FlacData *data = (FlacData *) fdata;
if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) {
return flac_seek_status_error;
}
return flac_seek_status_ok;
}
static flac_tell_status flacTell(mpd_unused const flac_decoder * flacDec,
FLAC__uint64 * offset,
void *fdata)
{
FlacData *data = (FlacData *) fdata;
*offset = (long)(data->inStream->offset);
return flac_tell_status_ok;
}
static flac_length_status flacLength(mpd_unused const flac_decoder * flacDec,
FLAC__uint64 * length,
void *fdata)
{
FlacData *data = (FlacData *) fdata;
*length = (size_t) (data->inStream->size);
return flac_length_status_ok;
}
static FLAC__bool flacEOF(mpd_unused const flac_decoder * flacDec, void *fdata)
{
FlacData *data = (FlacData *) fdata;
return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
inputStreamAtEOF(data->inStream);
}
static void flacError(mpd_unused const flac_decoder *dec,
FLAC__StreamDecoderErrorStatus status, void *fdata)
{
flac_error_common_cb("flac", status, (FlacData *) fdata);
}
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
{
const char *str = ""; /* "" to silence compiler warning */
switch (state) {
case FLAC__SEEKABLE_STREAM_DECODER_OK:
case FLAC__SEEKABLE_STREAM_DECODER_SEEKING:
case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
return;
case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
str = "allocation error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
str = "read error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
str = "seek error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
str = "seekable stream error";
break;
case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
str = "decoder already initialized";
break;
case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
str = "invalid callback";
break;
case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
str = "decoder uninitialized";
}
ERROR("flac %s\n", str);
}
static int flac_init(FLAC__SeekableStreamDecoder *dec,
FLAC__SeekableStreamDecoderReadCallback read_cb,
FLAC__SeekableStreamDecoderSeekCallback seek_cb,
FLAC__SeekableStreamDecoderTellCallback tell_cb,
FLAC__SeekableStreamDecoderLengthCallback length_cb,
FLAC__SeekableStreamDecoderEofCallback eof_cb,
FLAC__SeekableStreamDecoderWriteCallback write_cb,
FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
FLAC__SeekableStreamDecoderErrorCallback error_cb,
void *data)
{
int s = 1;
s &= FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb);
s &= FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb);
s &= FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb);
s &= FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb);
s &= FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb);
s &= FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb);
s &= FLAC__seekable_stream_decoder_set_metadata_callback(dec,
metadata_cb);
s &= FLAC__seekable_stream_decoder_set_metadata_respond(dec,
FLAC__METADATA_TYPE_VORBIS_COMMENT);
s &= FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb);
s &= FLAC__seekable_stream_decoder_set_client_data(dec, data);
if (!s || (FLAC__seekable_stream_decoder_init(dec) !=
FLAC__SEEKABLE_STREAM_DECODER_OK))
return 0;
return 1;
}
#else /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacPrintErroredState(FLAC__StreamDecoderState state)
{
const char *str = ""; /* "" to silence compiler warning */
switch (state) {
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
case FLAC__STREAM_DECODER_READ_METADATA:
case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
case FLAC__STREAM_DECODER_READ_FRAME:
case FLAC__STREAM_DECODER_END_OF_STREAM:
return;
case FLAC__STREAM_DECODER_OGG_ERROR:
str = "error in the Ogg layer";
break;
case FLAC__STREAM_DECODER_SEEK_ERROR:
str = "seek error";
break;
case FLAC__STREAM_DECODER_ABORTED:
str = "decoder aborted by read";
break;
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
str = "allocation error";
break;
case FLAC__STREAM_DECODER_UNINITIALIZED:
str = "decoder uninitialized";
}
ERROR("flac %s\n", str);
}
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacMetadata(mpd_unused const flac_decoder * dec,
const FLAC__StreamMetadata * block, void *vdata)
{
flac_metadata_common_cb(block, (FlacData *) vdata);
}
static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec,
const FLAC__Frame * frame,
const FLAC__int32 * const buf[],
void *vdata)
{
FLAC__uint32 samples = frame->header.blocksize;
FlacData *data = (FlacData *) vdata;
float timeChange;
FLAC__uint64 newPosition = 0;
timeChange = ((float)samples) / frame->header.sample_rate;
data->time += timeChange;
flac_get_decode_position(dec, &newPosition);
if (data->position && newPosition >= data->position) {
assert(timeChange >= 0);
data->bitRate =
((newPosition - data->position) * 8.0 / timeChange)
/ 1000 + 0.5;
}
data->position = newPosition;
return flac_common_write(data, frame, buf);
}
static struct tag *flacMetadataDup(char *file, int *vorbisCommentFound)
{
struct tag *ret = NULL;
FLAC__Metadata_SimpleIterator *it;
FLAC__StreamMetadata *block = NULL;
*vorbisCommentFound = 0;
it = FLAC__metadata_simple_iterator_new();
if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
const char *err;
FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
s = FLAC__metadata_simple_iterator_status(it);
switch (s) { /* slightly more human-friendly messages: */
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
err = "illegal input";
break;
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
err = "error opening file";
break;
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
err = "not a FLAC file";
break;
default:
err = FLAC__Metadata_SimpleIteratorStatusString[s];
}
DEBUG("flacMetadataDup: Reading '%s' "
"metadata gave the following error: %s\n",
file, err);
FLAC__metadata_simple_iterator_delete(it);
return ret;
}
do {
block = FLAC__metadata_simple_iterator_get_block(it);
if (!block)
break;
if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
ret = copyVorbisCommentBlockToMpdTag(block, ret);
if (ret)
*vorbisCommentFound = 1;
} else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
if (!ret)
ret = tag_new();
ret->time = ((float)block->data.stream_info.
total_samples) /
block->data.stream_info.sample_rate + 0.5;
}
FLAC__metadata_object_delete(block);
} while (FLAC__metadata_simple_iterator_next(it));
FLAC__metadata_simple_iterator_delete(it);
return ret;
}
static struct tag *flacTagDup(char *file)
{
struct tag *ret = NULL;
int foundVorbisComment = 0;
ret = flacMetadataDup(file, &foundVorbisComment);
if (!ret) {
DEBUG("flacTagDup: Failed to grab information from: %s\n",
file);
return NULL;
}
if (!foundVorbisComment) {
struct tag *temp = tag_id3_load(file);
if (temp) {
temp->time = ret->time;
tag_free(ret);
ret = temp;
}
}
return ret;
}
static int flac_decode_internal(struct decoder * decoder,
InputStream * inStream, int is_ogg)
{
flac_decoder *flacDec;
FlacData data;
const char *err = NULL;
if (!(flacDec = flac_new()))
return -1;
init_FlacData(&data, decoder, inStream);
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
if(!FLAC__stream_decoder_set_metadata_respond(flacDec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
{
DEBUG(__FILE__": Failed to set metadata respond\n");
}
#endif
if (is_ogg) {
if (!flac_ogg_init(flacDec, flacRead, flacSeek, flacTell,
flacLength, flacEOF, flacWrite, flacMetadata,
flacError, (void *)&data)) {
err = "doing Ogg init()";
goto fail;
}
} else {
if (!flac_init(flacDec, flacRead, flacSeek, flacTell,
flacLength, flacEOF, flacWrite, flacMetadata,
flacError, (void *)&data)) {
err = "doing init()";
goto fail;
}
if (!flac_process_metadata(flacDec)) {
err = "problem reading metadata";
goto fail;
}
}
decoder_initialized(decoder, &data.audio_format, data.total_time);
while (1) {
if (!flac_process_single(flacDec))
break;
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
FLAC__uint64 sampleToSeek = decoder_seek_where(decoder) *
data.audio_format.sample_rate + 0.5;
if (flac_seek_absolute(flacDec, sampleToSeek)) {
decoder_clear(decoder);
data.time = ((float)sampleToSeek) /
data.audio_format.sample_rate;
data.position = 0;
decoder_command_finished(decoder);
} else
decoder_seek_error(decoder);
} else if (flac_get_state(flacDec) == flac_decoder_eof)
break;
}
if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) {
flacPrintErroredState(flac_get_state(flacDec));
flac_finish(flacDec);
}
fail:
if (data.replayGainInfo)
freeReplayGainInfo(data.replayGainInfo);
if (flacDec)
flac_delete(flacDec);
if (err) {
ERROR("flac %s\n", err);
return -1;
}
return 0;
}
static int flac_decode(struct decoder * decoder, InputStream * inStream)
{
return flac_decode_internal(decoder, inStream, 0);
}
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 && \
!defined(HAVE_OGGFLAC)
static struct tag *oggflac_tag_dup(char *file)
{
struct tag *ret = NULL;
FLAC__Metadata_Iterator *it;
FLAC__StreamMetadata *block;
FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
if (!(FLAC__metadata_chain_read_ogg(chain, file)))
goto out;
it = FLAC__metadata_iterator_new();
FLAC__metadata_iterator_init(it, chain);
do {
if (!(block = FLAC__metadata_iterator_get_block(it)))
break;
if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
ret = copyVorbisCommentBlockToMpdTag(block, ret);
} else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
if (!ret)
ret = tag_new();
ret->time = ((float)block->data.stream_info.
total_samples) /
block->data.stream_info.sample_rate + 0.5;
}
} while (FLAC__metadata_iterator_next(it));
FLAC__metadata_iterator_delete(it);
out:
FLAC__metadata_chain_delete(chain);
return ret;
}
static int oggflac_decode(struct decoder *decoder, InputStream * inStream)
{
return flac_decode_internal(decoder, inStream, 1);
}
static bool oggflac_try_decode(InputStream * inStream)
{
return FLAC_API_SUPPORTS_OGG_FLAC &&
ogg_stream_type_detect(inStream) == FLAC;
}
static const char *oggflac_suffixes[] = { "ogg", "oga", NULL };
static const char *oggflac_mime_types[] = { "audio/x-flac+ogg",
"application/ogg",
"application/x-ogg",
NULL };
struct decoder_plugin oggflacPlugin = {
.name = "oggflac",
.try_decode = oggflac_try_decode,
.stream_decode = oggflac_decode,
.tag_dup = oggflac_tag_dup,
.stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
.suffixes = oggflac_suffixes,
.mime_types = oggflac_mime_types
};
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
static const char *flacSuffixes[] = { "flac", NULL };
static const char *flac_mime_types[] = { "audio/x-flac",
"application/x-flac",
NULL };
struct decoder_plugin flacPlugin = {
.name = "flac",
.stream_decode = flac_decode,
.tag_dup = flacTagDup,
.stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
.suffixes = flacSuffixes,
.mime_types = flac_mime_types
};

278
src/decoder/mod_plugin.c Normal file
View File

@@ -0,0 +1,278 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#include "../utils.h"
#include "../log.h"
#include <mikmod.h>
/* this is largely copied from alsaplayer */
#define MIKMOD_FRAME_SIZE 4096
static BOOL mod_mpd_Init(void)
{
return VC_Init();
}
static void mod_mpd_Exit(void)
{
VC_Exit();
}
static void mod_mpd_Update(void)
{
}
static BOOL mod_mpd_IsThere(void)
{
return 1;
}
static char drv_name[] = "MPD";
static char drv_version[] = "MPD Output Driver v0.1";
#if (LIBMIKMOD_VERSION > 0x030106)
static char drv_alias[] = "mpd";
#endif
static MDRIVER drv_mpd = {
NULL,
drv_name,
drv_version,
0,
255,
#if (LIBMIKMOD_VERSION > 0x030106)
drv_alias,
#if (LIBMIKMOD_VERSION >= 0x030200)
NULL, /* CmdLineHelp */
#endif
NULL, /* CommandLine */
#endif
mod_mpd_IsThere,
VC_SampleLoad,
VC_SampleUnload,
VC_SampleSpace,
VC_SampleLength,
mod_mpd_Init,
mod_mpd_Exit,
NULL,
VC_SetNumVoices,
VC_PlayStart,
VC_PlayStop,
mod_mpd_Update,
NULL,
VC_VoiceSetVolume,
VC_VoiceGetVolume,
VC_VoiceSetFrequency,
VC_VoiceGetFrequency,
VC_VoiceSetPanning,
VC_VoiceGetPanning,
VC_VoicePlay,
VC_VoiceStop,
VC_VoiceStopped,
VC_VoiceGetPosition,
VC_VoiceRealVolume
};
static int mod_mikModInitiated;
static int mod_mikModInitError;
static int mod_initMikMod(void)
{
static char params[] = "";
if (mod_mikModInitError)
return -1;
if (!mod_mikModInitiated) {
mod_mikModInitiated = 1;
md_device = 0;
md_reverb = 0;
MikMod_RegisterDriver(&drv_mpd);
MikMod_RegisterAllLoaders();
}
md_pansep = 64;
md_mixfreq = 44100;
md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
DMODE_16BITS);
if (MikMod_Init(params)) {
ERROR("Could not init MikMod: %s\n",
MikMod_strerror(MikMod_errno));
mod_mikModInitError = 1;
return -1;
}
return 0;
}
static void mod_finishMikMod(void)
{
MikMod_Exit();
}
typedef struct _mod_Data {
MODULE *moduleHandle;
SBYTE *audio_buffer;
} mod_Data;
static mod_Data *mod_open(char *path)
{
MODULE *moduleHandle;
mod_Data *data;
if (!(moduleHandle = Player_Load(path, 128, 0)))
return NULL;
/* Prevent module from looping forever */
moduleHandle->loop = 0;
data = xmalloc(sizeof(mod_Data));
data->audio_buffer = xmalloc(MIKMOD_FRAME_SIZE);
data->moduleHandle = moduleHandle;
Player_Start(data->moduleHandle);
return data;
}
static void mod_close(mod_Data * data)
{
Player_Stop();
Player_Free(data->moduleHandle);
free(data->audio_buffer);
free(data);
}
static int mod_decode(struct decoder * decoder, char *path)
{
mod_Data *data;
struct audio_format audio_format;
float total_time = 0.0;
int ret;
float secPerByte;
if (mod_initMikMod() < 0)
return -1;
if (!(data = mod_open(path))) {
ERROR("failed to open mod: %s\n", path);
MikMod_Exit();
return -1;
}
audio_format.bits = 16;
audio_format.sample_rate = 44100;
audio_format.channels = 2;
secPerByte =
1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
(float)audio_format.sample_rate);
decoder_initialized(decoder, &audio_format, 0);
while (1) {
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
decoder_seek_error(decoder);
}
if (decoder_get_command(decoder) == DECODE_COMMAND_STOP)
break;
if (!Player_Active())
break;
ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE);
total_time += ret * secPerByte;
decoder_data(decoder, NULL, 0,
(char *)data->audio_buffer, ret,
total_time, 0, NULL);
}
decoder_flush(decoder);
mod_close(data);
MikMod_Exit();
return 0;
}
static struct tag *modTagDup(char *file)
{
struct tag *ret = NULL;
MODULE *moduleHandle;
char *title;
if (mod_initMikMod() < 0) {
DEBUG("modTagDup: Failed to initialize MikMod\n");
return NULL;
}
if (!(moduleHandle = Player_Load(file, 128, 0))) {
DEBUG("modTagDup: Failed to open file: %s\n", file);
MikMod_Exit();
return NULL;
}
Player_Free(moduleHandle);
ret = tag_new();
ret->time = 0;
title = xstrdup(Player_LoadTitle(file));
if (title)
tag_add_item(ret, TAG_ITEM_TITLE, title);
MikMod_Exit();
return ret;
}
static const char *modSuffixes[] = { "amf",
"dsm",
"far",
"gdm",
"imf",
"it",
"med",
"mod",
"mtm",
"s3m",
"stm",
"stx",
"ult",
"uni",
"xm",
NULL
};
struct decoder_plugin modPlugin = {
.name = "mod",
.finish = mod_finishMikMod,
.file_decode = mod_decode,
.tag_dup = modTagDup,
.stream_types = INPUT_PLUGIN_STREAM_FILE,
.suffixes = modSuffixes,
};

1086
src/decoder/mp3_plugin.c Normal file

File diff suppressed because it is too large Load Diff

423
src/decoder/mp4_plugin.c Normal file
View File

@@ -0,0 +1,423 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#include "../utils.h"
#include "../log.h"
#include "mp4ff.h"
#include <limits.h>
#include <faad.h>
/* all code here is either based on or copied from FAAD2's frontend code */
static int mp4_getAACTrack(mp4ff_t * infile)
{
/* find AAC track */
int i, rc;
int numTracks = mp4ff_total_tracks(infile);
for (i = 0; i < numTracks; i++) {
unsigned char *buff = NULL;
unsigned int buff_size = 0;
#ifdef HAVE_MP4AUDIOSPECIFICCONFIG
mp4AudioSpecificConfig mp4ASC;
#else
unsigned long dummy1_32;
unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8,
dummy7_8, dummy8_8;
#endif
mp4ff_get_decoder_config(infile, i, &buff, &buff_size);
if (buff) {
#ifdef HAVE_MP4AUDIOSPECIFICCONFIG
rc = AudioSpecificConfig(buff, buff_size, &mp4ASC);
#else
rc = AudioSpecificConfig(buff, &dummy1_32, &dummy2_8,
&dummy3_8, &dummy4_8,
&dummy5_8, &dummy6_8,
&dummy7_8, &dummy8_8);
#endif
free(buff);
if (rc < 0)
continue;
return i;
}
}
/* can't decode this */
return -1;
}
static uint32_t mp4_inputStreamReadCallback(void *inStream, void *buffer,
uint32_t length)
{
return readFromInputStream((InputStream *) inStream, buffer, length);
}
static uint32_t mp4_inputStreamSeekCallback(void *inStream, uint64_t position)
{
return seekInputStream((InputStream *) inStream, position, SEEK_SET);
}
static int mp4_decode(struct decoder * mpd_decoder, InputStream * inStream)
{
mp4ff_t *mp4fh;
mp4ff_callback_t *mp4cb;
int32_t track;
float file_time, total_time;
int32_t scale;
faacDecHandle decoder;
faacDecFrameInfo frameInfo;
faacDecConfigurationPtr config;
struct audio_format audio_format;
unsigned char *mp4Buffer;
unsigned int mp4BufferSize;
uint32_t sample_rate;
unsigned char channels;
long sampleId;
long numSamples;
long dur;
unsigned int sampleCount;
char *sampleBuffer;
size_t sampleBufferLen;
unsigned int initial = 1;
float *seekTable;
long seekTableEnd = -1;
bool seekPositionFound = false;
long offset;
uint16_t bitRate = 0;
bool seeking = false;
double seek_where = 0;
bool initialized = false;
mp4cb = xmalloc(sizeof(mp4ff_callback_t));
mp4cb->read = mp4_inputStreamReadCallback;
mp4cb->seek = mp4_inputStreamSeekCallback;
mp4cb->user_data = inStream;
mp4fh = mp4ff_open_read(mp4cb);
if (!mp4fh) {
ERROR("Input does not appear to be a mp4 stream.\n");
free(mp4cb);
return -1;
}
track = mp4_getAACTrack(mp4fh);
if (track < 0) {
ERROR("No AAC track found in mp4 stream.\n");
mp4ff_close(mp4fh);
free(mp4cb);
return -1;
}
decoder = faacDecOpen();
config = faacDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX
config->downMatrix = 1;
#endif
#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR
config->dontUpSampleImplicitSBR = 0;
#endif
faacDecSetConfiguration(decoder, config);
audio_format.bits = 16;
mp4Buffer = NULL;
mp4BufferSize = 0;
mp4ff_get_decoder_config(mp4fh, track, &mp4Buffer, &mp4BufferSize);
if (faacDecInit2
(decoder, mp4Buffer, mp4BufferSize, &sample_rate, &channels) < 0) {
ERROR("Error not a AAC stream.\n");
faacDecClose(decoder);
mp4ff_close(mp4fh);
free(mp4cb);
return -1;
}
audio_format.sample_rate = sample_rate;
audio_format.channels = channels;
file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
scale = mp4ff_time_scale(mp4fh, track);
if (mp4Buffer)
free(mp4Buffer);
if (scale < 0) {
ERROR("Error getting audio format of mp4 AAC track.\n");
faacDecClose(decoder);
mp4ff_close(mp4fh);
free(mp4cb);
return -1;
}
total_time = ((float)file_time) / scale;
numSamples = mp4ff_num_samples(mp4fh, track);
if (numSamples > (long)(INT_MAX / sizeof(float))) {
ERROR("Integer overflow.\n");
faacDecClose(decoder);
mp4ff_close(mp4fh);
free(mp4cb);
return -1;
}
file_time = 0.0;
seekTable = xmalloc(sizeof(float) * numSamples);
for (sampleId = 0; sampleId < numSamples; sampleId++) {
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
seeking = true;
seek_where = decoder_seek_where(mpd_decoder);
}
if (seeking && seekTableEnd > 1 &&
seekTable[seekTableEnd] >= seek_where) {
int i = 2;
while (seekTable[i] < seek_where)
i++;
sampleId = i - 1;
file_time = seekTable[sampleId];
}
dur = mp4ff_get_sample_duration(mp4fh, track, sampleId);
offset = mp4ff_get_sample_offset(mp4fh, track, sampleId);
if (sampleId > seekTableEnd) {
seekTable[sampleId] = file_time;
seekTableEnd = sampleId;
}
if (sampleId == 0)
dur = 0;
if (offset > dur)
dur = 0;
else
dur -= offset;
file_time += ((float)dur) / scale;
if (seeking && file_time > seek_where)
seekPositionFound = true;
if (seeking && seekPositionFound) {
seekPositionFound = false;
decoder_clear(mpd_decoder);
seeking = 0;
decoder_command_finished(mpd_decoder);
}
if (seeking)
continue;
if (mp4ff_read_sample(mp4fh, track, sampleId, &mp4Buffer,
&mp4BufferSize) == 0)
break;
#ifdef HAVE_FAAD_BUFLEN_FUNCS
sampleBuffer = faacDecDecode(decoder, &frameInfo, mp4Buffer,
mp4BufferSize);
#else
sampleBuffer = faacDecDecode(decoder, &frameInfo, mp4Buffer);
#endif
if (mp4Buffer)
free(mp4Buffer);
if (frameInfo.error > 0) {
ERROR("faad2 error: %s\n",
faacDecGetErrorMessage(frameInfo.error));
break;
}
if (!initialized) {
channels = frameInfo.channels;
#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE
scale = frameInfo.samplerate;
#endif
audio_format.sample_rate = scale;
audio_format.channels = frameInfo.channels;
decoder_initialized(mpd_decoder, &audio_format,
total_time);
initialized = true;
}
if (channels * (unsigned long)(dur + offset) > frameInfo.samples) {
dur = frameInfo.samples / channels;
offset = 0;
}
sampleCount = (unsigned long)(dur * channels);
if (sampleCount > 0) {
initial = 0;
bitRate = frameInfo.bytesconsumed * 8.0 *
frameInfo.channels * scale /
frameInfo.samples / 1000 + 0.5;
}
sampleBufferLen = sampleCount * 2;
sampleBuffer += offset * channels * 2;
decoder_data(mpd_decoder, inStream, 1, sampleBuffer,
sampleBufferLen, file_time,
bitRate, NULL);
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP)
break;
}
free(seekTable);
faacDecClose(decoder);
mp4ff_close(mp4fh);
free(mp4cb);
if (!initialized)
return -1;
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK && seeking) {
decoder_clear(mpd_decoder);
decoder_command_finished(mpd_decoder);
}
decoder_flush(mpd_decoder);
return 0;
}
static struct tag *mp4DataDup(char *file, int *mp4MetadataFound)
{
struct tag *ret = NULL;
InputStream inStream;
mp4ff_t *mp4fh;
mp4ff_callback_t *callback;
int32_t track;
int32_t file_time;
int32_t scale;
int i;
*mp4MetadataFound = 0;
if (openInputStream(&inStream, file) < 0) {
DEBUG("mp4DataDup: Failed to open file: %s\n", file);
return NULL;
}
callback = xmalloc(sizeof(mp4ff_callback_t));
callback->read = mp4_inputStreamReadCallback;
callback->seek = mp4_inputStreamSeekCallback;
callback->user_data = &inStream;
mp4fh = mp4ff_open_read(callback);
if (!mp4fh) {
free(callback);
closeInputStream(&inStream);
return NULL;
}
track = mp4_getAACTrack(mp4fh);
if (track < 0) {
mp4ff_close(mp4fh);
closeInputStream(&inStream);
free(callback);
return NULL;
}
ret = tag_new();
file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track);
scale = mp4ff_time_scale(mp4fh, track);
if (scale < 0) {
mp4ff_close(mp4fh);
closeInputStream(&inStream);
free(callback);
tag_free(ret);
return NULL;
}
ret->time = ((float)file_time) / scale + 0.5;
for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) {
char *item;
char *value;
mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
if (0 == strcasecmp("artist", item)) {
tag_add_item(ret, TAG_ITEM_ARTIST, value);
*mp4MetadataFound = 1;
} else if (0 == strcasecmp("title", item)) {
tag_add_item(ret, TAG_ITEM_TITLE, value);
*mp4MetadataFound = 1;
} else if (0 == strcasecmp("album", item)) {
tag_add_item(ret, TAG_ITEM_ALBUM, value);
*mp4MetadataFound = 1;
} else if (0 == strcasecmp("track", item)) {
tag_add_item(ret, TAG_ITEM_TRACK, value);
*mp4MetadataFound = 1;
} else if (0 == strcasecmp("disc", item)) { /* Is that the correct id? */
tag_add_item(ret, TAG_ITEM_DISC, value);
*mp4MetadataFound = 1;
} else if (0 == strcasecmp("genre", item)) {
tag_add_item(ret, TAG_ITEM_GENRE, value);
*mp4MetadataFound = 1;
} else if (0 == strcasecmp("date", item)) {
tag_add_item(ret, TAG_ITEM_DATE, value);
*mp4MetadataFound = 1;
}
free(item);
free(value);
}
mp4ff_close(mp4fh);
closeInputStream(&inStream);
return ret;
}
static struct tag *mp4TagDup(char *file)
{
struct tag *ret = NULL;
int mp4MetadataFound = 0;
ret = mp4DataDup(file, &mp4MetadataFound);
if (!ret)
return NULL;
if (!mp4MetadataFound) {
struct tag *temp = tag_id3_load(file);
if (temp) {
temp->time = ret->time;
tag_free(ret);
ret = temp;
}
}
return ret;
}
static const char *mp4_suffixes[] = { "m4a", "mp4", NULL };
static const char *mp4_mimeTypes[] = { "audio/mp4", "audio/m4a", NULL };
struct decoder_plugin mp4Plugin = {
.name = "mp4",
.stream_decode = mp4_decode,
.tag_dup = mp4TagDup,
.stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL,
.suffixes = mp4_suffixes,
.mime_types = mp4_mimeTypes,
};

308
src/decoder/mpc_plugin.c Normal file
View File

@@ -0,0 +1,308 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#include "../utils.h"
#include "../log.h"
#include <mpcdec/mpcdec.h>
typedef struct _MpcCallbackData {
InputStream *inStream;
struct decoder *decoder;
} MpcCallbackData;
static mpc_int32_t mpc_read_cb(void *vdata, void *ptr, mpc_int32_t size)
{
MpcCallbackData *data = (MpcCallbackData *) vdata;
return decoder_read(data->decoder, data->inStream, ptr, size);
}
static mpc_bool_t mpc_seek_cb(void *vdata, mpc_int32_t offset)
{
MpcCallbackData *data = (MpcCallbackData *) vdata;
return seekInputStream(data->inStream, offset, SEEK_SET) < 0 ? 0 : 1;
}
static mpc_int32_t mpc_tell_cb(void *vdata)
{
MpcCallbackData *data = (MpcCallbackData *) vdata;
return (long)(data->inStream->offset);
}
static mpc_bool_t mpc_canseek_cb(void *vdata)
{
MpcCallbackData *data = (MpcCallbackData *) vdata;
return data->inStream->seekable;
}
static mpc_int32_t mpc_getsize_cb(void *vdata)
{
MpcCallbackData *data = (MpcCallbackData *) vdata;
return data->inStream->size;
}
/* this _looks_ performance-critical, don't de-inline -- eric */
static inline int16_t convertSample(MPC_SAMPLE_FORMAT sample)
{
/* only doing 16-bit audio for now */
int32_t val;
const int clip_min = -1 << (16 - 1);
const int clip_max = (1 << (16 - 1)) - 1;
#ifdef MPC_FIXED_POINT
const int shift = 16 - MPC_FIXED_POINT_SCALE_SHIFT;
if (sample > 0) {
sample <<= shift;
} else if (shift < 0) {
sample >>= -shift;
}
val = sample;
#else
const int float_scale = 1 << (16 - 1);
val = sample * float_scale;
#endif
if (val < clip_min)
val = clip_min;
else if (val > clip_max)
val = clip_max;
return val;
}
static int mpc_decode(struct decoder * mpd_decoder, InputStream * inStream)
{
mpc_decoder decoder;
mpc_reader reader;
mpc_streaminfo info;
struct audio_format audio_format;
MpcCallbackData data;
MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH];
int eof = 0;
long ret;
#define MPC_CHUNK_SIZE 4096
char chunk[MPC_CHUNK_SIZE];
int chunkpos = 0;
long bitRate = 0;
int16_t *s16 = (int16_t *) chunk;
unsigned long samplePos = 0;
mpc_uint32_t vbrUpdateAcc;
mpc_uint32_t vbrUpdateBits;
float total_time;
int i;
ReplayGainInfo *replayGainInfo = NULL;
data.inStream = inStream;
data.decoder = mpd_decoder;
reader.read = mpc_read_cb;
reader.seek = mpc_seek_cb;
reader.tell = mpc_tell_cb;
reader.get_size = mpc_getsize_cb;
reader.canseek = mpc_canseek_cb;
reader.data = &data;
mpc_streaminfo_init(&info);
if ((ret = mpc_streaminfo_read(&info, &reader)) != ERROR_CODE_OK) {
if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) {
ERROR("Not a valid musepack stream\n");
return -1;
}
return 0;
}
mpc_decoder_setup(&decoder, &reader);
if (!mpc_decoder_initialize(&decoder, &info)) {
if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) {
ERROR("Not a valid musepack stream\n");
return -1;
}
return 0;
}
audio_format.bits = 16;
audio_format.channels = info.channels;
audio_format.sample_rate = info.sample_freq;
replayGainInfo = newReplayGainInfo();
replayGainInfo->albumGain = info.gain_album * 0.01;
replayGainInfo->albumPeak = info.peak_album / 32767.0;
replayGainInfo->trackGain = info.gain_title * 0.01;
replayGainInfo->trackPeak = info.peak_title / 32767.0;
decoder_initialized(mpd_decoder, &audio_format,
mpc_streaminfo_get_length(&info));
while (!eof) {
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
samplePos = decoder_seek_where(mpd_decoder) *
audio_format.sample_rate;
if (mpc_decoder_seek_sample(&decoder, samplePos)) {
decoder_clear(mpd_decoder);
s16 = (int16_t *) chunk;
chunkpos = 0;
decoder_command_finished(mpd_decoder);
} else
decoder_seek_error(mpd_decoder);
}
vbrUpdateAcc = 0;
vbrUpdateBits = 0;
ret = mpc_decoder_decode(&decoder, sample_buffer,
&vbrUpdateAcc, &vbrUpdateBits);
if (ret <= 0 || decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) {
eof = 1;
break;
}
samplePos += ret;
/* ret is in samples, and we have stereo */
ret *= 2;
for (i = 0; i < ret; i++) {
/* 16 bit audio again */
*s16 = convertSample(sample_buffer[i]);
chunkpos += 2;
s16++;
if (chunkpos >= MPC_CHUNK_SIZE) {
total_time = ((float)samplePos) /
audio_format.sample_rate;
bitRate = vbrUpdateBits *
audio_format.sample_rate / 1152 / 1000;
decoder_data(mpd_decoder, inStream,
inStream->seekable,
chunk, chunkpos,
total_time,
bitRate, replayGainInfo);
chunkpos = 0;
s16 = (int16_t *) chunk;
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) {
eof = 1;
break;
}
}
}
}
if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP &&
chunkpos > 0) {
total_time = ((float)samplePos) / audio_format.sample_rate;
bitRate =
vbrUpdateBits * audio_format.sample_rate / 1152 / 1000;
decoder_data(mpd_decoder, NULL, inStream->seekable,
chunk, chunkpos, total_time, bitRate,
replayGainInfo);
}
decoder_flush(mpd_decoder);
freeReplayGainInfo(replayGainInfo);
return 0;
}
static float mpcGetTime(char *file)
{
InputStream inStream;
float total_time = -1;
mpc_reader reader;
mpc_streaminfo info;
MpcCallbackData data;
data.inStream = &inStream;
data.decoder = NULL;
reader.read = mpc_read_cb;
reader.seek = mpc_seek_cb;
reader.tell = mpc_tell_cb;
reader.get_size = mpc_getsize_cb;
reader.canseek = mpc_canseek_cb;
reader.data = &data;
mpc_streaminfo_init(&info);
if (openInputStream(&inStream, file) < 0) {
DEBUG("mpcGetTime: Failed to open file: %s\n", file);
return -1;
}
if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) {
closeInputStream(&inStream);
return -1;
}
total_time = mpc_streaminfo_get_length(&info);
closeInputStream(&inStream);
return total_time;
}
static struct tag *mpcTagDup(char *file)
{
struct tag *ret = NULL;
float total_time = mpcGetTime(file);
if (total_time < 0) {
DEBUG("mpcTagDup: Failed to get Songlength of file: %s\n",
file);
return NULL;
}
ret = tag_ape_load(file);
if (!ret)
ret = tag_id3_load(file);
if (!ret)
ret = tag_new();
ret->time = total_time;
return ret;
}
static const char *mpcSuffixes[] = { "mpc", NULL };
struct decoder_plugin mpcPlugin = {
.name = "mpc",
.stream_decode = mpc_decode,
.tag_dup = mpcTagDup,
.stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
.suffixes = mpcSuffixes,
};

View File

@@ -0,0 +1,355 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* OggFLAC support (half-stolen from flac_plugin.c :))
* (c) 2005 by Eric Wong <normalperson@yhbt.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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "_flac_common.h"
#include "_ogg_common.h"
#include "../utils.h"
#include "../log.h"
#include <OggFLAC/seekable_stream_decoder.h>
static void oggflac_cleanup(FlacData * data,
OggFLAC__SeekableStreamDecoder * decoder)
{
if (data->replayGainInfo)
freeReplayGainInfo(data->replayGainInfo);
if (decoder)
OggFLAC__seekable_stream_decoder_delete(decoder);
}
static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(mpd_unused const
OggFLAC__SeekableStreamDecoder
* decoder,
FLAC__byte buf[],
unsigned *bytes,
void *fdata)
{
FlacData *data = (FlacData *) fdata;
size_t r;
r = decoder_read(data->decoder, data->inStream, (void *)buf, *bytes);
*bytes = r;
if (r == 0 && !inputStreamAtEOF(data->inStream) &&
decoder_get_command(data->decoder) == DECODE_COMMAND_NONE)
return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR;
return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
}
static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(mpd_unused const
OggFLAC__SeekableStreamDecoder
* decoder,
FLAC__uint64 offset,
void *fdata)
{
FlacData *data = (FlacData *) fdata;
if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) {
return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
}
return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK;
}
static OggFLAC__SeekableStreamDecoderTellStatus of_tell_cb(mpd_unused const
OggFLAC__SeekableStreamDecoder
* decoder,
FLAC__uint64 *
offset, void *fdata)
{
FlacData *data = (FlacData *) fdata;
*offset = (long)(data->inStream->offset);
return OggFLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
}
static OggFLAC__SeekableStreamDecoderLengthStatus of_length_cb(mpd_unused const
OggFLAC__SeekableStreamDecoder
* decoder,
FLAC__uint64 *
length,
void *fdata)
{
FlacData *data = (FlacData *) fdata;
*length = (size_t) (data->inStream->size);
return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
}
static FLAC__bool of_EOF_cb(mpd_unused const OggFLAC__SeekableStreamDecoder * decoder,
void *fdata)
{
FlacData *data = (FlacData *) fdata;
return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
inputStreamAtEOF(data->inStream);
}
static void of_error_cb(mpd_unused const OggFLAC__SeekableStreamDecoder * decoder,
FLAC__StreamDecoderErrorStatus status, void *fdata)
{
flac_error_common_cb("oggflac", status, (FlacData *) fdata);
}
static void oggflacPrintErroredState(OggFLAC__SeekableStreamDecoderState state)
{
switch (state) {
case OggFLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
ERROR("oggflac allocation error\n");
break;
case OggFLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
ERROR("oggflac read error\n");
break;
case OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
ERROR("oggflac seek error\n");
break;
case OggFLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
ERROR("oggflac seekable stream error\n");
break;
case OggFLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
ERROR("oggflac decoder already initialized\n");
break;
case OggFLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
ERROR("invalid oggflac callback\n");
break;
case OggFLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
ERROR("oggflac decoder uninitialized\n");
break;
case OggFLAC__SEEKABLE_STREAM_DECODER_OK:
case OggFLAC__SEEKABLE_STREAM_DECODER_SEEKING:
case OggFLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
break;
}
}
static FLAC__StreamDecoderWriteStatus oggflacWrite(mpd_unused const
OggFLAC__SeekableStreamDecoder
* decoder,
const FLAC__Frame * frame,
const FLAC__int32 *
const buf[], void *vdata)
{
FlacData *data = (FlacData *) vdata;
FLAC__uint32 samples = frame->header.blocksize;
float timeChange;
timeChange = ((float)samples) / frame->header.sample_rate;
data->time += timeChange;
return flac_common_write(data, frame, buf);
}
/* used by TagDup */
static void of_metadata_dup_cb(mpd_unused const OggFLAC__SeekableStreamDecoder * decoder,
const FLAC__StreamMetadata * block, void *vdata)
{
FlacData *data = (FlacData *) vdata;
switch (block->type) {
case FLAC__METADATA_TYPE_STREAMINFO:
if (!data->tag)
data->tag = tag_new();
data->tag->time = ((float)block->data.stream_info.
total_samples) /
block->data.stream_info.sample_rate + 0.5;
return;
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
copyVorbisCommentBlockToMpdTag(block, data->tag);
default:
break;
}
}
/* used by decode */
static void of_metadata_decode_cb(mpd_unused const OggFLAC__SeekableStreamDecoder * dec,
const FLAC__StreamMetadata * block,
void *vdata)
{
flac_metadata_common_cb(block, (FlacData *) vdata);
}
static OggFLAC__SeekableStreamDecoder
* full_decoder_init_and_read_metadata(FlacData * data,
unsigned int metadata_only)
{
OggFLAC__SeekableStreamDecoder *decoder = NULL;
unsigned int s = 1;
if (!(decoder = OggFLAC__seekable_stream_decoder_new()))
return NULL;
if (metadata_only) {
s &= OggFLAC__seekable_stream_decoder_set_metadata_callback
(decoder, of_metadata_dup_cb);
s &= OggFLAC__seekable_stream_decoder_set_metadata_respond
(decoder, FLAC__METADATA_TYPE_STREAMINFO);
} else {
s &= OggFLAC__seekable_stream_decoder_set_metadata_callback
(decoder, of_metadata_decode_cb);
}
s &= OggFLAC__seekable_stream_decoder_set_read_callback(decoder,
of_read_cb);
s &= OggFLAC__seekable_stream_decoder_set_seek_callback(decoder,
of_seek_cb);
s &= OggFLAC__seekable_stream_decoder_set_tell_callback(decoder,
of_tell_cb);
s &= OggFLAC__seekable_stream_decoder_set_length_callback(decoder,
of_length_cb);
s &= OggFLAC__seekable_stream_decoder_set_eof_callback(decoder,
of_EOF_cb);
s &= OggFLAC__seekable_stream_decoder_set_write_callback(decoder,
oggflacWrite);
s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(decoder,
FLAC__METADATA_TYPE_VORBIS_COMMENT);
s &= OggFLAC__seekable_stream_decoder_set_error_callback(decoder,
of_error_cb);
s &= OggFLAC__seekable_stream_decoder_set_client_data(decoder,
(void *)data);
if (!s) {
ERROR("oggflac problem before init()\n");
goto fail;
}
if (OggFLAC__seekable_stream_decoder_init(decoder) !=
OggFLAC__SEEKABLE_STREAM_DECODER_OK) {
ERROR("oggflac problem doing init()\n");
goto fail;
}
if (!OggFLAC__seekable_stream_decoder_process_until_end_of_metadata
(decoder)) {
ERROR("oggflac problem reading metadata\n");
goto fail;
}
return decoder;
fail:
oggflacPrintErroredState(OggFLAC__seekable_stream_decoder_get_state
(decoder));
OggFLAC__seekable_stream_decoder_delete(decoder);
return NULL;
}
/* public functions: */
static struct tag *oggflac_TagDup(char *file)
{
InputStream inStream;
OggFLAC__SeekableStreamDecoder *decoder;
FlacData data;
if (openInputStream(&inStream, file) < 0)
return NULL;
if (ogg_stream_type_detect(&inStream) != FLAC) {
closeInputStream(&inStream);
return NULL;
}
init_FlacData(&data, NULL, &inStream);
/* errors here won't matter,
* data.tag will be set or unset, that's all we care about */
decoder = full_decoder_init_and_read_metadata(&data, 1);
oggflac_cleanup(&data, decoder);
closeInputStream(&inStream);
return data.tag;
}
static bool oggflac_try_decode(InputStream * inStream)
{
if (!inStream->seekable)
/* we cannot seek after the detection, so don't bother
checking */
return true;
return ogg_stream_type_detect(inStream) == FLAC;
}
static int oggflac_decode(struct decoder * mpd_decoder, InputStream * inStream)
{
OggFLAC__SeekableStreamDecoder *decoder = NULL;
FlacData data;
int ret = 0;
init_FlacData(&data, mpd_decoder, inStream);
if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) {
ret = -1;
goto fail;
}
decoder_initialized(mpd_decoder, &data.audio_format, data.total_time);
while (1) {
OggFLAC__seekable_stream_decoder_process_single(decoder);
if (OggFLAC__seekable_stream_decoder_get_state(decoder) !=
OggFLAC__SEEKABLE_STREAM_DECODER_OK) {
break;
}
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
FLAC__uint64 sampleToSeek = decoder_seek_where(mpd_decoder) *
data.audio_format.sample_rate + 0.5;
if (OggFLAC__seekable_stream_decoder_seek_absolute
(decoder, sampleToSeek)) {
decoder_clear(mpd_decoder);
data.time = ((float)sampleToSeek) /
data.audio_format.sample_rate;
data.position = 0;
decoder_command_finished(mpd_decoder);
} else
decoder_seek_error(mpd_decoder);
}
}
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) {
oggflacPrintErroredState
(OggFLAC__seekable_stream_decoder_get_state(decoder));
OggFLAC__seekable_stream_decoder_finish(decoder);
}
fail:
oggflac_cleanup(&data, decoder);
return ret;
}
static const char *oggflac_Suffixes[] = { "ogg", "oga",NULL };
static const char *oggflac_mime_types[] = { "audio/x-flac+ogg",
"application/ogg",
"application/x-ogg",
NULL };
struct decoder_plugin oggflacPlugin = {
.name = "oggflac",
.try_decode = oggflac_try_decode,
.stream_decode = oggflac_decode,
.tag_dup = oggflac_TagDup,
.stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
.suffixes = oggflac_Suffixes,
.mime_types = oggflac_mime_types
};

View File

@@ -0,0 +1,387 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */
#include "_ogg_common.h"
#include "../utils.h"
#include "../log.h"
#ifndef HAVE_TREMOR
#include <vorbis/vorbisfile.h>
#else
#include <tremor/ivorbisfile.h>
/* Macros to make Tremor's API look like libogg. Tremor always
returns host-byte-order 16-bit signed data, and uses integer
milliseconds where libogg uses double seconds.
*/
#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \
ov_read(VF, BUFFER, LENGTH, BITSTREAM)
#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000)
#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000)
#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
#endif /* HAVE_TREMOR */
#ifdef WORDS_BIGENDIAN
#define OGG_DECODE_USE_BIGENDIAN 1
#else
#define OGG_DECODE_USE_BIGENDIAN 0
#endif
typedef struct _OggCallbackData {
InputStream *inStream;
struct decoder *decoder;
} OggCallbackData;
static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata)
{
size_t ret;
OggCallbackData *data = (OggCallbackData *) vdata;
ret = decoder_read(data->decoder, data->inStream, ptr, size * nmemb);
errno = 0;
/*if(ret<0) errno = ((InputStream *)inStream)->error; */
return ret / size;
}
static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence)
{
const OggCallbackData *data = (const OggCallbackData *) vdata;
if(decoder_get_command(data->decoder) == DECODE_COMMAND_STOP)
return -1;
return seekInputStream(data->inStream, offset, whence);
}
/* TODO: check Ogg libraries API and see if we can just not have this func */
static int ogg_close_cb(mpd_unused void *vdata)
{
return 0;
}
static long ogg_tell_cb(void *vdata)
{
const OggCallbackData *data = (const OggCallbackData *) vdata;
return (long)(data->inStream->offset);
}
static const char *ogg_parseComment(const char *comment, const char *needle)
{
int len = strlen(needle);
if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') {
return comment + len + 1;
}
return NULL;
}
static void ogg_getReplayGainInfo(char **comments, ReplayGainInfo ** infoPtr)
{
const char *temp;
int found = 0;
if (*infoPtr)
freeReplayGainInfo(*infoPtr);
*infoPtr = newReplayGainInfo();
while (*comments) {
if ((temp =
ogg_parseComment(*comments, "replaygain_track_gain"))) {
(*infoPtr)->trackGain = atof(temp);
found = 1;
} else if ((temp = ogg_parseComment(*comments,
"replaygain_album_gain"))) {
(*infoPtr)->albumGain = atof(temp);
found = 1;
} else if ((temp = ogg_parseComment(*comments,
"replaygain_track_peak"))) {
(*infoPtr)->trackPeak = atof(temp);
found = 1;
} else if ((temp = ogg_parseComment(*comments,
"replaygain_album_peak"))) {
(*infoPtr)->albumPeak = atof(temp);
found = 1;
}
comments++;
}
if (!found) {
freeReplayGainInfo(*infoPtr);
*infoPtr = NULL;
}
}
static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
static unsigned int ogg_parseCommentAddToTag(char *comment,
unsigned int itemType,
struct tag ** tag)
{
const char *needle;
unsigned int len;
switch (itemType) {
case TAG_ITEM_TRACK:
needle = VORBIS_COMMENT_TRACK_KEY;
break;
case TAG_ITEM_DISC:
needle = VORBIS_COMMENT_DISC_KEY;
break;
default:
needle = mpdTagItemKeys[itemType];
}
len = strlen(needle);
if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') {
if (!*tag)
*tag = tag_new();
tag_add_item(*tag, itemType, comment + len + 1);
return 1;
}
return 0;
}
static struct tag *oggCommentsParse(char **comments)
{
struct tag *tag = NULL;
while (*comments) {
int j;
for (j = TAG_NUM_OF_ITEM_TYPES; --j >= 0;) {
if (ogg_parseCommentAddToTag(*comments, j, &tag))
break;
}
comments++;
}
return tag;
}
static void putOggCommentsIntoOutputBuffer(char *streamName,
char **comments)
{
struct tag *tag;
tag = oggCommentsParse(comments);
if (!tag && streamName) {
tag = tag_new();
}
if (!tag)
return;
if (streamName) {
tag_clear_items_by_type(tag, TAG_ITEM_NAME);
tag_add_item(tag, TAG_ITEM_NAME, streamName);
}
tag_free(tag);
}
/* public */
static int oggvorbis_decode(struct decoder * decoder, InputStream * inStream)
{
OggVorbis_File vf;
ov_callbacks callbacks;
OggCallbackData data;
struct audio_format audio_format;
int current_section;
int prev_section = -1;
long ret;
#define OGG_CHUNK_SIZE 4096
char chunk[OGG_CHUNK_SIZE];
int chunkpos = 0;
long bitRate = 0;
long test;
ReplayGainInfo *replayGainInfo = NULL;
char **comments;
const char *errorStr;
int initialized = 0;
data.inStream = inStream;
data.decoder = decoder;
callbacks.read_func = ogg_read_cb;
callbacks.seek_func = ogg_seek_cb;
callbacks.close_func = ogg_close_cb;
callbacks.tell_func = ogg_tell_cb;
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
return 0;
switch (ret) {
case OV_EREAD:
errorStr = "read error";
break;
case OV_ENOTVORBIS:
errorStr = "not vorbis stream";
break;
case OV_EVERSION:
errorStr = "vorbis version mismatch";
break;
case OV_EBADHEADER:
errorStr = "invalid vorbis header";
break;
case OV_EFAULT:
errorStr = "internal logic error";
break;
default:
errorStr = "unknown error";
break;
}
ERROR("Error decoding Ogg Vorbis stream: %s\n",
errorStr);
return -1;
}
audio_format.bits = 16;
while (1) {
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
double seek_where = decoder_seek_where(decoder);
if (0 == ov_time_seek_page(&vf, seek_where)) {
decoder_clear(decoder);
chunkpos = 0;
decoder_command_finished(decoder);
} else
decoder_seek_error(decoder);
}
ret = ov_read(&vf, chunk + chunkpos,
OGG_CHUNK_SIZE - chunkpos,
OGG_DECODE_USE_BIGENDIAN, 2, 1, &current_section);
if (current_section != prev_section) {
/*printf("new song!\n"); */
vorbis_info *vi = ov_info(&vf, -1);
audio_format.channels = vi->channels;
audio_format.sample_rate = vi->rate;
if (!initialized) {
float total_time = ov_time_total(&vf, -1);
if (total_time < 0)
total_time = 0;
decoder_initialized(decoder, &audio_format,
total_time);
initialized = 1;
}
comments = ov_comment(&vf, -1)->user_comments;
putOggCommentsIntoOutputBuffer(inStream->metaName,
comments);
ogg_getReplayGainInfo(comments, &replayGainInfo);
}
prev_section = current_section;
if (ret <= 0) {
if (ret == OV_HOLE) /* bad packet */
ret = 0;
else /* break on EOF or other error */
break;
}
chunkpos += ret;
if (chunkpos >= OGG_CHUNK_SIZE) {
if ((test = ov_bitrate_instant(&vf)) > 0) {
bitRate = test / 1000;
}
decoder_data(decoder, inStream,
inStream->seekable,
chunk, chunkpos,
ov_pcm_tell(&vf) / audio_format.sample_rate,
bitRate, replayGainInfo);
chunkpos = 0;
if (decoder_get_command(decoder) == DECODE_COMMAND_STOP)
break;
}
}
if (decoder_get_command(decoder) == DECODE_COMMAND_NONE &&
chunkpos > 0) {
decoder_data(decoder, NULL, inStream->seekable,
chunk, chunkpos,
ov_time_tell(&vf), bitRate,
replayGainInfo);
}
if (replayGainInfo)
freeReplayGainInfo(replayGainInfo);
ov_clear(&vf);
decoder_flush(decoder);
return 0;
}
static struct tag *oggvorbis_TagDup(char *file)
{
struct tag *ret;
FILE *fp;
OggVorbis_File vf;
fp = fopen(file, "r");
if (!fp) {
DEBUG("oggvorbis_TagDup: Failed to open file: '%s', %s\n",
file, strerror(errno));
return NULL;
}
if (ov_open(fp, &vf, NULL, 0) < 0) {
fclose(fp);
return NULL;
}
ret = oggCommentsParse(ov_comment(&vf, -1)->user_comments);
if (!ret)
ret = tag_new();
ret->time = (int)(ov_time_total(&vf, -1) + 0.5);
ov_clear(&vf);
return ret;
}
static bool oggvorbis_try_decode(InputStream * inStream)
{
if (!inStream->seekable)
/* we cannot seek after the detection, so don't bother
checking */
return true;
return ogg_stream_type_detect(inStream) == VORBIS;
}
static const char *oggvorbis_Suffixes[] = { "ogg","oga", NULL };
static const char *oggvorbis_MimeTypes[] = { "application/ogg",
"audio/x-vorbis+ogg",
"application/x-ogg",
NULL };
struct decoder_plugin oggvorbisPlugin = {
.name = "oggvorbis",
.try_decode = oggvorbis_try_decode,
.stream_decode = oggvorbis_decode,
.tag_dup = oggvorbis_TagDup,
.stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
.suffixes = oggvorbis_Suffixes,
.mime_types = oggvorbis_MimeTypes
};

View File

@@ -0,0 +1,574 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* WavPack support added by Laszlo Ashin <kodest@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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../decoder_api.h"
#include "../utils.h"
#include "../log.h"
#include "../path.h"
#include <wavpack/wavpack.h>
/* 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;
int 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 },
{ NULL, 0 }
};
/*
* This function has been borrowed from the tiny player found on
* wavpack.com. Modifications were required because mpd only handles
* max 16 bit samples.
*/
static void format_samples_int(int Bps, void *buffer, uint32_t samcnt)
{
int32_t temp;
uchar *dst = (uchar *)buffer;
int32_t *src = (int32_t *)buffer;
switch (Bps) {
case 1:
while (samcnt--)
*dst++ = *src++;
break;
case 2:
while (samcnt--) {
temp = *src++;
#ifdef WORDS_BIGENDIAN
*dst++ = (uchar)(temp >> 8);
*dst++ = (uchar)(temp);
#else
*dst++ = (uchar)(temp);
*dst++ = (uchar)(temp >> 8);
#endif
}
break;
case 3:
/* downscale to 16 bits */
while (samcnt--) {
temp = *src++;
#ifdef WORDS_BIGENDIAN
*dst++ = (uchar)(temp >> 16);
*dst++ = (uchar)(temp >> 8);
#else
*dst++ = (uchar)(temp >> 8);
*dst++ = (uchar)(temp >> 16);
#endif
}
break;
case 4:
/* downscale to 16 bits */
while (samcnt--) {
temp = *src++;
#ifdef WORDS_BIGENDIAN
*dst++ = (uchar)(temp >> 24);
*dst++ = (uchar)(temp >> 16);
#else
*dst++ = (uchar)(temp >> 16);
*dst++ = (uchar)(temp >> 24);
#endif
}
break;
}
}
/*
* This function converts floating point sample data to 16 bit integer.
*/
static void format_samples_float(mpd_unused int Bps, void *buffer,
uint32_t samcnt)
{
int16_t *dst = (int16_t *)buffer;
float *src = (float *)buffer;
while (samcnt--) {
*dst++ = (int16_t)(*src++);
}
}
/*
* This does the main decoding thing.
* Requires an already opened WavpackContext.
*/
static void wavpack_decode(struct decoder * decoder,
WavpackContext *wpc, int canseek,
ReplayGainInfo *replayGainInfo)
{
struct audio_format audio_format;
void (*format_samples)(int Bps, void *buffer, uint32_t samcnt);
char chunk[CHUNK_SIZE];
float file_time;
int samplesreq, samplesgot;
int allsamples;
int position, outsamplesize;
int Bps;
audio_format.sample_rate = WavpackGetSampleRate(wpc);
audio_format.channels = WavpackGetReducedChannels(wpc);
audio_format.bits = WavpackGetBitsPerSample(wpc);
if (audio_format.bits > 16)
audio_format.bits = 16;
if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT)
format_samples = format_samples_float;
else
format_samples = format_samples_int;
/*
if ((WavpackGetMode(wpc) & MODE_WVC) == MODE_WVC)
ERROR("decoding WITH wvc!!!\n");
else
ERROR("decoding without wvc\n");
*/
allsamples = WavpackGetNumSamples(wpc);
Bps = WavpackGetBytesPerSample(wpc);
outsamplesize = Bps;
if (outsamplesize > 2)
outsamplesize = 2;
outsamplesize *= audio_format.channels;
samplesreq = sizeof(chunk) / (4 * audio_format.channels);
decoder_initialized(decoder, &audio_format,
(float)allsamples / audio_format.sample_rate);
position = 0;
do {
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
if (canseek) {
int where;
decoder_clear(decoder);
where = decoder_seek_where(decoder) *
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;
samplesgot = WavpackUnpackSamples(wpc,
(int32_t *)chunk, samplesreq);
if (samplesgot > 0) {
int bitrate = (int)(WavpackGetInstantBitrate(wpc) /
1000 + 0.5);
position += samplesgot;
file_time = (float)position / audio_format.sample_rate;
format_samples(Bps, chunk,
samplesgot * audio_format.channels);
decoder_data(decoder, NULL, 0, chunk,
samplesgot * outsamplesize,
file_time, bitrate,
replayGainInfo);
}
} while (samplesgot == samplesreq);
decoder_flush(decoder);
}
static char *wavpack_tag(WavpackContext *wpc, char *key)
{
char *value = NULL;
int size;
size = WavpackGetTagItem(wpc, key, NULL, 0);
if (size > 0) {
size++;
value = xmalloc(size);
if (!value)
return NULL;
WavpackGetTagItem(wpc, key, value, size);
}
return value;
}
static ReplayGainInfo *wavpack_replaygain(WavpackContext *wpc)
{
static char replaygain_track_gain[] = "replaygain_track_gain";
static char replaygain_album_gain[] = "replaygain_album_gain";
static char replaygain_track_peak[] = "replaygain_track_peak";
static char replaygain_album_peak[] = "replaygain_album_peak";
ReplayGainInfo *replayGainInfo;
int found = 0;
char *value;
replayGainInfo = newReplayGainInfo();
value = wavpack_tag(wpc, replaygain_track_gain);
if (value) {
replayGainInfo->trackGain = atof(value);
free(value);
found = 1;
}
value = wavpack_tag(wpc, replaygain_album_gain);
if (value) {
replayGainInfo->albumGain = atof(value);
free(value);
found = 1;
}
value = wavpack_tag(wpc, replaygain_track_peak);
if (value) {
replayGainInfo->trackPeak = atof(value);
free(value);
found = 1;
}
value = wavpack_tag(wpc, replaygain_album_peak);
if (value) {
replayGainInfo->albumPeak = atof(value);
free(value);
found = 1;
}
if (found)
return replayGainInfo;
freeReplayGainInfo(replayGainInfo);
return NULL;
}
/*
* Reads metainfo from the specified file.
*/
static struct tag *wavpack_tagdup(char *fname)
{
WavpackContext *wpc;
struct tag *tag;
char error[ERRORLEN];
char *s;
int ssize;
int i, j;
wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
if (wpc == NULL) {
ERROR("failed to open WavPack file \"%s\": %s\n", fname, error);
return NULL;
}
tag = tag_new();
tag->time =
(float)WavpackGetNumSamples(wpc) / WavpackGetSampleRate(wpc);
ssize = 0;
s = NULL;
for (i = 0; tagtypes[i].name != NULL; ++i) {
j = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0);
if (j > 0) {
++j;
if (s == NULL) {
s = xmalloc(j);
if (s == NULL) break;
ssize = j;
} else if (j > ssize) {
char *t = (char *)xrealloc(s, j);
if (t == NULL) break;
ssize = j;
s = t;
}
WavpackGetTagItem(wpc, tagtypes[i].name, s, j);
tag_add_item(tag, tagtypes[i].type, s);
}
}
if (s != NULL)
free(s);
WavpackCloseFile(wpc);
return tag;
}
/*
* mpd InputStream <=> WavpackStreamReader wrapper callbacks
*/
/* This struct is needed for per-stream last_byte storage. */
typedef struct {
struct decoder *decoder;
InputStream *is;
/* Needed for push_back_byte() */
int last_byte;
} InputStreamPlus;
static int32_t read_bytes(void *id, void *data, int32_t bcount)
{
InputStreamPlus *isp = (InputStreamPlus *)id;
uint8_t *buf = (uint8_t *)data;
int32_t i = 0;
if (isp->last_byte != EOF) {
*buf++ = isp->last_byte;
isp->last_byte = EOF;
--bcount;
++i;
}
return i + decoder_read(isp->decoder, isp->is, buf, bcount);
}
static uint32_t get_pos(void *id)
{
return ((InputStreamPlus *)id)->is->offset;
}
static int set_pos_abs(void *id, uint32_t pos)
{
return seekInputStream(((InputStreamPlus *)id)->is, pos, SEEK_SET);
}
static int set_pos_rel(void *id, int32_t delta, int mode)
{
return seekInputStream(((InputStreamPlus *)id)->is, delta, mode);
}
static int push_back_byte(void *id, int c)
{
((InputStreamPlus *)id)->last_byte = c;
return 1;
}
static uint32_t get_length(void *id)
{
return ((InputStreamPlus *)id)->is->size;
}
static int can_seek(void *id)
{
return ((InputStreamPlus *)id)->is->seekable;
}
static WavpackStreamReader mpd_is_reader = {
.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 = NULL /* no need to write edited tags */
};
static void
initInputStreamPlus(InputStreamPlus *isp, struct decoder *decoder,
InputStream *is)
{
isp->decoder = decoder;
isp->is = is;
isp->last_byte = EOF;
}
/*
* Tries to decode the specified stream, and gives true if managed to do it.
*/
static bool wavpack_trydecode(InputStream *is)
{
char error[ERRORLEN];
WavpackContext *wpc;
InputStreamPlus isp;
initInputStreamPlus(&isp, NULL, is);
wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, NULL, error,
OPEN_STREAMING, 0);
if (wpc == NULL)
return false;
WavpackCloseFile(wpc);
/* Seek it back in order to play from the first byte. */
seekInputStream(is, 0, SEEK_SET);
return true;
}
static int wavpack_open_wvc(struct decoder *decoder,
InputStream *is_wvc)
{
char tmp[MPD_PATH_MAX];
const char *utf8url;
size_t len;
char *wvc_url = NULL;
int ret;
/*
* As we use dc->utf8url, this function will be bad for
* single files. utf8url is not absolute file path :/
*/
utf8url = decoder_get_url(decoder, tmp);
if (utf8url == NULL)
return 0;
len = strlen(utf8url);
if (!len)
return 0;
wvc_url = (char *)xmalloc(len + 2); /* +2: 'c' and EOS */
if (wvc_url == NULL)
return 0;
memcpy(wvc_url, utf8url, len);
wvc_url[len] = 'c';
wvc_url[len + 1] = '\0';
ret = openInputStream(is_wvc, wvc_url);
free(wvc_url);
if (ret)
return 0;
/*
* And we try to buffer in order to get know
* about a possible 404 error.
*/
for (;;) {
if (inputStreamAtEOF(is_wvc)) {
/*
* EOF is reached even without
* a single byte is read...
* So, this is not good :/
*/
closeInputStream(is_wvc);
return 0;
}
if (bufferInputStream(is_wvc) >= 0)
return 1;
if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) {
closeInputStream(is_wvc);
return 0;
}
/* Save some CPU */
my_usleep(1000);
}
}
/*
* Decodes a stream.
*/
static int wavpack_streamdecode(struct decoder * decoder, InputStream *is)
{
char error[ERRORLEN];
WavpackContext *wpc;
InputStream is_wvc;
int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE /*| OPEN_STREAMING*/;
InputStreamPlus isp, isp_wvc;
if (wavpack_open_wvc(decoder, &is_wvc)) {
initInputStreamPlus(&isp_wvc, decoder, &is_wvc);
open_flags |= OPEN_WVC;
}
initInputStreamPlus(&isp, decoder, is);
wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, &isp_wvc, error,
open_flags, 15);
if (wpc == NULL) {
ERROR("failed to open WavPack stream: %s\n", error);
return -1;
}
wavpack_decode(decoder, wpc, can_seek(&isp), NULL);
WavpackCloseFile(wpc);
if (open_flags & OPEN_WVC)
closeInputStream(&is_wvc);
closeInputStream(is);
return 0;
}
/*
* Decodes a file.
*/
static int wavpack_filedecode(struct decoder * decoder, char *fname)
{
char error[ERRORLEN];
WavpackContext *wpc;
ReplayGainInfo *replayGainInfo;
wpc = WavpackOpenFileInput(fname, error,
OPEN_TAGS | OPEN_WVC |
OPEN_2CH_MAX | OPEN_NORMALIZE, 15);
if (wpc == NULL) {
ERROR("failed to open WavPack file \"%s\": %s\n", fname, error);
return -1;
}
replayGainInfo = wavpack_replaygain(wpc);
wavpack_decode(decoder, wpc, 1, replayGainInfo);
if (replayGainInfo)
freeReplayGainInfo(replayGainInfo);
WavpackCloseFile(wpc);
return 0;
}
static char const *wavpackSuffixes[] = { "wv", NULL };
static char const *wavpackMimeTypes[] = { "audio/x-wavpack", NULL };
struct decoder_plugin wavpackPlugin = {
.name = "wavpack",
.try_decode = wavpack_trydecode,
.stream_decode = wavpack_streamdecode,
.file_decode = wavpack_filedecode,
.tag_dup = wavpack_tagdup,
.stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL,
.suffixes = wavpackSuffixes,
.mime_types = wavpackMimeTypes
};