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:
323
src/decoder/_flac_common.c
Normal file
323
src/decoder/_flac_common.c
Normal 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
168
src/decoder/_flac_common.h
Normal 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
49
src/decoder/_ogg_common.c
Normal 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
31
src/decoder/_ogg_common.h
Normal 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
602
src/decoder/aac_plugin.c
Normal 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
|
||||
};
|
147
src/decoder/audiofile_plugin.c
Normal file
147
src/decoder/audiofile_plugin.c
Normal 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
419
src/decoder/ffmpeg_plugin.c
Normal 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
459
src/decoder/flac_plugin.c
Normal 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
278
src/decoder/mod_plugin.c
Normal 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
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
423
src/decoder/mp4_plugin.c
Normal 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
308
src/decoder/mpc_plugin.c
Normal 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,
|
||||
};
|
355
src/decoder/oggflac_plugin.c
Normal file
355
src/decoder/oggflac_plugin.c
Normal 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
|
||||
};
|
387
src/decoder/oggvorbis_plugin.c
Normal file
387
src/decoder/oggvorbis_plugin.c
Normal 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, ¤t_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
|
||||
};
|
574
src/decoder/wavpack_plugin.c
Normal file
574
src/decoder/wavpack_plugin.c
Normal 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
|
||||
};
|
Reference in New Issue
Block a user