decoder/*: move to decoder/plugins/
This commit is contained in:
141
src/decoder/plugins/AdPlugDecoderPlugin.cxx
Normal file
141
src/decoder/plugins/AdPlugDecoderPlugin.cxx
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "AdPlugDecoderPlugin.h"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <adplug/adplug.h>
|
||||
#include <adplug/emuopl.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static unsigned sample_rate;
|
||||
|
||||
static bool
|
||||
adplug_init(const config_param ¶m)
|
||||
{
|
||||
Error error;
|
||||
|
||||
sample_rate = param.GetBlockValue("sample_rate", 48000u);
|
||||
if (!audio_check_sample_rate(sample_rate, error)) {
|
||||
LogError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
adplug_file_decode(Decoder &decoder, const char *path_fs)
|
||||
{
|
||||
CEmuopl opl(sample_rate, true, true);
|
||||
opl.init();
|
||||
|
||||
CPlayer *player = CAdPlug::factory(path_fs, &opl);
|
||||
if (player == nullptr)
|
||||
return;
|
||||
|
||||
const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2);
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
decoder_initialized(decoder, audio_format, false,
|
||||
player->songlength() / 1000.);
|
||||
|
||||
int16_t buffer[2048];
|
||||
const unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2;
|
||||
DecoderCommand cmd;
|
||||
|
||||
do {
|
||||
if (!player->update())
|
||||
break;
|
||||
|
||||
opl.update(buffer, frames_per_buffer);
|
||||
cmd = decoder_data(decoder, nullptr,
|
||||
buffer, sizeof(buffer),
|
||||
0);
|
||||
} while (cmd == DecoderCommand::NONE);
|
||||
|
||||
delete player;
|
||||
}
|
||||
|
||||
static void
|
||||
adplug_scan_tag(TagType type, const std::string &value,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
if (!value.empty())
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
type, value.c_str());
|
||||
}
|
||||
|
||||
static bool
|
||||
adplug_scan_file(const char *path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
CEmuopl opl(sample_rate, true, true);
|
||||
opl.init();
|
||||
|
||||
CPlayer *player = CAdPlug::factory(path_fs, &opl);
|
||||
if (player == nullptr)
|
||||
return false;
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
player->songlength() / 1000);
|
||||
|
||||
if (handler->tag != nullptr) {
|
||||
adplug_scan_tag(TAG_TITLE, player->gettitle(),
|
||||
handler, handler_ctx);
|
||||
adplug_scan_tag(TAG_ARTIST, player->getauthor(),
|
||||
handler, handler_ctx);
|
||||
adplug_scan_tag(TAG_COMMENT, player->getdesc(),
|
||||
handler, handler_ctx);
|
||||
}
|
||||
|
||||
delete player;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const adplug_suffixes[] = {
|
||||
"amd",
|
||||
"d00",
|
||||
"hsc",
|
||||
"laa",
|
||||
"rad",
|
||||
"raw",
|
||||
"sa2",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin adplug_decoder_plugin = {
|
||||
"adplug",
|
||||
adplug_init,
|
||||
nullptr,
|
||||
nullptr,
|
||||
adplug_file_decode,
|
||||
adplug_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
adplug_suffixes,
|
||||
nullptr,
|
||||
};
|
25
src/decoder/plugins/AdPlugDecoderPlugin.h
Normal file
25
src/decoder/plugins/AdPlugDecoderPlugin.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_ADPLUG_H
|
||||
#define MPD_DECODER_ADPLUG_H
|
||||
|
||||
extern const struct DecoderPlugin adplug_decoder_plugin;
|
||||
|
||||
#endif
|
267
src/decoder/plugins/AudiofileDecoderPlugin.cxx
Normal file
267
src/decoder/plugins/AudiofileDecoderPlugin.cxx
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "AudiofileDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <audiofile.h>
|
||||
#include <af_vfs.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
|
||||
#define CHUNK_SIZE 1020
|
||||
|
||||
static constexpr Domain audiofile_domain("audiofile");
|
||||
|
||||
static int audiofile_get_duration(const char *file)
|
||||
{
|
||||
int total_time;
|
||||
AFfilehandle af_fp = afOpenFile(file, "r", nullptr);
|
||||
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 ssize_t
|
||||
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
|
||||
{
|
||||
InputStream &is = *(InputStream *)vfile->closure;
|
||||
|
||||
Error error;
|
||||
size_t nbytes = is.LockRead(data, length, error);
|
||||
if (nbytes == 0 && error.IsDefined()) {
|
||||
LogError(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
static AFfileoffset
|
||||
audiofile_file_length(AFvirtualfile *vfile)
|
||||
{
|
||||
InputStream &is = *(InputStream *)vfile->closure;
|
||||
return is.GetSize();
|
||||
}
|
||||
|
||||
static AFfileoffset
|
||||
audiofile_file_tell(AFvirtualfile *vfile)
|
||||
{
|
||||
InputStream &is = *(InputStream *)vfile->closure;
|
||||
return is.GetOffset();
|
||||
}
|
||||
|
||||
static void
|
||||
audiofile_file_destroy(AFvirtualfile *vfile)
|
||||
{
|
||||
assert(vfile->closure != nullptr);
|
||||
|
||||
vfile->closure = nullptr;
|
||||
}
|
||||
|
||||
static AFfileoffset
|
||||
audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
|
||||
{
|
||||
InputStream &is = *(InputStream *)vfile->closure;
|
||||
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
|
||||
|
||||
Error error;
|
||||
if (is.LockSeek(offset, whence, error)) {
|
||||
return is.GetOffset();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static AFvirtualfile *
|
||||
setup_virtual_fops(InputStream &stream)
|
||||
{
|
||||
AFvirtualfile *vf = new AFvirtualfile();
|
||||
vf->closure = &stream;
|
||||
vf->write = nullptr;
|
||||
vf->read = audiofile_file_read;
|
||||
vf->length = audiofile_file_length;
|
||||
vf->destroy = audiofile_file_destroy;
|
||||
vf->seek = audiofile_file_seek;
|
||||
vf->tell = audiofile_file_tell;
|
||||
return vf;
|
||||
}
|
||||
|
||||
static SampleFormat
|
||||
audiofile_bits_to_sample_format(int bits)
|
||||
{
|
||||
switch (bits) {
|
||||
case 8:
|
||||
return SampleFormat::S8;
|
||||
|
||||
case 16:
|
||||
return SampleFormat::S16;
|
||||
|
||||
case 24:
|
||||
return SampleFormat::S24_P32;
|
||||
|
||||
case 32:
|
||||
return SampleFormat::S32;
|
||||
}
|
||||
|
||||
return SampleFormat::UNDEFINED;
|
||||
}
|
||||
|
||||
static SampleFormat
|
||||
audiofile_setup_sample_format(AFfilehandle af_fp)
|
||||
{
|
||||
int fs, bits;
|
||||
|
||||
afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
|
||||
if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) {
|
||||
FormatDebug(audiofile_domain,
|
||||
"input file has %d bit samples, converting to 16",
|
||||
bits);
|
||||
bits = 16;
|
||||
}
|
||||
|
||||
afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
|
||||
AF_SAMPFMT_TWOSCOMP, bits);
|
||||
afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
|
||||
|
||||
return audiofile_bits_to_sample_format(bits);
|
||||
}
|
||||
|
||||
static void
|
||||
audiofile_stream_decode(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
AFvirtualfile *vf;
|
||||
int fs, frame_count;
|
||||
AFfilehandle af_fp;
|
||||
AudioFormat audio_format;
|
||||
float total_time;
|
||||
uint16_t bit_rate;
|
||||
int ret;
|
||||
char chunk[CHUNK_SIZE];
|
||||
|
||||
if (!is.IsSeekable()) {
|
||||
LogWarning(audiofile_domain, "not seekable");
|
||||
return;
|
||||
}
|
||||
|
||||
vf = setup_virtual_fops(is);
|
||||
|
||||
af_fp = afOpenVirtualFile(vf, "r", nullptr);
|
||||
if (af_fp == AF_NULL_FILEHANDLE) {
|
||||
LogWarning(audiofile_domain, "failed to input stream");
|
||||
return;
|
||||
}
|
||||
|
||||
Error error;
|
||||
if (!audio_format_init_checked(audio_format,
|
||||
afGetRate(af_fp, AF_DEFAULT_TRACK),
|
||||
audiofile_setup_sample_format(af_fp),
|
||||
afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
|
||||
error)) {
|
||||
LogError(error);
|
||||
afCloseFile(af_fp);
|
||||
return;
|
||||
}
|
||||
|
||||
frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK);
|
||||
|
||||
total_time = ((float)frame_count / (float)audio_format.sample_rate);
|
||||
|
||||
bit_rate = (uint16_t)(is.GetSize() * 8.0 / total_time / 1000.0 + 0.5);
|
||||
|
||||
fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1);
|
||||
|
||||
decoder_initialized(decoder, audio_format, true, total_time);
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk,
|
||||
CHUNK_SIZE / fs);
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
cmd = decoder_data(decoder, nullptr,
|
||||
chunk, ret * fs,
|
||||
bit_rate);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
AFframecount frame = decoder_seek_where(decoder) *
|
||||
audio_format.sample_rate;
|
||||
afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
|
||||
|
||||
decoder_command_finished(decoder);
|
||||
cmd = DecoderCommand::NONE;
|
||||
}
|
||||
} while (cmd == DecoderCommand::NONE);
|
||||
|
||||
afCloseFile(af_fp);
|
||||
}
|
||||
|
||||
static bool
|
||||
audiofile_scan_file(const char *file,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
int total_time = audiofile_get_duration(file);
|
||||
|
||||
if (total_time < 0) {
|
||||
FormatWarning(audiofile_domain,
|
||||
"Failed to get total song time from: %s",
|
||||
file);
|
||||
return false;
|
||||
}
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx, total_time);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const audiofile_suffixes[] = {
|
||||
"wav", "au", "aiff", "aif", nullptr
|
||||
};
|
||||
|
||||
static const char *const audiofile_mime_types[] = {
|
||||
"audio/x-wav",
|
||||
"audio/x-aiff",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin audiofile_decoder_plugin = {
|
||||
"audiofile",
|
||||
nullptr,
|
||||
nullptr,
|
||||
audiofile_stream_decode,
|
||||
nullptr,
|
||||
audiofile_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
audiofile_suffixes,
|
||||
audiofile_mime_types,
|
||||
};
|
25
src/decoder/plugins/AudiofileDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/AudiofileDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_AUDIOFILE_HXX
|
||||
#define MPD_DECODER_AUDIOFILE_HXX
|
||||
|
||||
extern const struct DecoderPlugin audiofile_decoder_plugin;
|
||||
|
||||
#endif
|
163
src/decoder/plugins/DsdLib.cxx
Normal file
163
src/decoder/plugins/DsdLib.cxx
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* \file
|
||||
*
|
||||
* This file contains functions used by the DSF and DSDIFF decoders.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "DsdLib.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "tag/TagId3.hxx"
|
||||
#include "util/Error.hxx"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
|
||||
|
||||
#ifdef HAVE_ID3TAG
|
||||
#include <id3tag.h>
|
||||
#endif
|
||||
|
||||
bool
|
||||
DsdId::Equals(const char *s) const
|
||||
{
|
||||
assert(s != nullptr);
|
||||
assert(strlen(s) == sizeof(value));
|
||||
|
||||
return memcmp(value, s, sizeof(value)) == 0;
|
||||
}
|
||||
|
||||
bool
|
||||
dsdlib_read(Decoder *decoder, InputStream &is,
|
||||
void *data, size_t length)
|
||||
{
|
||||
size_t nbytes = decoder_read(decoder, is, data, length);
|
||||
return nbytes == length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the #input_stream to the specified offset.
|
||||
*/
|
||||
bool
|
||||
dsdlib_skip_to(Decoder *decoder, InputStream &is,
|
||||
int64_t offset)
|
||||
{
|
||||
if (is.IsSeekable())
|
||||
return is.Seek(offset, SEEK_SET, IgnoreError());
|
||||
|
||||
if (is.GetOffset() > offset)
|
||||
return false;
|
||||
|
||||
char buffer[8192];
|
||||
while (is.GetOffset() < offset) {
|
||||
size_t length = sizeof(buffer);
|
||||
if (offset - is.GetOffset() < (int64_t)length)
|
||||
length = offset - is.GetOffset();
|
||||
|
||||
size_t nbytes = decoder_read(decoder, is, buffer, length);
|
||||
if (nbytes == 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(is.GetOffset() == offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip some bytes from the #input_stream.
|
||||
*/
|
||||
bool
|
||||
dsdlib_skip(Decoder *decoder, InputStream &is,
|
||||
int64_t delta)
|
||||
{
|
||||
assert(delta >= 0);
|
||||
|
||||
if (delta == 0)
|
||||
return true;
|
||||
|
||||
if (is.IsSeekable())
|
||||
return is.Seek(delta, SEEK_CUR, IgnoreError());
|
||||
|
||||
char buffer[8192];
|
||||
while (delta > 0) {
|
||||
size_t length = sizeof(buffer);
|
||||
if ((int64_t)length > delta)
|
||||
length = delta;
|
||||
|
||||
size_t nbytes = decoder_read(decoder, is, buffer, length);
|
||||
if (nbytes == 0)
|
||||
return false;
|
||||
|
||||
delta -= nbytes;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_ID3TAG
|
||||
void
|
||||
dsdlib_tag_id3(InputStream &is,
|
||||
const struct tag_handler *handler,
|
||||
void *handler_ctx, int64_t tagoffset)
|
||||
{
|
||||
assert(tagoffset >= 0);
|
||||
|
||||
if (tagoffset == 0)
|
||||
return;
|
||||
|
||||
if (!dsdlib_skip_to(nullptr, is, tagoffset))
|
||||
return;
|
||||
|
||||
struct id3_tag *id3_tag = nullptr;
|
||||
id3_length_t count;
|
||||
|
||||
/* Prevent broken files causing problems */
|
||||
const auto size = is.GetSize();
|
||||
const auto offset = is.GetOffset();
|
||||
if (offset >= size)
|
||||
return;
|
||||
|
||||
count = size - offset;
|
||||
|
||||
/* Check and limit id3 tag size to prevent a stack overflow */
|
||||
if (count == 0 || count > 4096)
|
||||
return;
|
||||
|
||||
id3_byte_t dsdid3[count];
|
||||
id3_byte_t *dsdid3data;
|
||||
dsdid3data = dsdid3;
|
||||
|
||||
if (!dsdlib_read(nullptr, is, dsdid3data, count))
|
||||
return;
|
||||
|
||||
id3_tag = id3_tag_parse(dsdid3data, count);
|
||||
if (id3_tag == nullptr)
|
||||
return;
|
||||
|
||||
scan_id3_tag(id3_tag, handler, handler_ctx);
|
||||
|
||||
id3_tag_delete(id3_tag);
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
82
src/decoder/plugins/DsdLib.hxx
Normal file
82
src/decoder/plugins/DsdLib.hxx
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_DSDLIB_HXX
|
||||
#define MPD_DECODER_DSDLIB_HXX
|
||||
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct Decoder;
|
||||
struct InputStream;
|
||||
|
||||
struct DsdId {
|
||||
char value[4];
|
||||
|
||||
gcc_pure
|
||||
bool Equals(const char *s) const;
|
||||
};
|
||||
|
||||
class DsdUint64 {
|
||||
uint32_t lo;
|
||||
uint32_t hi;
|
||||
|
||||
public:
|
||||
constexpr uint64_t Read() const {
|
||||
return (uint64_t(FromLE32(hi)) << 32) |
|
||||
uint64_t(FromLE32(lo));
|
||||
}
|
||||
};
|
||||
|
||||
class DffDsdUint64 {
|
||||
uint32_t hi;
|
||||
uint32_t lo;
|
||||
|
||||
public:
|
||||
constexpr uint64_t Read() const {
|
||||
return (uint64_t(FromBE32(hi)) << 32) |
|
||||
uint64_t(FromBE32(lo));
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
dsdlib_read(Decoder *decoder, InputStream &is,
|
||||
void *data, size_t length);
|
||||
|
||||
bool
|
||||
dsdlib_skip_to(Decoder *decoder, InputStream &is,
|
||||
int64_t offset);
|
||||
|
||||
bool
|
||||
dsdlib_skip(Decoder *decoder, InputStream &is,
|
||||
int64_t delta);
|
||||
|
||||
/**
|
||||
* Add tags from ID3 tag. All tags commonly found in the ID3 tags of
|
||||
* DSF and DSDIFF files are imported
|
||||
*/
|
||||
void
|
||||
dsdlib_tag_id3(InputStream &is,
|
||||
const struct tag_handler *handler,
|
||||
void *handler_ctx, int64_t tagoffset);
|
||||
|
||||
#endif
|
523
src/decoder/plugins/DsdiffDecoderPlugin.cxx
Normal file
523
src/decoder/plugins/DsdiffDecoderPlugin.cxx
Normal file
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* \file
|
||||
*
|
||||
* This plugin decodes DSDIFF data (SACD) embedded in DFF files.
|
||||
* The DFF code was modeled after the specification found here:
|
||||
* http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf
|
||||
*
|
||||
* All functions common to both DSD decoders have been moved to dsdlib
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "DsdiffDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/bit_reverse.h"
|
||||
#include "util/Error.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "DsdLib.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
struct DsdiffHeader {
|
||||
DsdId id;
|
||||
DffDsdUint64 size;
|
||||
DsdId format;
|
||||
};
|
||||
|
||||
struct DsdiffChunkHeader {
|
||||
DsdId id;
|
||||
DffDsdUint64 size;
|
||||
|
||||
/**
|
||||
* Read the "size" attribute from the specified header, converting it
|
||||
* to the host byte order if needed.
|
||||
*/
|
||||
constexpr
|
||||
uint64_t GetSize() const {
|
||||
return size.Read();
|
||||
}
|
||||
};
|
||||
|
||||
/** struct for DSDIFF native Artist and Title tags */
|
||||
struct dsdiff_native_tag {
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
struct DsdiffMetaData {
|
||||
unsigned sample_rate, channels;
|
||||
bool bitreverse;
|
||||
uint64_t chunk_size;
|
||||
#ifdef HAVE_ID3TAG
|
||||
InputStream::offset_type id3_offset;
|
||||
uint64_t id3_size;
|
||||
#endif
|
||||
/** offset for artist tag */
|
||||
InputStream::offset_type diar_offset;
|
||||
/** offset for title tag */
|
||||
InputStream::offset_type diti_offset;
|
||||
};
|
||||
|
||||
static bool lsbitfirst;
|
||||
|
||||
static bool
|
||||
dsdiff_init(const config_param ¶m)
|
||||
{
|
||||
lsbitfirst = param.GetBlockValue("lsbitfirst", false);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
dsdiff_read_id(Decoder *decoder, InputStream &is,
|
||||
DsdId *id)
|
||||
{
|
||||
return dsdlib_read(decoder, is, id, sizeof(*id));
|
||||
}
|
||||
|
||||
static bool
|
||||
dsdiff_read_chunk_header(Decoder *decoder, InputStream &is,
|
||||
DsdiffChunkHeader *header)
|
||||
{
|
||||
return dsdlib_read(decoder, is, header, sizeof(*header));
|
||||
}
|
||||
|
||||
static bool
|
||||
dsdiff_read_payload(Decoder *decoder, InputStream &is,
|
||||
const DsdiffChunkHeader *header,
|
||||
void *data, size_t length)
|
||||
{
|
||||
uint64_t size = header->GetSize();
|
||||
if (size != (uint64_t)length)
|
||||
return false;
|
||||
|
||||
size_t nbytes = decoder_read(decoder, is, data, length);
|
||||
return nbytes == length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse a "SND" chunk inside "PROP".
|
||||
*/
|
||||
static bool
|
||||
dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
|
||||
DsdiffMetaData *metadata,
|
||||
InputStream::offset_type end_offset)
|
||||
{
|
||||
DsdiffChunkHeader header;
|
||||
while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) {
|
||||
if (!dsdiff_read_chunk_header(decoder, is, &header))
|
||||
return false;
|
||||
|
||||
InputStream::offset_type chunk_end_offset = is.GetOffset()
|
||||
+ header.GetSize();
|
||||
if (chunk_end_offset > end_offset)
|
||||
return false;
|
||||
|
||||
if (header.id.Equals("FS ")) {
|
||||
uint32_t sample_rate;
|
||||
if (!dsdiff_read_payload(decoder, is, &header,
|
||||
&sample_rate,
|
||||
sizeof(sample_rate)))
|
||||
return false;
|
||||
|
||||
metadata->sample_rate = FromBE32(sample_rate);
|
||||
} else if (header.id.Equals("CHNL")) {
|
||||
uint16_t channels;
|
||||
if (header.GetSize() < sizeof(channels) ||
|
||||
!dsdlib_read(decoder, is,
|
||||
&channels, sizeof(channels)) ||
|
||||
!dsdlib_skip_to(decoder, is, chunk_end_offset))
|
||||
return false;
|
||||
|
||||
metadata->channels = FromBE16(channels);
|
||||
} else if (header.id.Equals("CMPR")) {
|
||||
DsdId type;
|
||||
if (header.GetSize() < sizeof(type) ||
|
||||
!dsdlib_read(decoder, is,
|
||||
&type, sizeof(type)) ||
|
||||
!dsdlib_skip_to(decoder, is, chunk_end_offset))
|
||||
return false;
|
||||
|
||||
if (!type.Equals("DSD "))
|
||||
/* only uncompressed DSD audio data
|
||||
is implemented */
|
||||
return false;
|
||||
} else {
|
||||
/* ignore unknown chunk */
|
||||
|
||||
if (!dsdlib_skip_to(decoder, is, chunk_end_offset))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return is.GetOffset() == end_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse a "PROP" chunk.
|
||||
*/
|
||||
static bool
|
||||
dsdiff_read_prop(Decoder *decoder, InputStream &is,
|
||||
DsdiffMetaData *metadata,
|
||||
const DsdiffChunkHeader *prop_header)
|
||||
{
|
||||
uint64_t prop_size = prop_header->GetSize();
|
||||
InputStream::offset_type end_offset = is.GetOffset() + prop_size;
|
||||
|
||||
DsdId prop_id;
|
||||
if (prop_size < sizeof(prop_id) ||
|
||||
!dsdiff_read_id(decoder, is, &prop_id))
|
||||
return false;
|
||||
|
||||
if (prop_id.Equals("SND "))
|
||||
return dsdiff_read_prop_snd(decoder, is, metadata, end_offset);
|
||||
else
|
||||
/* ignore unknown PROP chunk */
|
||||
return dsdlib_skip_to(decoder, is, end_offset);
|
||||
}
|
||||
|
||||
static void
|
||||
dsdiff_handle_native_tag(InputStream &is,
|
||||
const struct tag_handler *handler,
|
||||
void *handler_ctx, InputStream::offset_type tagoffset,
|
||||
TagType type)
|
||||
{
|
||||
if (!dsdlib_skip_to(nullptr, is, tagoffset))
|
||||
return;
|
||||
|
||||
struct dsdiff_native_tag metatag;
|
||||
|
||||
if (!dsdlib_read(nullptr, is, &metatag, sizeof(metatag)))
|
||||
return;
|
||||
|
||||
uint32_t length = FromBE32(metatag.size);
|
||||
|
||||
/* Check and limit size of the tag to prevent a stack overflow */
|
||||
if (length == 0 || length > 60)
|
||||
return;
|
||||
|
||||
char string[length];
|
||||
char *label;
|
||||
label = string;
|
||||
|
||||
if (!dsdlib_read(nullptr, is, label, (size_t)length))
|
||||
return;
|
||||
|
||||
string[length] = '\0';
|
||||
tag_handler_invoke_tag(handler, handler_ctx, type, label);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse additional metadata chunks for tagging purposes. By default
|
||||
* dsdiff files only support equivalents for artist and title but some of the
|
||||
* extract tools add an id3 tag to provide more tags. If such id3 is found
|
||||
* this will be used for tagging otherwise the native tags (if any) will be
|
||||
* used
|
||||
*/
|
||||
|
||||
static bool
|
||||
dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
|
||||
DsdiffMetaData *metadata,
|
||||
DsdiffChunkHeader *chunk_header,
|
||||
const struct tag_handler *handler,
|
||||
void *handler_ctx)
|
||||
{
|
||||
|
||||
/* skip from DSD data to next chunk header */
|
||||
if (!dsdlib_skip(decoder, is, metadata->chunk_size))
|
||||
return false;
|
||||
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
|
||||
return false;
|
||||
|
||||
#ifdef HAVE_ID3TAG
|
||||
metadata->id3_size = 0;
|
||||
#endif
|
||||
|
||||
/* Now process all the remaining chunk headers in the stream
|
||||
and record their position and size */
|
||||
|
||||
const auto size = is.GetSize();
|
||||
while (is.GetOffset() < size) {
|
||||
uint64_t chunk_size = chunk_header->GetSize();
|
||||
|
||||
/* DIIN chunk, is directly followed by other chunks */
|
||||
if (chunk_header->id.Equals("DIIN"))
|
||||
chunk_size = 0;
|
||||
|
||||
/* DIAR chunk - DSDIFF native tag for Artist */
|
||||
if (chunk_header->id.Equals("DIAR")) {
|
||||
chunk_size = chunk_header->GetSize();
|
||||
metadata->diar_offset = is.GetOffset();
|
||||
}
|
||||
|
||||
/* DITI chunk - DSDIFF native tag for Title */
|
||||
if (chunk_header->id.Equals("DITI")) {
|
||||
chunk_size = chunk_header->GetSize();
|
||||
metadata->diti_offset = is.GetOffset();
|
||||
}
|
||||
#ifdef HAVE_ID3TAG
|
||||
/* 'ID3 ' chunk, offspec. Used by sacdextract */
|
||||
if (chunk_header->id.Equals("ID3 ")) {
|
||||
chunk_size = chunk_header->GetSize();
|
||||
metadata->id3_offset = is.GetOffset();
|
||||
metadata->id3_size = chunk_size;
|
||||
}
|
||||
#endif
|
||||
if (chunk_size != 0) {
|
||||
if (!dsdlib_skip(decoder, is, chunk_size))
|
||||
break;
|
||||
}
|
||||
|
||||
if (is.GetOffset() < size) {
|
||||
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/* done processing chunk headers, process tags if any */
|
||||
|
||||
#ifdef HAVE_ID3TAG
|
||||
if (metadata->id3_offset != 0)
|
||||
{
|
||||
/* a ID3 tag has preference over the other tags, do not process
|
||||
other tags if we have one */
|
||||
dsdlib_tag_id3(is, handler, handler_ctx, metadata->id3_offset);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (metadata->diar_offset != 0)
|
||||
dsdiff_handle_native_tag(is, handler, handler_ctx,
|
||||
metadata->diar_offset, TAG_ARTIST);
|
||||
|
||||
if (metadata->diti_offset != 0)
|
||||
dsdiff_handle_native_tag(is, handler, handler_ctx,
|
||||
metadata->diti_offset, TAG_TITLE);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse all metadata chunks at the beginning. Stop when the
|
||||
* first "DSD" chunk is seen, and return its header in the
|
||||
* "chunk_header" parameter.
|
||||
*/
|
||||
static bool
|
||||
dsdiff_read_metadata(Decoder *decoder, InputStream &is,
|
||||
DsdiffMetaData *metadata,
|
||||
DsdiffChunkHeader *chunk_header)
|
||||
{
|
||||
DsdiffHeader header;
|
||||
if (!dsdlib_read(decoder, is, &header, sizeof(header)) ||
|
||||
!header.id.Equals("FRM8") ||
|
||||
!header.format.Equals("DSD "))
|
||||
return false;
|
||||
|
||||
while (true) {
|
||||
if (!dsdiff_read_chunk_header(decoder, is,
|
||||
chunk_header))
|
||||
return false;
|
||||
|
||||
if (chunk_header->id.Equals("PROP")) {
|
||||
if (!dsdiff_read_prop(decoder, is, metadata,
|
||||
chunk_header))
|
||||
return false;
|
||||
} else if (chunk_header->id.Equals("DSD ")) {
|
||||
const uint64_t chunk_size = chunk_header->GetSize();
|
||||
metadata->chunk_size = chunk_size;
|
||||
return true;
|
||||
} else {
|
||||
/* ignore unknown chunk */
|
||||
const uint64_t chunk_size = chunk_header->GetSize();
|
||||
InputStream::offset_type chunk_end_offset =
|
||||
is.GetOffset() + chunk_size;
|
||||
|
||||
if (!dsdlib_skip_to(decoder, is, chunk_end_offset))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
bit_reverse_buffer(uint8_t *p, uint8_t *end)
|
||||
{
|
||||
for (; p < end; ++p)
|
||||
*p = bit_reverse(*p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode one "DSD" chunk.
|
||||
*/
|
||||
static bool
|
||||
dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
|
||||
unsigned channels,
|
||||
uint64_t chunk_size)
|
||||
{
|
||||
uint8_t buffer[8192];
|
||||
|
||||
const size_t sample_size = sizeof(buffer[0]);
|
||||
const size_t frame_size = channels * sample_size;
|
||||
const unsigned buffer_frames = sizeof(buffer) / frame_size;
|
||||
const unsigned buffer_samples = buffer_frames * frame_size;
|
||||
const size_t buffer_size = buffer_samples * sample_size;
|
||||
|
||||
while (chunk_size > 0) {
|
||||
/* see how much aligned data from the remaining chunk
|
||||
fits into the local buffer */
|
||||
size_t now_size = buffer_size;
|
||||
if (chunk_size < (uint64_t)now_size) {
|
||||
unsigned now_frames =
|
||||
(unsigned)chunk_size / frame_size;
|
||||
now_size = now_frames * frame_size;
|
||||
}
|
||||
|
||||
size_t nbytes = decoder_read(decoder, is, buffer, now_size);
|
||||
if (nbytes != now_size)
|
||||
return false;
|
||||
|
||||
chunk_size -= nbytes;
|
||||
|
||||
if (lsbitfirst)
|
||||
bit_reverse_buffer(buffer, buffer + nbytes);
|
||||
|
||||
const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0);
|
||||
switch (cmd) {
|
||||
case DecoderCommand::NONE:
|
||||
break;
|
||||
|
||||
case DecoderCommand::START:
|
||||
case DecoderCommand::STOP:
|
||||
return false;
|
||||
|
||||
case DecoderCommand::SEEK:
|
||||
|
||||
/* Not implemented yet */
|
||||
decoder_seek_error(decoder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return dsdlib_skip(&decoder, is, chunk_size);
|
||||
}
|
||||
|
||||
static void
|
||||
dsdiff_stream_decode(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
DsdiffMetaData metadata;
|
||||
|
||||
DsdiffChunkHeader chunk_header;
|
||||
/* check if it is is a proper DFF file */
|
||||
if (!dsdiff_read_metadata(&decoder, is, &metadata, &chunk_header))
|
||||
return;
|
||||
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
|
||||
SampleFormat::DSD,
|
||||
metadata.channels, error)) {
|
||||
LogError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* calculate song time from DSD chunk size and sample frequency */
|
||||
uint64_t chunk_size = metadata.chunk_size;
|
||||
float songtime = ((chunk_size / metadata.channels) * 8) /
|
||||
(float) metadata.sample_rate;
|
||||
|
||||
/* success: file was recognized */
|
||||
decoder_initialized(decoder, audio_format, false, songtime);
|
||||
|
||||
/* every iteration of the following loop decodes one "DSD"
|
||||
chunk from a DFF file */
|
||||
|
||||
while (true) {
|
||||
chunk_size = chunk_header.GetSize();
|
||||
|
||||
if (chunk_header.id.Equals("DSD ")) {
|
||||
if (!dsdiff_decode_chunk(decoder, is,
|
||||
metadata.channels,
|
||||
chunk_size))
|
||||
break;
|
||||
} else {
|
||||
/* ignore other chunks */
|
||||
if (!dsdlib_skip(&decoder, is, chunk_size))
|
||||
break;
|
||||
}
|
||||
|
||||
/* read next chunk header; the first one was read by
|
||||
dsdiff_read_metadata() */
|
||||
if (!dsdiff_read_chunk_header(&decoder,
|
||||
is, &chunk_header))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
dsdiff_scan_stream(InputStream &is,
|
||||
gcc_unused const struct tag_handler *handler,
|
||||
gcc_unused void *handler_ctx)
|
||||
{
|
||||
DsdiffMetaData metadata;
|
||||
DsdiffChunkHeader chunk_header;
|
||||
|
||||
/* First check for DFF metadata */
|
||||
if (!dsdiff_read_metadata(nullptr, is, &metadata, &chunk_header))
|
||||
return false;
|
||||
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
|
||||
SampleFormat::DSD,
|
||||
metadata.channels, IgnoreError()))
|
||||
/* refuse to parse files which we cannot play anyway */
|
||||
return false;
|
||||
|
||||
/* calculate song time and add as tag */
|
||||
unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) /
|
||||
metadata.sample_rate;
|
||||
tag_handler_invoke_duration(handler, handler_ctx, songtime);
|
||||
|
||||
/* Read additional metadata and created tags if available */
|
||||
dsdiff_read_metadata_extra(nullptr, is, &metadata, &chunk_header,
|
||||
handler, handler_ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const dsdiff_suffixes[] = {
|
||||
"dff",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static const char *const dsdiff_mime_types[] = {
|
||||
"application/x-dff",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin dsdiff_decoder_plugin = {
|
||||
"dsdiff",
|
||||
dsdiff_init,
|
||||
nullptr,
|
||||
dsdiff_stream_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
dsdiff_scan_stream,
|
||||
nullptr,
|
||||
dsdiff_suffixes,
|
||||
dsdiff_mime_types,
|
||||
};
|
25
src/decoder/plugins/DsdiffDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/DsdiffDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_DSDIFF_H
|
||||
#define MPD_DECODER_DSDIFF_H
|
||||
|
||||
extern const struct DecoderPlugin dsdiff_decoder_plugin;
|
||||
|
||||
#endif
|
357
src/decoder/plugins/DsfDecoderPlugin.cxx
Normal file
357
src/decoder/plugins/DsfDecoderPlugin.cxx
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* \file
|
||||
*
|
||||
* This plugin decodes DSDIFF data (SACD) embedded in DSF files.
|
||||
*
|
||||
* The DSF code was created using the specification found here:
|
||||
* http://dsd-guide.com/sonys-dsf-file-format-spec
|
||||
*
|
||||
* All functions common to both DSD decoders have been moved to dsdlib
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "DsfDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/bit_reverse.h"
|
||||
#include "util/Error.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "DsdLib.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
struct DsfMetaData {
|
||||
unsigned sample_rate, channels;
|
||||
bool bitreverse;
|
||||
uint64_t chunk_size;
|
||||
#ifdef HAVE_ID3TAG
|
||||
InputStream::offset_type id3_offset;
|
||||
uint64_t id3_size;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct DsfHeader {
|
||||
/** DSF header id: "DSD " */
|
||||
DsdId id;
|
||||
/** DSD chunk size, including id = 28 */
|
||||
DsdUint64 size;
|
||||
/** total file size */
|
||||
DsdUint64 fsize;
|
||||
/** pointer to id3v2 metadata, should be at the end of the file */
|
||||
DsdUint64 pmeta;
|
||||
};
|
||||
|
||||
/** DSF file fmt chunk */
|
||||
struct DsfFmtChunk {
|
||||
/** id: "fmt " */
|
||||
DsdId id;
|
||||
/** fmt chunk size, including id, normally 52 */
|
||||
DsdUint64 size;
|
||||
/** version of this format = 1 */
|
||||
uint32_t version;
|
||||
/** 0: DSD raw */
|
||||
uint32_t formatid;
|
||||
/** channel type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */
|
||||
uint32_t channeltype;
|
||||
/** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */
|
||||
uint32_t channelnum;
|
||||
/** sample frequency: 2822400, 5644800 */
|
||||
uint32_t sample_freq;
|
||||
/** bits per sample 1 or 8 */
|
||||
uint32_t bitssample;
|
||||
/** Sample count per channel in bytes */
|
||||
DsdUint64 scnt;
|
||||
/** block size per channel = 4096 */
|
||||
uint32_t block_size;
|
||||
/** reserved, should be all zero */
|
||||
uint32_t reserved;
|
||||
};
|
||||
|
||||
struct DsfDataChunk {
|
||||
DsdId id;
|
||||
/** "data" chunk size, includes header (id+size) */
|
||||
DsdUint64 size;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read and parse all needed metadata chunks for DSF files.
|
||||
*/
|
||||
static bool
|
||||
dsf_read_metadata(Decoder *decoder, InputStream &is,
|
||||
DsfMetaData *metadata)
|
||||
{
|
||||
DsfHeader dsf_header;
|
||||
if (!dsdlib_read(decoder, is, &dsf_header, sizeof(dsf_header)) ||
|
||||
!dsf_header.id.Equals("DSD "))
|
||||
return false;
|
||||
|
||||
const uint64_t chunk_size = dsf_header.size.Read();
|
||||
if (sizeof(dsf_header) != chunk_size)
|
||||
return false;
|
||||
|
||||
#ifdef HAVE_ID3TAG
|
||||
const uint64_t metadata_offset = dsf_header.pmeta.Read();
|
||||
#endif
|
||||
|
||||
/* read the 'fmt ' chunk of the DSF file */
|
||||
DsfFmtChunk dsf_fmt_chunk;
|
||||
if (!dsdlib_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
|
||||
!dsf_fmt_chunk.id.Equals("fmt "))
|
||||
return false;
|
||||
|
||||
const uint64_t fmt_chunk_size = dsf_fmt_chunk.size.Read();
|
||||
if (fmt_chunk_size != sizeof(dsf_fmt_chunk))
|
||||
return false;
|
||||
|
||||
uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq);
|
||||
|
||||
/* for now, only support version 1 of the standard, DSD raw stereo
|
||||
files with a sample freq of 2822400 or 5644800 Hz */
|
||||
|
||||
if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0
|
||||
|| dsf_fmt_chunk.channeltype != 2
|
||||
|| dsf_fmt_chunk.channelnum != 2
|
||||
|| (samplefreq != 2822400 && samplefreq != 5644800))
|
||||
return false;
|
||||
|
||||
uint32_t chblksize = FromLE32(dsf_fmt_chunk.block_size);
|
||||
/* according to the spec block size should always be 4096 */
|
||||
if (chblksize != 4096)
|
||||
return false;
|
||||
|
||||
/* read the 'data' chunk of the DSF file */
|
||||
DsfDataChunk data_chunk;
|
||||
if (!dsdlib_read(decoder, is, &data_chunk, sizeof(data_chunk)) ||
|
||||
!data_chunk.id.Equals("data"))
|
||||
return false;
|
||||
|
||||
/* data size of DSF files are padded to multiple of 4096,
|
||||
we use the actual data size as chunk size */
|
||||
|
||||
uint64_t data_size = data_chunk.size.Read();
|
||||
if (data_size < sizeof(data_chunk))
|
||||
return false;
|
||||
|
||||
data_size -= sizeof(data_chunk);
|
||||
|
||||
/* data_size cannot be bigger or equal to total file size */
|
||||
const uint64_t size = (uint64_t)is.GetSize();
|
||||
if (data_size >= size)
|
||||
return false;
|
||||
|
||||
/* use the sample count from the DSF header as the upper
|
||||
bound, because some DSF files contain junk at the end of
|
||||
the "data" chunk */
|
||||
const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read();
|
||||
const uint64_t playable_size = samplecnt * 2 / 8;
|
||||
if (data_size > playable_size)
|
||||
data_size = playable_size;
|
||||
|
||||
metadata->chunk_size = data_size;
|
||||
metadata->channels = (unsigned) dsf_fmt_chunk.channelnum;
|
||||
metadata->sample_rate = samplefreq;
|
||||
#ifdef HAVE_ID3TAG
|
||||
/* metada_offset cannot be bigger then or equal to total file size */
|
||||
if (metadata_offset >= size)
|
||||
metadata->id3_offset = 0;
|
||||
else
|
||||
metadata->id3_offset = (InputStream::offset_type)metadata_offset;
|
||||
#endif
|
||||
/* check bits per sample format, determine if bitreverse is needed */
|
||||
metadata->bitreverse = dsf_fmt_chunk.bitssample == 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
bit_reverse_buffer(uint8_t *p, uint8_t *end)
|
||||
{
|
||||
for (; p < end; ++p)
|
||||
*p = bit_reverse(*p);
|
||||
}
|
||||
|
||||
/**
|
||||
* DSF data is build up of alternating 4096 blocks of DSD samples for left and
|
||||
* right. Convert the buffer holding 1 block of 4096 DSD left samples and 1
|
||||
* block of 4096 DSD right samples to 8k of samples in normal PCM left/right
|
||||
* order.
|
||||
*/
|
||||
static void
|
||||
dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes)
|
||||
{
|
||||
for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) {
|
||||
scratch[i] = *(dest+j);
|
||||
j++;
|
||||
}
|
||||
|
||||
for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) {
|
||||
scratch[i] = *(dest+4096+j);
|
||||
j++;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < (unsigned)nrbytes; i++) {
|
||||
*dest = scratch[i];
|
||||
dest++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode one complete DSF 'data' chunk i.e. a complete song
|
||||
*/
|
||||
static bool
|
||||
dsf_decode_chunk(Decoder &decoder, InputStream &is,
|
||||
unsigned channels,
|
||||
uint64_t chunk_size,
|
||||
bool bitreverse)
|
||||
{
|
||||
uint8_t buffer[8192];
|
||||
|
||||
/* scratch buffer for DSF samples to convert to the needed
|
||||
normal left/right regime of samples */
|
||||
uint8_t dsf_scratch_buffer[8192];
|
||||
|
||||
const size_t sample_size = sizeof(buffer[0]);
|
||||
const size_t frame_size = channels * sample_size;
|
||||
const unsigned buffer_frames = sizeof(buffer) / frame_size;
|
||||
const unsigned buffer_samples = buffer_frames * frame_size;
|
||||
const size_t buffer_size = buffer_samples * sample_size;
|
||||
|
||||
while (chunk_size > 0) {
|
||||
/* see how much aligned data from the remaining chunk
|
||||
fits into the local buffer */
|
||||
size_t now_size = buffer_size;
|
||||
if (chunk_size < (uint64_t)now_size) {
|
||||
unsigned now_frames =
|
||||
(unsigned)chunk_size / frame_size;
|
||||
now_size = now_frames * frame_size;
|
||||
}
|
||||
|
||||
size_t nbytes = decoder_read(&decoder, is, buffer, now_size);
|
||||
if (nbytes != now_size)
|
||||
return false;
|
||||
|
||||
chunk_size -= nbytes;
|
||||
|
||||
if (bitreverse)
|
||||
bit_reverse_buffer(buffer, buffer + nbytes);
|
||||
|
||||
dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes);
|
||||
|
||||
const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0);
|
||||
switch (cmd) {
|
||||
case DecoderCommand::NONE:
|
||||
break;
|
||||
|
||||
case DecoderCommand::START:
|
||||
case DecoderCommand::STOP:
|
||||
return false;
|
||||
|
||||
case DecoderCommand::SEEK:
|
||||
|
||||
/* not implemented yet */
|
||||
decoder_seek_error(decoder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return dsdlib_skip(&decoder, is, chunk_size);
|
||||
}
|
||||
|
||||
static void
|
||||
dsf_stream_decode(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
/* check if it is a proper DSF file */
|
||||
DsfMetaData metadata;
|
||||
if (!dsf_read_metadata(&decoder, is, &metadata))
|
||||
return;
|
||||
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
|
||||
SampleFormat::DSD,
|
||||
metadata.channels, error)) {
|
||||
LogError(error);
|
||||
return;
|
||||
}
|
||||
/* Calculate song time from DSD chunk size and sample frequency */
|
||||
uint64_t chunk_size = metadata.chunk_size;
|
||||
float songtime = ((chunk_size / metadata.channels) * 8) /
|
||||
(float) metadata.sample_rate;
|
||||
|
||||
/* success: file was recognized */
|
||||
decoder_initialized(decoder, audio_format, false, songtime);
|
||||
|
||||
if (!dsf_decode_chunk(decoder, is, metadata.channels,
|
||||
chunk_size,
|
||||
metadata.bitreverse))
|
||||
return;
|
||||
}
|
||||
|
||||
static bool
|
||||
dsf_scan_stream(InputStream &is,
|
||||
gcc_unused const struct tag_handler *handler,
|
||||
gcc_unused void *handler_ctx)
|
||||
{
|
||||
/* check DSF metadata */
|
||||
DsfMetaData metadata;
|
||||
if (!dsf_read_metadata(nullptr, is, &metadata))
|
||||
return false;
|
||||
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
|
||||
SampleFormat::DSD,
|
||||
metadata.channels, IgnoreError()))
|
||||
/* refuse to parse files which we cannot play anyway */
|
||||
return false;
|
||||
|
||||
/* calculate song time and add as tag */
|
||||
unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) /
|
||||
metadata.sample_rate;
|
||||
tag_handler_invoke_duration(handler, handler_ctx, songtime);
|
||||
|
||||
#ifdef HAVE_ID3TAG
|
||||
/* Add available tags from the ID3 tag */
|
||||
dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const dsf_suffixes[] = {
|
||||
"dsf",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static const char *const dsf_mime_types[] = {
|
||||
"application/x-dsf",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin dsf_decoder_plugin = {
|
||||
"dsf",
|
||||
nullptr,
|
||||
nullptr,
|
||||
dsf_stream_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
dsf_scan_stream,
|
||||
nullptr,
|
||||
dsf_suffixes,
|
||||
dsf_mime_types,
|
||||
};
|
25
src/decoder/plugins/DsfDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/DsfDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_DSF_H
|
||||
#define MPD_DECODER_DSF_H
|
||||
|
||||
extern const struct DecoderPlugin dsf_decoder_plugin;
|
||||
|
||||
#endif
|
464
src/decoder/plugins/FaadDecoderPlugin.cxx
Normal file
464
src/decoder/plugins/FaadDecoderPlugin.cxx
Normal file
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "FaadDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "../DecoderBuffer.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <neaacdec.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define AAC_MAX_CHANNELS 6
|
||||
|
||||
static const unsigned adts_sample_rates[] =
|
||||
{ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
|
||||
16000, 12000, 11025, 8000, 7350, 0, 0, 0
|
||||
};
|
||||
|
||||
static constexpr Domain faad_decoder_domain("faad_decoder");
|
||||
|
||||
/**
|
||||
* 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(const unsigned char *data)
|
||||
{
|
||||
/* check syncword */
|
||||
if (!((data[0] == 0xFF) && ((data[1] & 0xF6) == 0xF0)))
|
||||
return 0;
|
||||
|
||||
return (((unsigned int)data[3] & 0x3) << 11) |
|
||||
(((unsigned int)data[4]) << 3) |
|
||||
(data[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(DecoderBuffer *buffer)
|
||||
{
|
||||
while (true) {
|
||||
auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
|
||||
if (data.size < 8) {
|
||||
/* not enough data yet */
|
||||
if (!decoder_buffer_fill(buffer))
|
||||
/* failed */
|
||||
return 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* find the 0xff marker */
|
||||
const uint8_t *p = (const uint8_t *)
|
||||
memchr(data.data, 0xff, data.size);
|
||||
if (p == nullptr) {
|
||||
/* no marker - discard the buffer */
|
||||
decoder_buffer_clear(buffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p > data.data) {
|
||||
/* discard data before 0xff */
|
||||
decoder_buffer_consume(buffer, p - data.data);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is it a frame? */
|
||||
size_t frame_length = adts_check_frame(data.data);
|
||||
if (frame_length == 0) {
|
||||
/* it's just some random 0xff byte; discard it
|
||||
and continue searching */
|
||||
decoder_buffer_consume(buffer, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data.size < frame_length) {
|
||||
/* available buffer size is smaller than the
|
||||
frame will be - attempt to read more
|
||||
data */
|
||||
if (!decoder_buffer_fill(buffer)) {
|
||||
/* not enough data; discard this frame
|
||||
to prevent a possible buffer
|
||||
overflow */
|
||||
decoder_buffer_clear(buffer);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* found a full frame! */
|
||||
return frame_length;
|
||||
}
|
||||
}
|
||||
|
||||
static float
|
||||
adts_song_duration(DecoderBuffer *buffer)
|
||||
{
|
||||
unsigned sample_rate = 0;
|
||||
|
||||
/* Read all frames to ensure correct time and bitrate */
|
||||
unsigned frames = 0;
|
||||
for (;; frames++) {
|
||||
unsigned frame_length = adts_find_frame(buffer);
|
||||
if (frame_length == 0)
|
||||
break;
|
||||
|
||||
if (frames == 0) {
|
||||
auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
|
||||
assert(!data.IsEmpty());
|
||||
assert(frame_length <= data.size);
|
||||
|
||||
sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2];
|
||||
}
|
||||
|
||||
decoder_buffer_consume(buffer, frame_length);
|
||||
}
|
||||
|
||||
float frames_per_second = (float)sample_rate / 1024.0;
|
||||
if (frames_per_second <= 0)
|
||||
return -1;
|
||||
|
||||
return (float)frames / frames_per_second;
|
||||
}
|
||||
|
||||
static float
|
||||
faad_song_duration(DecoderBuffer *buffer, InputStream &is)
|
||||
{
|
||||
const auto size = is.GetSize();
|
||||
const size_t fileread = size >= 0 ? size : 0;
|
||||
|
||||
decoder_buffer_fill(buffer);
|
||||
auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
|
||||
if (data.IsEmpty())
|
||||
return -1;
|
||||
|
||||
size_t tagsize = 0;
|
||||
if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) {
|
||||
/* skip the ID3 tag */
|
||||
|
||||
tagsize = (data.data[6] << 21) | (data.data[7] << 14) |
|
||||
(data.data[8] << 7) | (data.data[9] << 0);
|
||||
|
||||
tagsize += 10;
|
||||
|
||||
bool success = decoder_buffer_skip(buffer, tagsize) &&
|
||||
decoder_buffer_fill(buffer);
|
||||
if (!success)
|
||||
return -1;
|
||||
|
||||
data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
|
||||
if (data.IsEmpty())
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (is.IsSeekable() && data.size >= 2 &&
|
||||
data.data[0] == 0xFF && ((data.data[1] & 0xF6) == 0xF0)) {
|
||||
/* obtain the duration from the ADTS header */
|
||||
float song_length = adts_song_duration(buffer);
|
||||
|
||||
is.LockSeek(tagsize, SEEK_SET, IgnoreError());
|
||||
|
||||
decoder_buffer_clear(buffer);
|
||||
decoder_buffer_fill(buffer);
|
||||
|
||||
return song_length;
|
||||
} else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) {
|
||||
/* obtain the duration from the ADIF header */
|
||||
unsigned bit_rate;
|
||||
size_t skip_size = (data.data[4] & 0x80) ? 9 : 0;
|
||||
|
||||
if (8 + skip_size > data.size)
|
||||
/* not enough data yet; skip parsing this
|
||||
header */
|
||||
return -1;
|
||||
|
||||
bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) |
|
||||
(data.data[5 + skip_size] << 11) |
|
||||
(data.data[6 + skip_size] << 3) |
|
||||
(data.data[7 + skip_size] & 0xE0);
|
||||
|
||||
if (fileread != 0 && bit_rate != 0)
|
||||
return fileread * 8.0 / bit_rate;
|
||||
else
|
||||
return fileread;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for NeAACDecInit() which works around some API
|
||||
* inconsistencies in libfaad.
|
||||
*/
|
||||
static bool
|
||||
faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
|
||||
AudioFormat &audio_format, Error &error)
|
||||
{
|
||||
uint32_t sample_rate;
|
||||
#ifdef HAVE_FAAD_LONG
|
||||
/* neaacdec.h declares all arguments as "unsigned long", but
|
||||
internally expects uint32_t pointers. To avoid gcc
|
||||
warnings, use this workaround. */
|
||||
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
|
||||
#else
|
||||
uint32_t *sample_rate_p = &sample_rate;
|
||||
#endif
|
||||
|
||||
auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
|
||||
if (data.IsEmpty()) {
|
||||
error.Set(faad_decoder_domain, "Empty file");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t channels;
|
||||
int32_t nbytes = NeAACDecInit(decoder,
|
||||
/* deconst hack, libfaad requires this */
|
||||
const_cast<uint8_t *>(data.data),
|
||||
data.size,
|
||||
sample_rate_p, &channels);
|
||||
if (nbytes < 0) {
|
||||
error.Set(faad_decoder_domain, "Not an AAC stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
decoder_buffer_consume(buffer, nbytes);
|
||||
|
||||
return audio_format_init_checked(audio_format, sample_rate,
|
||||
SampleFormat::S16, channels, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for NeAACDecDecode() which works around some API
|
||||
* inconsistencies in libfaad.
|
||||
*/
|
||||
static const void *
|
||||
faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
|
||||
NeAACDecFrameInfo *frame_info)
|
||||
{
|
||||
auto data = ConstBuffer<uint8_t>::FromVoid(decoder_buffer_read(buffer));
|
||||
if (data.IsEmpty())
|
||||
return nullptr;
|
||||
|
||||
return NeAACDecDecode(decoder, frame_info,
|
||||
/* deconst hack, libfaad requires this */
|
||||
const_cast<uint8_t *>(data.data),
|
||||
data.size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a song file's total playing time in seconds, as a float.
|
||||
* Returns 0 if the duration is unknown, and a negative value if the
|
||||
* file is invalid.
|
||||
*/
|
||||
static float
|
||||
faad_get_file_time_float(InputStream &is)
|
||||
{
|
||||
DecoderBuffer *buffer =
|
||||
decoder_buffer_new(nullptr, is,
|
||||
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
|
||||
float length = faad_song_duration(buffer, is);
|
||||
|
||||
if (length < 0) {
|
||||
NeAACDecHandle decoder = NeAACDecOpen();
|
||||
|
||||
NeAACDecConfigurationPtr config =
|
||||
NeAACDecGetCurrentConfiguration(decoder);
|
||||
config->outputFormat = FAAD_FMT_16BIT;
|
||||
NeAACDecSetConfiguration(decoder, config);
|
||||
|
||||
decoder_buffer_fill(buffer);
|
||||
|
||||
AudioFormat audio_format;
|
||||
if (faad_decoder_init(decoder, buffer, audio_format,
|
||||
IgnoreError()))
|
||||
length = 0;
|
||||
|
||||
NeAACDecClose(decoder);
|
||||
}
|
||||
|
||||
decoder_buffer_free(buffer);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a song file's total playing time in seconds, as an int.
|
||||
* Returns 0 if the duration is unknown, and a negative value if the
|
||||
* file is invalid.
|
||||
*/
|
||||
static int
|
||||
faad_get_file_time(InputStream &is)
|
||||
{
|
||||
int file_time = -1;
|
||||
float length;
|
||||
|
||||
if ((length = faad_get_file_time_float(is)) >= 0)
|
||||
file_time = length + 0.5;
|
||||
|
||||
return file_time;
|
||||
}
|
||||
|
||||
static void
|
||||
faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
|
||||
{
|
||||
DecoderBuffer *buffer =
|
||||
decoder_buffer_new(&mpd_decoder, is,
|
||||
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
|
||||
const float total_time = faad_song_duration(buffer, is);
|
||||
|
||||
/* create the libfaad decoder */
|
||||
|
||||
NeAACDecHandle decoder = NeAACDecOpen();
|
||||
|
||||
NeAACDecConfigurationPtr config =
|
||||
NeAACDecGetCurrentConfiguration(decoder);
|
||||
config->outputFormat = FAAD_FMT_16BIT;
|
||||
config->downMatrix = 1;
|
||||
config->dontUpSampleImplicitSBR = 0;
|
||||
NeAACDecSetConfiguration(decoder, config);
|
||||
|
||||
while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
|
||||
decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
|
||||
adts_find_frame(buffer);
|
||||
decoder_buffer_fill(buffer);
|
||||
}
|
||||
|
||||
/* initialize it */
|
||||
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
|
||||
LogError(error);
|
||||
NeAACDecClose(decoder);
|
||||
decoder_buffer_free(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
/* initialize the MPD core */
|
||||
|
||||
decoder_initialized(mpd_decoder, audio_format, false, total_time);
|
||||
|
||||
/* the decoder loop */
|
||||
|
||||
DecoderCommand cmd;
|
||||
unsigned bit_rate = 0;
|
||||
do {
|
||||
size_t frame_size;
|
||||
const void *decoded;
|
||||
NeAACDecFrameInfo frame_info;
|
||||
|
||||
/* find the next frame */
|
||||
|
||||
frame_size = adts_find_frame(buffer);
|
||||
if (frame_size == 0)
|
||||
/* end of file */
|
||||
break;
|
||||
|
||||
/* decode it */
|
||||
|
||||
decoded = faad_decoder_decode(decoder, buffer, &frame_info);
|
||||
|
||||
if (frame_info.error > 0) {
|
||||
FormatWarning(faad_decoder_domain,
|
||||
"error decoding AAC stream: %s",
|
||||
NeAACDecGetErrorMessage(frame_info.error));
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame_info.channels != audio_format.channels) {
|
||||
FormatDefault(faad_decoder_domain,
|
||||
"channel count changed from %u to %u",
|
||||
audio_format.channels, frame_info.channels);
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame_info.samplerate != audio_format.sample_rate) {
|
||||
FormatDefault(faad_decoder_domain,
|
||||
"sample rate changed from %u to %lu",
|
||||
audio_format.sample_rate,
|
||||
(unsigned long)frame_info.samplerate);
|
||||
break;
|
||||
}
|
||||
|
||||
decoder_buffer_consume(buffer, frame_info.bytesconsumed);
|
||||
|
||||
/* update bit rate and position */
|
||||
|
||||
if (frame_info.samples > 0) {
|
||||
bit_rate = frame_info.bytesconsumed * 8.0 *
|
||||
frame_info.channels * audio_format.sample_rate /
|
||||
frame_info.samples / 1000 + 0.5;
|
||||
}
|
||||
|
||||
/* send PCM samples to MPD */
|
||||
|
||||
cmd = decoder_data(mpd_decoder, is, decoded,
|
||||
(size_t)frame_info.samples * 2,
|
||||
bit_rate);
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
/* cleanup */
|
||||
|
||||
NeAACDecClose(decoder);
|
||||
decoder_buffer_free(buffer);
|
||||
}
|
||||
|
||||
static bool
|
||||
faad_scan_stream(InputStream &is,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
int file_time = faad_get_file_time(is);
|
||||
|
||||
if (file_time < 0)
|
||||
return false;
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx, file_time);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const faad_suffixes[] = { "aac", nullptr };
|
||||
static const char *const faad_mime_types[] = {
|
||||
"audio/aac", "audio/aacp", nullptr
|
||||
};
|
||||
|
||||
const DecoderPlugin faad_decoder_plugin = {
|
||||
"faad",
|
||||
nullptr,
|
||||
nullptr,
|
||||
faad_stream_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
faad_scan_stream,
|
||||
nullptr,
|
||||
faad_suffixes,
|
||||
faad_mime_types,
|
||||
};
|
25
src/decoder/plugins/FaadDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/FaadDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FAAD_DECODER_PLUGIN_HXX
|
||||
#define MPD_FAAD_DECODER_PLUGIN_HXX
|
||||
|
||||
extern const struct DecoderPlugin faad_decoder_plugin;
|
||||
|
||||
#endif
|
686
src/decoder/plugins/FfmpegDecoderPlugin.cxx
Normal file
686
src/decoder/plugins/FfmpegDecoderPlugin.cxx
Normal file
@@ -0,0 +1,686 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* necessary because libavutil/common.h uses UINT64_C */
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "config.h"
|
||||
#include "FfmpegDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "FfmpegMetaData.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "LogV.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/log.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
#include <libavutil/frame.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
static constexpr Domain ffmpeg_domain("ffmpeg");
|
||||
|
||||
/* suppress the ffmpeg compatibility macro */
|
||||
#ifdef SampleFormat
|
||||
#undef SampleFormat
|
||||
#endif
|
||||
|
||||
static LogLevel
|
||||
import_ffmpeg_level(int level)
|
||||
{
|
||||
if (level <= AV_LOG_FATAL)
|
||||
return LogLevel::ERROR;
|
||||
|
||||
if (level <= AV_LOG_WARNING)
|
||||
return LogLevel::WARNING;
|
||||
|
||||
if (level <= AV_LOG_INFO)
|
||||
return LogLevel::INFO;
|
||||
|
||||
return LogLevel::DEBUG;
|
||||
}
|
||||
|
||||
static void
|
||||
mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level,
|
||||
const char *fmt, va_list vl)
|
||||
{
|
||||
const AVClass * cls = nullptr;
|
||||
|
||||
if (ptr != nullptr)
|
||||
cls = *(const AVClass *const*)ptr;
|
||||
|
||||
if (cls != nullptr) {
|
||||
char domain[64];
|
||||
snprintf(domain, sizeof(domain), "%s/%s",
|
||||
ffmpeg_domain.GetName(), cls->item_name(ptr));
|
||||
const Domain d(domain);
|
||||
LogFormatV(d, import_ffmpeg_level(level), fmt, vl);
|
||||
}
|
||||
}
|
||||
|
||||
struct AvioStream {
|
||||
Decoder *const decoder;
|
||||
InputStream &input;
|
||||
|
||||
AVIOContext *io;
|
||||
|
||||
unsigned char buffer[8192];
|
||||
|
||||
AvioStream(Decoder *_decoder, InputStream &_input)
|
||||
:decoder(_decoder), input(_input), io(nullptr) {}
|
||||
|
||||
~AvioStream() {
|
||||
if (io != nullptr)
|
||||
av_free(io);
|
||||
}
|
||||
|
||||
bool Open();
|
||||
};
|
||||
|
||||
static int
|
||||
mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
|
||||
{
|
||||
AvioStream *stream = (AvioStream *)opaque;
|
||||
|
||||
return decoder_read(stream->decoder, stream->input,
|
||||
(void *)buf, size);
|
||||
}
|
||||
|
||||
static int64_t
|
||||
mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
|
||||
{
|
||||
AvioStream *stream = (AvioStream *)opaque;
|
||||
|
||||
if (whence == AVSEEK_SIZE)
|
||||
return stream->input.size;
|
||||
|
||||
if (!stream->input.LockSeek(pos, whence, IgnoreError()))
|
||||
return -1;
|
||||
|
||||
return stream->input.offset;
|
||||
}
|
||||
|
||||
bool
|
||||
AvioStream::Open()
|
||||
{
|
||||
io = avio_alloc_context(buffer, sizeof(buffer),
|
||||
false, this,
|
||||
mpd_ffmpeg_stream_read, nullptr,
|
||||
input.seekable
|
||||
? mpd_ffmpeg_stream_seek : nullptr);
|
||||
return io != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* API compatibility wrapper for av_open_input_stream() and
|
||||
* avformat_open_input().
|
||||
*/
|
||||
static int
|
||||
mpd_ffmpeg_open_input(AVFormatContext **ic_ptr,
|
||||
AVIOContext *pb,
|
||||
const char *filename,
|
||||
AVInputFormat *fmt)
|
||||
{
|
||||
AVFormatContext *context = avformat_alloc_context();
|
||||
if (context == nullptr)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
context->pb = pb;
|
||||
*ic_ptr = context;
|
||||
return avformat_open_input(ic_ptr, filename, fmt, nullptr);
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_init(gcc_unused const config_param ¶m)
|
||||
{
|
||||
av_log_set_callback(mpd_ffmpeg_log_callback);
|
||||
|
||||
av_register_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
ffmpeg_find_audio_stream(const AVFormatContext *format_context)
|
||||
{
|
||||
for (unsigned i = 0; i < format_context->nb_streams; ++i)
|
||||
if (format_context->streams[i]->codec->codec_type ==
|
||||
AVMEDIA_TYPE_AUDIO)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
gcc_const
|
||||
static double
|
||||
time_from_ffmpeg(int64_t t, const AVRational time_base)
|
||||
{
|
||||
assert(t != (int64_t)AV_NOPTS_VALUE);
|
||||
|
||||
return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
|
||||
/ (double)1024;
|
||||
}
|
||||
|
||||
gcc_const
|
||||
static int64_t
|
||||
time_to_ffmpeg(double t, const AVRational time_base)
|
||||
{
|
||||
return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024},
|
||||
time_base);
|
||||
}
|
||||
|
||||
static void
|
||||
copy_interleave_frame2(uint8_t *dest, uint8_t **src,
|
||||
unsigned nframes, unsigned nchannels,
|
||||
unsigned sample_size)
|
||||
{
|
||||
for (unsigned frame = 0; frame < nframes; ++frame) {
|
||||
for (unsigned channel = 0; channel < nchannels; ++channel) {
|
||||
memcpy(dest, src[channel] + frame * sample_size,
|
||||
sample_size);
|
||||
dest += sample_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy PCM data from a AVFrame to an interleaved buffer.
|
||||
*/
|
||||
static int
|
||||
copy_interleave_frame(const AVCodecContext *codec_context,
|
||||
const AVFrame *frame,
|
||||
uint8_t **output_buffer,
|
||||
uint8_t **global_buffer, int *global_buffer_size)
|
||||
{
|
||||
int plane_size;
|
||||
const int data_size =
|
||||
av_samples_get_buffer_size(&plane_size,
|
||||
codec_context->channels,
|
||||
frame->nb_samples,
|
||||
codec_context->sample_fmt, 1);
|
||||
if (data_size <= 0)
|
||||
return data_size;
|
||||
|
||||
if (av_sample_fmt_is_planar(codec_context->sample_fmt) &&
|
||||
codec_context->channels > 1) {
|
||||
if(*global_buffer_size < data_size) {
|
||||
av_freep(global_buffer);
|
||||
|
||||
*global_buffer = (uint8_t*)av_malloc(data_size);
|
||||
|
||||
if (!*global_buffer)
|
||||
/* Not enough memory - shouldn't happen */
|
||||
return AVERROR(ENOMEM);
|
||||
*global_buffer_size = data_size;
|
||||
}
|
||||
*output_buffer = *global_buffer;
|
||||
copy_interleave_frame2(*output_buffer, frame->extended_data,
|
||||
frame->nb_samples,
|
||||
codec_context->channels,
|
||||
av_get_bytes_per_sample(codec_context->sample_fmt));
|
||||
} else {
|
||||
*output_buffer = frame->extended_data[0];
|
||||
}
|
||||
|
||||
return data_size;
|
||||
}
|
||||
|
||||
static DecoderCommand
|
||||
ffmpeg_send_packet(Decoder &decoder, InputStream &is,
|
||||
const AVPacket *packet,
|
||||
AVCodecContext *codec_context,
|
||||
const AVStream *stream,
|
||||
AVFrame *frame,
|
||||
uint8_t **buffer, int *buffer_size)
|
||||
{
|
||||
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
|
||||
decoder_timestamp(decoder,
|
||||
time_from_ffmpeg(packet->pts - stream->start_time,
|
||||
stream->time_base));
|
||||
|
||||
AVPacket packet2 = *packet;
|
||||
|
||||
uint8_t *output_buffer;
|
||||
|
||||
DecoderCommand cmd = DecoderCommand::NONE;
|
||||
while (packet2.size > 0 && cmd == DecoderCommand::NONE) {
|
||||
int audio_size = 0;
|
||||
int got_frame = 0;
|
||||
int len = avcodec_decode_audio4(codec_context,
|
||||
frame, &got_frame,
|
||||
&packet2);
|
||||
if (len >= 0 && got_frame) {
|
||||
audio_size = copy_interleave_frame(codec_context,
|
||||
frame,
|
||||
&output_buffer,
|
||||
buffer, buffer_size);
|
||||
if (audio_size < 0)
|
||||
len = audio_size;
|
||||
}
|
||||
|
||||
if (len < 0) {
|
||||
/* if error, we skip the frame */
|
||||
LogDefault(ffmpeg_domain,
|
||||
"decoding failed, frame skipped");
|
||||
break;
|
||||
}
|
||||
|
||||
packet2.data += len;
|
||||
packet2.size -= len;
|
||||
|
||||
if (audio_size <= 0)
|
||||
continue;
|
||||
|
||||
cmd = decoder_data(decoder, is,
|
||||
output_buffer, audio_size,
|
||||
codec_context->bit_rate / 1000);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
gcc_const
|
||||
static SampleFormat
|
||||
ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
|
||||
{
|
||||
switch (sample_fmt) {
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
return SampleFormat::S16;
|
||||
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
return SampleFormat::S32;
|
||||
|
||||
case AV_SAMPLE_FMT_FLTP:
|
||||
return SampleFormat::FLOAT;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
char buffer[64];
|
||||
const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer),
|
||||
sample_fmt);
|
||||
if (name != nullptr)
|
||||
FormatError(ffmpeg_domain,
|
||||
"Unsupported libavcodec SampleFormat value: %s (%d)",
|
||||
name, sample_fmt);
|
||||
else
|
||||
FormatError(ffmpeg_domain,
|
||||
"Unsupported libavcodec SampleFormat value: %d",
|
||||
sample_fmt);
|
||||
return SampleFormat::UNDEFINED;
|
||||
}
|
||||
|
||||
static AVInputFormat *
|
||||
ffmpeg_probe(Decoder *decoder, InputStream &is)
|
||||
{
|
||||
enum {
|
||||
BUFFER_SIZE = 16384,
|
||||
PADDING = 16,
|
||||
};
|
||||
|
||||
unsigned char buffer[BUFFER_SIZE];
|
||||
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
|
||||
if (nbytes <= PADDING || !is.LockRewind(IgnoreError()))
|
||||
return nullptr;
|
||||
|
||||
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
|
||||
beyond the declared buffer limit, which makes valgrind
|
||||
angry; this workaround removes some padding from the buffer
|
||||
size */
|
||||
nbytes -= PADDING;
|
||||
|
||||
AVProbeData avpd;
|
||||
avpd.buf = buffer;
|
||||
avpd.buf_size = nbytes;
|
||||
avpd.filename = is.uri.c_str();
|
||||
|
||||
return av_probe_input_format(&avpd, true);
|
||||
}
|
||||
|
||||
static void
|
||||
ffmpeg_decode(Decoder &decoder, InputStream &input)
|
||||
{
|
||||
AVInputFormat *input_format = ffmpeg_probe(&decoder, input);
|
||||
if (input_format == nullptr)
|
||||
return;
|
||||
|
||||
FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)",
|
||||
input_format->name, input_format->long_name);
|
||||
|
||||
AvioStream stream(&decoder, input);
|
||||
if (!stream.Open()) {
|
||||
LogError(ffmpeg_domain, "Failed to open stream");
|
||||
return;
|
||||
}
|
||||
|
||||
//ffmpeg works with ours "fileops" helper
|
||||
AVFormatContext *format_context = nullptr;
|
||||
if (mpd_ffmpeg_open_input(&format_context, stream.io,
|
||||
input.uri.c_str(),
|
||||
input_format) != 0) {
|
||||
LogError(ffmpeg_domain, "Open failed");
|
||||
return;
|
||||
}
|
||||
|
||||
const int find_result =
|
||||
avformat_find_stream_info(format_context, nullptr);
|
||||
if (find_result < 0) {
|
||||
LogError(ffmpeg_domain, "Couldn't find stream info");
|
||||
avformat_close_input(&format_context);
|
||||
return;
|
||||
}
|
||||
|
||||
int audio_stream = ffmpeg_find_audio_stream(format_context);
|
||||
if (audio_stream == -1) {
|
||||
LogError(ffmpeg_domain, "No audio stream inside");
|
||||
avformat_close_input(&format_context);
|
||||
return;
|
||||
}
|
||||
|
||||
AVStream *av_stream = format_context->streams[audio_stream];
|
||||
|
||||
AVCodecContext *codec_context = av_stream->codec;
|
||||
if (codec_context->codec_name[0] != 0)
|
||||
FormatDebug(ffmpeg_domain, "codec '%s'",
|
||||
codec_context->codec_name);
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
|
||||
|
||||
if (!codec) {
|
||||
LogError(ffmpeg_domain, "Unsupported audio codec");
|
||||
avformat_close_input(&format_context);
|
||||
return;
|
||||
}
|
||||
|
||||
const SampleFormat sample_format =
|
||||
ffmpeg_sample_format(codec_context->sample_fmt);
|
||||
if (sample_format == SampleFormat::UNDEFINED)
|
||||
return;
|
||||
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format,
|
||||
codec_context->sample_rate,
|
||||
sample_format,
|
||||
codec_context->channels, error)) {
|
||||
LogError(error);
|
||||
avformat_close_input(&format_context);
|
||||
return;
|
||||
}
|
||||
|
||||
/* the audio format must be read from AVCodecContext by now,
|
||||
because avcodec_open() has been demonstrated to fill bogus
|
||||
values into AVCodecContext.channels - a change that will be
|
||||
reverted later by avcodec_decode_audio3() */
|
||||
|
||||
const int open_result = avcodec_open2(codec_context, codec, nullptr);
|
||||
if (open_result < 0) {
|
||||
LogError(ffmpeg_domain, "Could not open codec");
|
||||
avformat_close_input(&format_context);
|
||||
return;
|
||||
}
|
||||
|
||||
int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
|
||||
? format_context->duration / AV_TIME_BASE
|
||||
: 0;
|
||||
|
||||
decoder_initialized(decoder, audio_format,
|
||||
input.seekable, total_time);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
#else
|
||||
AVFrame *frame = avcodec_alloc_frame();
|
||||
#endif
|
||||
if (!frame) {
|
||||
LogError(ffmpeg_domain, "Could not allocate frame");
|
||||
avformat_close_input(&format_context);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t *interleaved_buffer = nullptr;
|
||||
int interleaved_buffer_size = 0;
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
AVPacket packet;
|
||||
if (av_read_frame(format_context, &packet) < 0)
|
||||
/* end of file */
|
||||
break;
|
||||
|
||||
if (packet.stream_index == audio_stream)
|
||||
cmd = ffmpeg_send_packet(decoder, input,
|
||||
&packet, codec_context,
|
||||
av_stream,
|
||||
frame,
|
||||
&interleaved_buffer, &interleaved_buffer_size);
|
||||
else
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
av_free_packet(&packet);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
int64_t where =
|
||||
time_to_ffmpeg(decoder_seek_where(decoder),
|
||||
av_stream->time_base) +
|
||||
av_stream->start_time;
|
||||
|
||||
if (av_seek_frame(format_context, audio_stream, where,
|
||||
AV_TIME_BASE) < 0)
|
||||
decoder_seek_error(decoder);
|
||||
else {
|
||||
avcodec_flush_buffers(codec_context);
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
}
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 53
|
||||
av_frame_free(&frame);
|
||||
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
|
||||
avcodec_free_frame(&frame);
|
||||
#else
|
||||
av_freep(&frame);
|
||||
#endif
|
||||
av_freep(&interleaved_buffer);
|
||||
|
||||
avcodec_close(codec_context);
|
||||
avformat_close_input(&format_context);
|
||||
}
|
||||
|
||||
//no tag reading in ffmpeg, check if playable
|
||||
static bool
|
||||
ffmpeg_scan_stream(InputStream &is,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
AVInputFormat *input_format = ffmpeg_probe(nullptr, is);
|
||||
if (input_format == nullptr)
|
||||
return false;
|
||||
|
||||
AvioStream stream(nullptr, is);
|
||||
if (!stream.Open())
|
||||
return false;
|
||||
|
||||
AVFormatContext *f = nullptr;
|
||||
if (mpd_ffmpeg_open_input(&f, stream.io, is.uri.c_str(),
|
||||
input_format) != 0)
|
||||
return false;
|
||||
|
||||
const int find_result =
|
||||
avformat_find_stream_info(f, nullptr);
|
||||
if (find_result < 0) {
|
||||
avformat_close_input(&f);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f->duration != (int64_t)AV_NOPTS_VALUE)
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
f->duration / AV_TIME_BASE);
|
||||
|
||||
ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
|
||||
int idx = ffmpeg_find_audio_stream(f);
|
||||
if (idx >= 0)
|
||||
ffmpeg_scan_dictionary(f->streams[idx]->metadata,
|
||||
handler, handler_ctx);
|
||||
|
||||
avformat_close_input(&f);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of extensions found for the formats supported by ffmpeg.
|
||||
* This list is current as of 02-23-09; To find out if there are more
|
||||
* supported formats, check the ffmpeg changelog since this date for
|
||||
* more formats.
|
||||
*/
|
||||
static const char *const ffmpeg_suffixes[] = {
|
||||
"16sv", "3g2", "3gp", "4xm", "8svx", "aa3", "aac", "ac3", "afc", "aif",
|
||||
"aifc", "aiff", "al", "alaw", "amr", "anim", "apc", "ape", "asf",
|
||||
"atrac", "au", "aud", "avi", "avm2", "avs", "bap", "bfi", "c93", "cak",
|
||||
"cin", "cmv", "cpk", "daud", "dct", "divx", "dts", "dv", "dvd", "dxa",
|
||||
"eac3", "film", "flac", "flc", "fli", "fll", "flx", "flv", "g726",
|
||||
"gsm", "gxf", "iss", "m1v", "m2v", "m2t", "m2ts",
|
||||
"m4a", "m4b", "m4v",
|
||||
"mad",
|
||||
"mj2", "mjpeg", "mjpg", "mka", "mkv", "mlp", "mm", "mmf", "mov", "mp+",
|
||||
"mp1", "mp2", "mp3", "mp4", "mpc", "mpeg", "mpg", "mpga", "mpp", "mpu",
|
||||
"mve", "mvi", "mxf", "nc", "nsv", "nut", "nuv", "oga", "ogm", "ogv",
|
||||
"ogx", "oma", "ogg", "omg", "psp", "pva", "qcp", "qt", "r3d", "ra",
|
||||
"ram", "rl2", "rm", "rmvb", "roq", "rpl", "rvc", "shn", "smk", "snd",
|
||||
"sol", "son", "spx", "str", "swf", "tgi", "tgq", "tgv", "thp", "ts",
|
||||
"tsp", "tta", "xa", "xvid", "uv", "uv2", "vb", "vid", "vob", "voc",
|
||||
"vp6", "vmd", "wav", "webm", "wma", "wmv", "wsaud", "wsvga", "wv",
|
||||
"wve",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static const char *const ffmpeg_mime_types[] = {
|
||||
"application/flv",
|
||||
"application/m4a",
|
||||
"application/mp4",
|
||||
"application/octet-stream",
|
||||
"application/ogg",
|
||||
"application/x-ms-wmz",
|
||||
"application/x-ms-wmd",
|
||||
"application/x-ogg",
|
||||
"application/x-shockwave-flash",
|
||||
"application/x-shorten",
|
||||
"audio/8svx",
|
||||
"audio/16sv",
|
||||
"audio/aac",
|
||||
"audio/ac3",
|
||||
"audio/aiff"
|
||||
"audio/amr",
|
||||
"audio/basic",
|
||||
"audio/flac",
|
||||
"audio/m4a",
|
||||
"audio/mp4",
|
||||
"audio/mpeg",
|
||||
"audio/musepack",
|
||||
"audio/ogg",
|
||||
"audio/qcelp",
|
||||
"audio/vorbis",
|
||||
"audio/vorbis+ogg",
|
||||
"audio/x-8svx",
|
||||
"audio/x-16sv",
|
||||
"audio/x-aac",
|
||||
"audio/x-ac3",
|
||||
"audio/x-aiff"
|
||||
"audio/x-alaw",
|
||||
"audio/x-au",
|
||||
"audio/x-dca",
|
||||
"audio/x-eac3",
|
||||
"audio/x-flac",
|
||||
"audio/x-gsm",
|
||||
"audio/x-mace",
|
||||
"audio/x-matroska",
|
||||
"audio/x-monkeys-audio",
|
||||
"audio/x-mpeg",
|
||||
"audio/x-ms-wma",
|
||||
"audio/x-ms-wax",
|
||||
"audio/x-musepack",
|
||||
"audio/x-ogg",
|
||||
"audio/x-vorbis",
|
||||
"audio/x-vorbis+ogg",
|
||||
"audio/x-pn-realaudio",
|
||||
"audio/x-pn-multirate-realaudio",
|
||||
"audio/x-speex",
|
||||
"audio/x-tta"
|
||||
"audio/x-voc",
|
||||
"audio/x-wav",
|
||||
"audio/x-wma",
|
||||
"audio/x-wv",
|
||||
"video/anim",
|
||||
"video/quicktime",
|
||||
"video/msvideo",
|
||||
"video/ogg",
|
||||
"video/theora",
|
||||
"video/webm",
|
||||
"video/x-dv",
|
||||
"video/x-flv",
|
||||
"video/x-matroska",
|
||||
"video/x-mjpeg",
|
||||
"video/x-mpeg",
|
||||
"video/x-ms-asf",
|
||||
"video/x-msvideo",
|
||||
"video/x-ms-wmv",
|
||||
"video/x-ms-wvx",
|
||||
"video/x-ms-wm",
|
||||
"video/x-ms-wmx",
|
||||
"video/x-nut",
|
||||
"video/x-pva",
|
||||
"video/x-theora",
|
||||
"video/x-vid",
|
||||
"video/x-wmv",
|
||||
"video/x-xvid",
|
||||
|
||||
/* special value for the "ffmpeg" input plugin: all streams by
|
||||
the "ffmpeg" input plugin shall be decoded by this
|
||||
plugin */
|
||||
"audio/x-mpd-ffmpeg",
|
||||
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin ffmpeg_decoder_plugin = {
|
||||
"ffmpeg",
|
||||
ffmpeg_init,
|
||||
nullptr,
|
||||
ffmpeg_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
ffmpeg_scan_stream,
|
||||
nullptr,
|
||||
ffmpeg_suffixes,
|
||||
ffmpeg_mime_types
|
||||
};
|
25
src/decoder/plugins/FfmpegDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/FfmpegDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_FFMPEG_HXX
|
||||
#define MPD_DECODER_FFMPEG_HXX
|
||||
|
||||
extern const struct DecoderPlugin ffmpeg_decoder_plugin;
|
||||
|
||||
#endif
|
76
src/decoder/plugins/FfmpegMetaData.cxx
Normal file
76
src/decoder/plugins/FfmpegMetaData.cxx
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* necessary because libavutil/common.h uses UINT64_C */
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "config.h"
|
||||
#include "FfmpegMetaData.hxx"
|
||||
#include "tag/TagTable.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
|
||||
static const struct tag_table ffmpeg_tags[] = {
|
||||
{ "year", TAG_DATE },
|
||||
{ "author-sort", TAG_ARTIST_SORT },
|
||||
{ "album_artist", TAG_ALBUM_ARTIST },
|
||||
{ "album_artist-sort", TAG_ALBUM_ARTIST_SORT },
|
||||
|
||||
/* sentinel */
|
||||
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
|
||||
};
|
||||
|
||||
static void
|
||||
ffmpeg_copy_metadata(TagType type,
|
||||
AVDictionary *m, const char *name,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
AVDictionaryEntry *mt = nullptr;
|
||||
|
||||
while ((mt = av_dict_get(m, name, mt, 0)) != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
type, mt->value);
|
||||
}
|
||||
|
||||
static void
|
||||
ffmpeg_scan_pairs(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
AVDictionaryEntry *i = nullptr;
|
||||
|
||||
while ((i = av_dict_get(dict, "", i, AV_DICT_IGNORE_SUFFIX)) != nullptr)
|
||||
tag_handler_invoke_pair(handler, handler_ctx,
|
||||
i->key, i->value);
|
||||
}
|
||||
|
||||
void
|
||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i],
|
||||
handler, handler_ctx);
|
||||
|
||||
for (const struct tag_table *i = ffmpeg_tags;
|
||||
i->name != nullptr; ++i)
|
||||
ffmpeg_copy_metadata(i->type, dict, i->name,
|
||||
handler, handler_ctx);
|
||||
|
||||
if (handler->pair != nullptr)
|
||||
ffmpeg_scan_pairs(dict, handler, handler_ctx);
|
||||
}
|
38
src/decoder/plugins/FfmpegMetaData.hxx
Normal file
38
src/decoder/plugins/FfmpegMetaData.hxx
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FFMPEG_METADATA_HXX
|
||||
#define MPD_FFMPEG_METADATA_HXX
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/dict.h>
|
||||
}
|
||||
|
||||
/* suppress the ffmpeg compatibility macro */
|
||||
#ifdef SampleFormat
|
||||
#undef SampleFormat
|
||||
#endif
|
||||
|
||||
struct tag_handler;
|
||||
|
||||
void
|
||||
ffmpeg_scan_dictionary(AVDictionary *dict,
|
||||
const tag_handler *handler, void *handler_ctx);
|
||||
|
||||
#endif
|
190
src/decoder/plugins/FlacCommon.cxx
Normal file
190
src/decoder/plugins/FlacCommon.cxx
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Common data structures and functions used by FLAC and OggFLAC
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "FlacCommon.hxx"
|
||||
#include "FlacMetadata.hxx"
|
||||
#include "FlacPcm.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
flac_data::flac_data(Decoder &_decoder,
|
||||
InputStream &_input_stream)
|
||||
:FlacInput(_input_stream, &_decoder),
|
||||
initialized(false), unsupported(false),
|
||||
total_frames(0), first_frame(0), next_frame(0), position(0),
|
||||
decoder(_decoder), input_stream(_input_stream)
|
||||
{
|
||||
}
|
||||
|
||||
static SampleFormat
|
||||
flac_sample_format(unsigned bits_per_sample)
|
||||
{
|
||||
switch (bits_per_sample) {
|
||||
case 8:
|
||||
return SampleFormat::S8;
|
||||
|
||||
case 16:
|
||||
return SampleFormat::S16;
|
||||
|
||||
case 24:
|
||||
return SampleFormat::S24_P32;
|
||||
|
||||
case 32:
|
||||
return SampleFormat::S32;
|
||||
|
||||
default:
|
||||
return SampleFormat::UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
flac_got_stream_info(struct flac_data *data,
|
||||
const FLAC__StreamMetadata_StreamInfo *stream_info)
|
||||
{
|
||||
if (data->initialized || data->unsupported)
|
||||
return;
|
||||
|
||||
Error error;
|
||||
if (!audio_format_init_checked(data->audio_format,
|
||||
stream_info->sample_rate,
|
||||
flac_sample_format(stream_info->bits_per_sample),
|
||||
stream_info->channels, error)) {
|
||||
LogError(error);
|
||||
data->unsupported = true;
|
||||
return;
|
||||
}
|
||||
|
||||
data->frame_size = data->audio_format.GetFrameSize();
|
||||
|
||||
if (data->total_frames == 0)
|
||||
data->total_frames = stream_info->total_samples;
|
||||
|
||||
data->initialized = true;
|
||||
}
|
||||
|
||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||
struct flac_data *data)
|
||||
{
|
||||
if (data->unsupported)
|
||||
return;
|
||||
|
||||
ReplayGainInfo rgi;
|
||||
|
||||
switch (block->type) {
|
||||
case FLAC__METADATA_TYPE_STREAMINFO:
|
||||
flac_got_stream_info(data, &block->data.stream_info);
|
||||
break;
|
||||
|
||||
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
||||
if (flac_parse_replay_gain(rgi, block))
|
||||
decoder_replay_gain(data->decoder, &rgi);
|
||||
|
||||
decoder_mixramp(data->decoder, flac_parse_mixramp(block));
|
||||
|
||||
data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function attempts to call decoder_initialized() in case there
|
||||
* was no STREAMINFO block. This is allowed for nonseekable streams,
|
||||
* where the server sends us only a part of the file, without
|
||||
* providing the STREAMINFO block from the beginning of the file
|
||||
* (e.g. when seeking with SqueezeBox Server).
|
||||
*/
|
||||
static bool
|
||||
flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
|
||||
{
|
||||
if (data->unsupported)
|
||||
return false;
|
||||
|
||||
Error error;
|
||||
if (!audio_format_init_checked(data->audio_format,
|
||||
header->sample_rate,
|
||||
flac_sample_format(header->bits_per_sample),
|
||||
header->channels, error)) {
|
||||
LogError(error);
|
||||
data->unsupported = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
data->frame_size = data->audio_format.GetFrameSize();
|
||||
|
||||
decoder_initialized(data->decoder, data->audio_format,
|
||||
data->input_stream.seekable,
|
||||
(float)data->total_frames /
|
||||
(float)data->audio_format.sample_rate);
|
||||
|
||||
data->initialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderWriteStatus
|
||||
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
||||
const FLAC__int32 *const buf[],
|
||||
FLAC__uint64 nbytes)
|
||||
{
|
||||
void *buffer;
|
||||
unsigned bit_rate;
|
||||
|
||||
if (!data->initialized && !flac_got_first_frame(data, &frame->header))
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
|
||||
size_t buffer_size = frame->header.blocksize * data->frame_size;
|
||||
buffer = data->buffer.Get(buffer_size);
|
||||
|
||||
flac_convert(buffer, frame->header.channels,
|
||||
data->audio_format.format, buf,
|
||||
0, frame->header.blocksize);
|
||||
|
||||
if (nbytes > 0)
|
||||
bit_rate = nbytes * 8 * frame->header.sample_rate /
|
||||
(1000 * frame->header.blocksize);
|
||||
else
|
||||
bit_rate = 0;
|
||||
|
||||
auto cmd = decoder_data(data->decoder, data->input_stream,
|
||||
buffer, buffer_size,
|
||||
bit_rate);
|
||||
data->next_frame += frame->header.blocksize;
|
||||
switch (cmd) {
|
||||
case DecoderCommand::NONE:
|
||||
case DecoderCommand::START:
|
||||
break;
|
||||
|
||||
case DecoderCommand::STOP:
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
|
||||
case DecoderCommand::SEEK:
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
93
src/decoder/plugins/FlacCommon.hxx
Normal file
93
src/decoder/plugins/FlacCommon.hxx
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Common data structures and functions used by FLAC and OggFLAC
|
||||
*/
|
||||
|
||||
#ifndef MPD_FLAC_COMMON_HXX
|
||||
#define MPD_FLAC_COMMON_HXX
|
||||
|
||||
#include "FlacInput.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "pcm/PcmBuffer.hxx"
|
||||
|
||||
#include <FLAC/stream_decoder.h>
|
||||
|
||||
struct flac_data : public FlacInput {
|
||||
PcmBuffer buffer;
|
||||
|
||||
/**
|
||||
* The size of one frame in the output buffer.
|
||||
*/
|
||||
unsigned frame_size;
|
||||
|
||||
/**
|
||||
* Has decoder_initialized() been called yet?
|
||||
*/
|
||||
bool initialized;
|
||||
|
||||
/**
|
||||
* Does the FLAC file contain an unsupported audio format?
|
||||
*/
|
||||
bool unsupported;
|
||||
|
||||
/**
|
||||
* The validated audio format of the FLAC file. This
|
||||
* attribute is defined if "initialized" is true.
|
||||
*/
|
||||
AudioFormat audio_format;
|
||||
|
||||
/**
|
||||
* The total number of frames in this song. The decoder
|
||||
* plugin may initialize this attribute to override the value
|
||||
* provided by libFLAC (e.g. for sub songs from a CUE sheet).
|
||||
*/
|
||||
FLAC__uint64 total_frames;
|
||||
|
||||
/**
|
||||
* The number of the first frame in this song. This is only
|
||||
* non-zero if playing sub songs from a CUE sheet.
|
||||
*/
|
||||
FLAC__uint64 first_frame;
|
||||
|
||||
/**
|
||||
* The number of the next frame which is going to be decoded.
|
||||
*/
|
||||
FLAC__uint64 next_frame;
|
||||
|
||||
FLAC__uint64 position;
|
||||
|
||||
Decoder &decoder;
|
||||
InputStream &input_stream;
|
||||
|
||||
Tag tag;
|
||||
|
||||
flac_data(Decoder &decoder, InputStream &input_stream);
|
||||
};
|
||||
|
||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||
struct flac_data *data);
|
||||
|
||||
FLAC__StreamDecoderWriteStatus
|
||||
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
||||
const FLAC__int32 *const buf[],
|
||||
FLAC__uint64 nbytes);
|
||||
|
||||
#endif /* _FLAC_COMMON_H */
|
383
src/decoder/plugins/FlacDecoderPlugin.cxx
Normal file
383
src/decoder/plugins/FlacDecoderPlugin.cxx
Normal file
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h" /* must be first for large file support */
|
||||
#include "FlacDecoderPlugin.h"
|
||||
#include "FlacDomain.hxx"
|
||||
#include "FlacCommon.hxx"
|
||||
#include "FlacMetadata.hxx"
|
||||
#include "OggCodec.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
||||
#error libFLAC is too old
|
||||
#endif
|
||||
|
||||
static void flacPrintErroredState(FLAC__StreamDecoderState state)
|
||||
{
|
||||
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:
|
||||
case FLAC__STREAM_DECODER_SEEK_ERROR:
|
||||
case FLAC__STREAM_DECODER_ABORTED:
|
||||
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
|
||||
case FLAC__STREAM_DECODER_UNINITIALIZED:
|
||||
break;
|
||||
}
|
||||
|
||||
LogError(flac_domain, FLAC__StreamDecoderStateString[state]);
|
||||
}
|
||||
|
||||
static void flacMetadata(gcc_unused const FLAC__StreamDecoder * dec,
|
||||
const FLAC__StreamMetadata * block, void *vdata)
|
||||
{
|
||||
flac_metadata_common_cb(block, (struct flac_data *) vdata);
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderWriteStatus
|
||||
flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
|
||||
const FLAC__int32 *const buf[], void *vdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) vdata;
|
||||
FLAC__uint64 nbytes = 0;
|
||||
|
||||
if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) {
|
||||
if (data->position > 0 && nbytes > data->position) {
|
||||
nbytes -= data->position;
|
||||
data->position += nbytes;
|
||||
} else {
|
||||
data->position = nbytes;
|
||||
nbytes = 0;
|
||||
}
|
||||
} else
|
||||
nbytes = 0;
|
||||
|
||||
return flac_common_write(data, frame, buf, nbytes);
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_scan_file(const char *file,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.Read(file)) {
|
||||
FormatDebug(flac_domain,
|
||||
"Failed to read FLAC tags: %s",
|
||||
chain.GetStatusString());
|
||||
return false;
|
||||
}
|
||||
|
||||
chain.Scan(handler, handler_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_scan_stream(InputStream &is,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.Read(is)) {
|
||||
FormatDebug(flac_domain,
|
||||
"Failed to read FLAC tags: %s",
|
||||
chain.GetStatusString());
|
||||
return false;
|
||||
}
|
||||
|
||||
chain.Scan(handler, handler_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some glue code around FLAC__stream_decoder_new().
|
||||
*/
|
||||
static FLAC__StreamDecoder *
|
||||
flac_decoder_new(void)
|
||||
{
|
||||
FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
|
||||
if (sd == nullptr) {
|
||||
LogError(flac_domain,
|
||||
"FLAC__stream_decoder_new() failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
|
||||
LogDebug(flac_domain,
|
||||
"FLAC__stream_decoder_set_metadata_respond() has failed");
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
|
||||
FLAC__uint64 duration)
|
||||
{
|
||||
data->total_frames = duration;
|
||||
|
||||
if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
|
||||
LogWarning(flac_domain, "problem reading metadata");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->initialized) {
|
||||
/* done */
|
||||
decoder_initialized(data->decoder, data->audio_format,
|
||||
data->input_stream.seekable,
|
||||
(float)data->total_frames /
|
||||
(float)data->audio_format.sample_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data->input_stream.seekable)
|
||||
/* allow the workaround below only for nonseekable
|
||||
streams*/
|
||||
return false;
|
||||
|
||||
/* no stream_info packet found; try to initialize the decoder
|
||||
from the first frame header */
|
||||
FLAC__stream_decoder_process_single(sd);
|
||||
return data->initialized;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
|
||||
FLAC__uint64 t_start, FLAC__uint64 t_end)
|
||||
{
|
||||
Decoder &decoder = data->decoder;
|
||||
|
||||
data->first_frame = t_start;
|
||||
|
||||
while (true) {
|
||||
DecoderCommand cmd;
|
||||
if (!data->tag.IsEmpty()) {
|
||||
cmd = decoder_tag(data->decoder, data->input_stream,
|
||||
std::move(data->tag));
|
||||
data->tag.Clear();
|
||||
} else
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
FLAC__uint64 seek_sample = t_start +
|
||||
decoder_seek_where(decoder) *
|
||||
data->audio_format.sample_rate;
|
||||
if (seek_sample >= t_start &&
|
||||
(t_end == 0 || seek_sample <= t_end) &&
|
||||
FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
|
||||
data->next_frame = seek_sample;
|
||||
data->position = 0;
|
||||
decoder_command_finished(decoder);
|
||||
} else
|
||||
decoder_seek_error(decoder);
|
||||
} else if (cmd == DecoderCommand::STOP ||
|
||||
FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
|
||||
break;
|
||||
|
||||
if (t_end != 0 && data->next_frame >= t_end)
|
||||
/* end of this sub track */
|
||||
break;
|
||||
|
||||
if (!FLAC__stream_decoder_process_single(flac_dec) &&
|
||||
decoder_get_command(decoder) == DecoderCommand::NONE) {
|
||||
/* a failure that was not triggered by a
|
||||
decoder command */
|
||||
flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderInitStatus
|
||||
stream_init_oggflac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
|
||||
{
|
||||
return FLAC__stream_decoder_init_ogg_stream(flac_dec,
|
||||
FlacInput::Read,
|
||||
FlacInput::Seek,
|
||||
FlacInput::Tell,
|
||||
FlacInput::Length,
|
||||
FlacInput::Eof,
|
||||
flac_write_cb,
|
||||
flacMetadata,
|
||||
FlacInput::Error,
|
||||
data);
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderInitStatus
|
||||
stream_init_flac(FLAC__StreamDecoder *flac_dec, struct flac_data *data)
|
||||
{
|
||||
return FLAC__stream_decoder_init_stream(flac_dec,
|
||||
FlacInput::Read,
|
||||
FlacInput::Seek,
|
||||
FlacInput::Tell,
|
||||
FlacInput::Length,
|
||||
FlacInput::Eof,
|
||||
flac_write_cb,
|
||||
flacMetadata,
|
||||
FlacInput::Error,
|
||||
data);
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderInitStatus
|
||||
stream_init(FLAC__StreamDecoder *flac_dec, struct flac_data *data, bool is_ogg)
|
||||
{
|
||||
return is_ogg
|
||||
? stream_init_oggflac(flac_dec, data)
|
||||
: stream_init_flac(flac_dec, data);
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decode_internal(Decoder &decoder,
|
||||
InputStream &input_stream,
|
||||
bool is_ogg)
|
||||
{
|
||||
FLAC__StreamDecoder *flac_dec;
|
||||
|
||||
flac_dec = flac_decoder_new();
|
||||
if (flac_dec == nullptr)
|
||||
return;
|
||||
|
||||
struct flac_data data(decoder, input_stream);
|
||||
|
||||
FLAC__StreamDecoderInitStatus status =
|
||||
stream_init(flac_dec, &data, is_ogg);
|
||||
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
LogWarning(flac_domain,
|
||||
FLAC__StreamDecoderInitStatusString[status]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!flac_decoder_initialize(&data, flac_dec, 0)) {
|
||||
FLAC__stream_decoder_finish(flac_dec);
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
return;
|
||||
}
|
||||
|
||||
flac_decoder_loop(&data, flac_dec, 0, 0);
|
||||
|
||||
FLAC__stream_decoder_finish(flac_dec);
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decode(Decoder &decoder, InputStream &input_stream)
|
||||
{
|
||||
flac_decode_internal(decoder, input_stream, false);
|
||||
}
|
||||
|
||||
static bool
|
||||
oggflac_init(gcc_unused const config_param ¶m)
|
||||
{
|
||||
return !!FLAC_API_SUPPORTS_OGG_FLAC;
|
||||
}
|
||||
|
||||
static bool
|
||||
oggflac_scan_file(const char *file,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.ReadOgg(file)) {
|
||||
FormatDebug(flac_domain,
|
||||
"Failed to read OggFLAC tags: %s",
|
||||
chain.GetStatusString());
|
||||
return false;
|
||||
}
|
||||
|
||||
chain.Scan(handler, handler_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
oggflac_scan_stream(InputStream &is,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
FlacMetadataChain chain;
|
||||
if (!chain.ReadOgg(is)) {
|
||||
FormatDebug(flac_domain,
|
||||
"Failed to read OggFLAC tags: %s",
|
||||
chain.GetStatusString());
|
||||
return false;
|
||||
}
|
||||
|
||||
chain.Scan(handler, handler_ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
oggflac_decode(Decoder &decoder, InputStream &input_stream)
|
||||
{
|
||||
if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_FLAC)
|
||||
return;
|
||||
|
||||
/* rewind the stream, because ogg_codec_detect() has
|
||||
moved it */
|
||||
input_stream.LockRewind(IgnoreError());
|
||||
|
||||
flac_decode_internal(decoder, input_stream, true);
|
||||
}
|
||||
|
||||
static const char *const oggflac_suffixes[] = { "ogg", "oga", nullptr };
|
||||
static const char *const oggflac_mime_types[] = {
|
||||
"application/ogg",
|
||||
"application/x-ogg",
|
||||
"audio/ogg",
|
||||
"audio/x-flac+ogg",
|
||||
"audio/x-ogg",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin oggflac_decoder_plugin = {
|
||||
"oggflac",
|
||||
oggflac_init,
|
||||
nullptr,
|
||||
oggflac_decode,
|
||||
nullptr,
|
||||
oggflac_scan_file,
|
||||
oggflac_scan_stream,
|
||||
nullptr,
|
||||
oggflac_suffixes,
|
||||
oggflac_mime_types,
|
||||
};
|
||||
|
||||
static const char *const flac_suffixes[] = { "flac", nullptr };
|
||||
static const char *const flac_mime_types[] = {
|
||||
"application/flac",
|
||||
"application/x-flac",
|
||||
"audio/flac",
|
||||
"audio/x-flac",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin flac_decoder_plugin = {
|
||||
"flac",
|
||||
nullptr,
|
||||
nullptr,
|
||||
flac_decode,
|
||||
nullptr,
|
||||
flac_scan_file,
|
||||
flac_scan_stream,
|
||||
nullptr,
|
||||
flac_suffixes,
|
||||
flac_mime_types,
|
||||
};
|
26
src/decoder/plugins/FlacDecoderPlugin.h
Normal file
26
src/decoder/plugins/FlacDecoderPlugin.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_FLAC_H
|
||||
#define MPD_DECODER_FLAC_H
|
||||
|
||||
extern const struct DecoderPlugin flac_decoder_plugin;
|
||||
extern const struct DecoderPlugin oggflac_decoder_plugin;
|
||||
|
||||
#endif
|
24
src/decoder/plugins/FlacDomain.cxx
Normal file
24
src/decoder/plugins/FlacDomain.cxx
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "FlacDomain.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
const Domain flac_domain("flac");
|
27
src/decoder/plugins/FlacDomain.hxx
Normal file
27
src/decoder/plugins/FlacDomain.hxx
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FLAC_DOMAIN_HXX
|
||||
#define MPD_FLAC_DOMAIN_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
extern const class Domain flac_domain;
|
||||
|
||||
#endif
|
114
src/decoder/plugins/FlacIOHandle.cxx
Normal file
114
src/decoder/plugins/FlacIOHandle.cxx
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "FlacIOHandle.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
static size_t
|
||||
FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
|
||||
{
|
||||
InputStream *is = (InputStream *)handle;
|
||||
|
||||
uint8_t *const p0 = (uint8_t *)ptr, *p = p0,
|
||||
*const end = p0 + size * nmemb;
|
||||
|
||||
/* libFLAC is very picky about short reads, and expects the IO
|
||||
callback to fill the whole buffer (undocumented!) */
|
||||
|
||||
Error error;
|
||||
while (p < end) {
|
||||
size_t nbytes = is->LockRead(p, end - p, error);
|
||||
if (nbytes == 0) {
|
||||
if (!error.IsDefined())
|
||||
/* end of file */
|
||||
break;
|
||||
|
||||
if (error.IsDomain(errno_domain))
|
||||
errno = error.GetCode();
|
||||
else
|
||||
/* just some random non-zero
|
||||
errno value */
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
p += nbytes;
|
||||
}
|
||||
|
||||
/* libFLAC expects a clean errno after returning from the IO
|
||||
callbacks (undocumented!) */
|
||||
errno = 0;
|
||||
return (p - p0) / size;
|
||||
}
|
||||
|
||||
static int
|
||||
FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence)
|
||||
{
|
||||
InputStream *is = (InputStream *)handle;
|
||||
|
||||
Error error;
|
||||
return is->LockSeek(offset, whence, error) ? 0 : -1;
|
||||
}
|
||||
|
||||
static FLAC__int64
|
||||
FlacIOTell(FLAC__IOHandle handle)
|
||||
{
|
||||
InputStream *is = (InputStream *)handle;
|
||||
|
||||
return is->offset;
|
||||
}
|
||||
|
||||
static int
|
||||
FlacIOEof(FLAC__IOHandle handle)
|
||||
{
|
||||
InputStream *is = (InputStream *)handle;
|
||||
|
||||
return is->LockIsEOF();
|
||||
}
|
||||
|
||||
static int
|
||||
FlacIOClose(gcc_unused FLAC__IOHandle handle)
|
||||
{
|
||||
/* no-op because the libFLAC caller is repsonsible for closing
|
||||
the #InputStream */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const FLAC__IOCallbacks flac_io_callbacks = {
|
||||
FlacIORead,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
FlacIOEof,
|
||||
FlacIOClose,
|
||||
};
|
||||
|
||||
const FLAC__IOCallbacks flac_io_callbacks_seekable = {
|
||||
FlacIORead,
|
||||
nullptr,
|
||||
FlacIOSeek,
|
||||
FlacIOTell,
|
||||
FlacIOEof,
|
||||
FlacIOClose,
|
||||
};
|
45
src/decoder/plugins/FlacIOHandle.hxx
Normal file
45
src/decoder/plugins/FlacIOHandle.hxx
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FLAC_IO_HANDLE_HXX
|
||||
#define MPD_FLAC_IO_HANDLE_HXX
|
||||
|
||||
#include "Compiler.h"
|
||||
#include "InputStream.hxx"
|
||||
|
||||
#include <FLAC/callback.h>
|
||||
|
||||
extern const FLAC__IOCallbacks flac_io_callbacks;
|
||||
extern const FLAC__IOCallbacks flac_io_callbacks_seekable;
|
||||
|
||||
static inline FLAC__IOHandle
|
||||
ToFlacIOHandle(InputStream &is)
|
||||
{
|
||||
return (FLAC__IOHandle)&is;
|
||||
}
|
||||
|
||||
static inline const FLAC__IOCallbacks &
|
||||
GetFlacIOCallbacks(const InputStream &is)
|
||||
{
|
||||
return is.seekable
|
||||
? flac_io_callbacks_seekable
|
||||
: flac_io_callbacks;
|
||||
}
|
||||
|
||||
#endif
|
154
src/decoder/plugins/FlacInput.cxx
Normal file
154
src/decoder/plugins/FlacInput.cxx
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "FlacInput.hxx"
|
||||
#include "FlacDomain.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "Compiler.h"
|
||||
|
||||
FLAC__StreamDecoderReadStatus
|
||||
FlacInput::Read(FLAC__byte buffer[], size_t *bytes)
|
||||
{
|
||||
size_t r = decoder_read(decoder, input_stream, (void *)buffer, *bytes);
|
||||
*bytes = r;
|
||||
|
||||
if (r == 0) {
|
||||
if (input_stream.LockIsEOF() ||
|
||||
(decoder != nullptr &&
|
||||
decoder_get_command(*decoder) != DecoderCommand::NONE))
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
||||
else
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderSeekStatus
|
||||
FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
|
||||
{
|
||||
if (!input_stream.seekable)
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
|
||||
|
||||
::Error error;
|
||||
if (!input_stream.LockSeek(absolute_byte_offset, SEEK_SET, error)) {
|
||||
LogError(error);
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderTellStatus
|
||||
FlacInput::Tell(FLAC__uint64 *absolute_byte_offset)
|
||||
{
|
||||
if (!input_stream.seekable)
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
|
||||
|
||||
*absolute_byte_offset = (FLAC__uint64)input_stream.offset;
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderLengthStatus
|
||||
FlacInput::Length(FLAC__uint64 *stream_length)
|
||||
{
|
||||
if (input_stream.size < 0)
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
|
||||
|
||||
*stream_length = (FLAC__uint64)input_stream.size;
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
|
||||
}
|
||||
|
||||
FLAC__bool
|
||||
FlacInput::Eof()
|
||||
{
|
||||
return (decoder != nullptr &&
|
||||
decoder_get_command(*decoder) != DecoderCommand::NONE &&
|
||||
decoder_get_command(*decoder) != DecoderCommand::SEEK) ||
|
||||
input_stream.LockIsEOF();
|
||||
}
|
||||
|
||||
void
|
||||
FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
|
||||
{
|
||||
if (decoder == nullptr ||
|
||||
decoder_get_command(*decoder) != DecoderCommand::STOP)
|
||||
LogWarning(flac_domain,
|
||||
FLAC__StreamDecoderErrorStatusString[status]);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderReadStatus
|
||||
FlacInput::Read(gcc_unused const FLAC__StreamDecoder *flac_decoder,
|
||||
FLAC__byte buffer[], size_t *bytes,
|
||||
void *client_data)
|
||||
{
|
||||
FlacInput *i = (FlacInput *)client_data;
|
||||
|
||||
return i->Read(buffer, bytes);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderSeekStatus
|
||||
FlacInput::Seek(gcc_unused const FLAC__StreamDecoder *flac_decoder,
|
||||
FLAC__uint64 absolute_byte_offset, void *client_data)
|
||||
{
|
||||
FlacInput *i = (FlacInput *)client_data;
|
||||
|
||||
return i->Seek(absolute_byte_offset);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderTellStatus
|
||||
FlacInput::Tell(gcc_unused const FLAC__StreamDecoder *flac_decoder,
|
||||
FLAC__uint64 *absolute_byte_offset, void *client_data)
|
||||
{
|
||||
FlacInput *i = (FlacInput *)client_data;
|
||||
|
||||
return i->Tell(absolute_byte_offset);
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderLengthStatus
|
||||
FlacInput::Length(gcc_unused const FLAC__StreamDecoder *flac_decoder,
|
||||
FLAC__uint64 *stream_length, void *client_data)
|
||||
{
|
||||
FlacInput *i = (FlacInput *)client_data;
|
||||
|
||||
return i->Length(stream_length);
|
||||
}
|
||||
|
||||
FLAC__bool
|
||||
FlacInput::Eof(gcc_unused const FLAC__StreamDecoder *flac_decoder,
|
||||
void *client_data)
|
||||
{
|
||||
FlacInput *i = (FlacInput *)client_data;
|
||||
|
||||
return i->Eof();
|
||||
}
|
||||
|
||||
void
|
||||
FlacInput::Error(gcc_unused const FLAC__StreamDecoder *decoder,
|
||||
FLAC__StreamDecoderErrorStatus status, void *client_data)
|
||||
{
|
||||
FlacInput *i = (FlacInput *)client_data;
|
||||
|
||||
i->Error(status);
|
||||
}
|
||||
|
75
src/decoder/plugins/FlacInput.hxx
Normal file
75
src/decoder/plugins/FlacInput.hxx
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FLAC_INPUT_HXX
|
||||
#define MPD_FLAC_INPUT_HXX
|
||||
|
||||
#include <FLAC/stream_decoder.h>
|
||||
|
||||
struct Decoder;
|
||||
struct InputStream;
|
||||
|
||||
/**
|
||||
* This class wraps an #InputStream in libFLAC stream decoder
|
||||
* callbacks.
|
||||
*/
|
||||
class FlacInput {
|
||||
Decoder *const decoder;
|
||||
|
||||
InputStream &input_stream;
|
||||
|
||||
public:
|
||||
FlacInput(InputStream &_input_stream,
|
||||
Decoder *_decoder=nullptr)
|
||||
:decoder(_decoder), input_stream(_input_stream) {}
|
||||
|
||||
protected:
|
||||
FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes);
|
||||
FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset);
|
||||
FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset);
|
||||
FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length);
|
||||
FLAC__bool Eof();
|
||||
void Error(FLAC__StreamDecoderErrorStatus status);
|
||||
|
||||
public:
|
||||
static FLAC__StreamDecoderReadStatus
|
||||
Read(const FLAC__StreamDecoder *flac_decoder,
|
||||
FLAC__byte buffer[], size_t *bytes, void *client_data);
|
||||
|
||||
static FLAC__StreamDecoderSeekStatus
|
||||
Seek(const FLAC__StreamDecoder *flac_decoder,
|
||||
FLAC__uint64 absolute_byte_offset, void *client_data);
|
||||
|
||||
static FLAC__StreamDecoderTellStatus
|
||||
Tell(const FLAC__StreamDecoder *flac_decoder,
|
||||
FLAC__uint64 *absolute_byte_offset, void *client_data);
|
||||
|
||||
static FLAC__StreamDecoderLengthStatus
|
||||
Length(const FLAC__StreamDecoder *flac_decoder,
|
||||
FLAC__uint64 *stream_length, void *client_data);
|
||||
|
||||
static FLAC__bool
|
||||
Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data);
|
||||
|
||||
static void
|
||||
Error(const FLAC__StreamDecoder *decoder,
|
||||
FLAC__StreamDecoderErrorStatus status, void *client_data);
|
||||
};
|
||||
|
||||
#endif
|
223
src/decoder/plugins/FlacMetadata.cxx
Normal file
223
src/decoder/plugins/FlacMetadata.cxx
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "FlacMetadata.hxx"
|
||||
#include "XiphTags.hxx"
|
||||
#include "MixRampInfo.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "tag/TagTable.hxx"
|
||||
#include "tag/TagBuilder.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "ReplayGainInfo.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/SplitString.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static const char *
|
||||
vorbis_comment_value(const FLAC__StreamMetadata *block,
|
||||
const char *name)
|
||||
{
|
||||
int offset =
|
||||
FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
|
||||
name);
|
||||
if (offset < 0)
|
||||
return nullptr;
|
||||
|
||||
size_t name_length = strlen(name);
|
||||
|
||||
const FLAC__StreamMetadata_VorbisComment_Entry &vc =
|
||||
block->data.vorbis_comment.comments[offset];
|
||||
const char *comment = (const char *)vc.entry;
|
||||
|
||||
/* 1 is for '=' */
|
||||
return comment + name_length + 1;
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_find_float_comment(const FLAC__StreamMetadata *block,
|
||||
const char *cmnt, float *fl)
|
||||
{
|
||||
const char *value = vorbis_comment_value(block, cmnt);
|
||||
if (value == nullptr)
|
||||
return false;
|
||||
|
||||
*fl = (float)atof(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
flac_parse_replay_gain(ReplayGainInfo &rgi,
|
||||
const FLAC__StreamMetadata *block)
|
||||
{
|
||||
rgi.Clear();
|
||||
|
||||
bool found = false;
|
||||
if (flac_find_float_comment(block, "replaygain_album_gain",
|
||||
&rgi.tuples[REPLAY_GAIN_ALBUM].gain))
|
||||
found = true;
|
||||
if (flac_find_float_comment(block, "replaygain_album_peak",
|
||||
&rgi.tuples[REPLAY_GAIN_ALBUM].peak))
|
||||
found = true;
|
||||
if (flac_find_float_comment(block, "replaygain_track_gain",
|
||||
&rgi.tuples[REPLAY_GAIN_TRACK].gain))
|
||||
found = true;
|
||||
if (flac_find_float_comment(block, "replaygain_track_peak",
|
||||
&rgi.tuples[REPLAY_GAIN_TRACK].peak))
|
||||
found = true;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static std::string
|
||||
flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt)
|
||||
{
|
||||
const char *value = vorbis_comment_value(block, cmnt);
|
||||
if (value == nullptr)
|
||||
return std::string();
|
||||
|
||||
return std::string(value);
|
||||
}
|
||||
|
||||
MixRampInfo
|
||||
flac_parse_mixramp(const FLAC__StreamMetadata *block)
|
||||
{
|
||||
MixRampInfo mix_ramp;
|
||||
mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start"));
|
||||
mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end"));
|
||||
return mix_ramp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified name matches the entry's name, and if yes,
|
||||
* returns the comment value;
|
||||
*/
|
||||
static const char *
|
||||
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
|
||||
const char *name)
|
||||
{
|
||||
size_t name_length = strlen(name);
|
||||
const char *comment = (const char*)entry->entry;
|
||||
|
||||
if (!StringEqualsCaseASCII(comment, name, name_length))
|
||||
return nullptr;
|
||||
|
||||
if (comment[name_length] == '=') {
|
||||
return comment + name_length + 1;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the comment's name equals the passed name, and if so, copy
|
||||
* the comment value into the tag.
|
||||
*/
|
||||
static bool
|
||||
flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
|
||||
const char *name, TagType tag_type,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
const char *value = flac_comment_value(entry, name);
|
||||
if (value != nullptr) {
|
||||
tag_handler_invoke_tag(handler, handler_ctx, tag_type, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
if (handler->pair != nullptr) {
|
||||
const char *comment = (const char *)entry->entry;
|
||||
const SplitString split(comment, '=');
|
||||
if (split.IsDefined() && !split.IsEmpty())
|
||||
tag_handler_invoke_pair(handler, handler_ctx,
|
||||
split.GetFirst(),
|
||||
split.GetSecond());
|
||||
}
|
||||
|
||||
for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i)
|
||||
if (flac_copy_comment(entry, i->name, i->type,
|
||||
handler, handler_ctx))
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
if (flac_copy_comment(entry,
|
||||
tag_item_names[i], (TagType)i,
|
||||
handler, handler_ctx))
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
for (unsigned i = 0; i < comment->num_comments; ++i)
|
||||
flac_scan_comment(&comment->comments[i],
|
||||
handler, handler_ctx);
|
||||
}
|
||||
|
||||
void
|
||||
flac_scan_metadata(const FLAC__StreamMetadata *block,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
switch (block->type) {
|
||||
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
||||
flac_scan_comments(&block->data.vorbis_comment,
|
||||
handler, handler_ctx);
|
||||
break;
|
||||
|
||||
case FLAC__METADATA_TYPE_STREAMINFO:
|
||||
if (block->data.stream_info.sample_rate > 0)
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
flac_duration(&block->data.stream_info));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Tag
|
||||
flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment)
|
||||
{
|
||||
TagBuilder tag_builder;
|
||||
flac_scan_comments(comment, &add_tag_handler, &tag_builder);
|
||||
return tag_builder.Commit();
|
||||
}
|
||||
|
||||
void
|
||||
FlacMetadataChain::Scan(const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
FLACMetadataIterator iterator(*this);
|
||||
|
||||
do {
|
||||
FLAC__StreamMetadata *block = iterator.GetBlock();
|
||||
if (block == nullptr)
|
||||
break;
|
||||
|
||||
flac_scan_metadata(block, handler, handler_ctx);
|
||||
} while (iterator.Next());
|
||||
}
|
140
src/decoder/plugins/FlacMetadata.hxx
Normal file
140
src/decoder/plugins/FlacMetadata.hxx
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FLAC_METADATA_H
|
||||
#define MPD_FLAC_METADATA_H
|
||||
|
||||
#include "Compiler.h"
|
||||
#include "FlacIOHandle.hxx"
|
||||
|
||||
#include <FLAC/metadata.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
struct tag_handler;
|
||||
class MixRampInfo;
|
||||
|
||||
class FlacMetadataChain {
|
||||
FLAC__Metadata_Chain *chain;
|
||||
|
||||
public:
|
||||
FlacMetadataChain():chain(::FLAC__metadata_chain_new()) {}
|
||||
|
||||
~FlacMetadataChain() {
|
||||
::FLAC__metadata_chain_delete(chain);
|
||||
}
|
||||
|
||||
explicit operator FLAC__Metadata_Chain *() {
|
||||
return chain;
|
||||
}
|
||||
|
||||
bool Read(const char *path) {
|
||||
return ::FLAC__metadata_chain_read(chain, path);
|
||||
}
|
||||
|
||||
bool Read(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) {
|
||||
return ::FLAC__metadata_chain_read_with_callbacks(chain,
|
||||
handle,
|
||||
callbacks);
|
||||
}
|
||||
|
||||
bool Read(InputStream &is) {
|
||||
return Read(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is));
|
||||
}
|
||||
|
||||
bool ReadOgg(const char *path) {
|
||||
return ::FLAC__metadata_chain_read_ogg(chain, path);
|
||||
}
|
||||
|
||||
bool ReadOgg(FLAC__IOHandle handle, FLAC__IOCallbacks callbacks) {
|
||||
return ::FLAC__metadata_chain_read_ogg_with_callbacks(chain,
|
||||
handle,
|
||||
callbacks);
|
||||
}
|
||||
|
||||
bool ReadOgg(InputStream &is) {
|
||||
return ReadOgg(::ToFlacIOHandle(is), ::GetFlacIOCallbacks(is));
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
FLAC__Metadata_ChainStatus GetStatus() const {
|
||||
return ::FLAC__metadata_chain_status(chain);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
const char *GetStatusString() const {
|
||||
return FLAC__Metadata_ChainStatusString[GetStatus()];
|
||||
}
|
||||
|
||||
void Scan(const tag_handler *handler, void *handler_ctx);
|
||||
};
|
||||
|
||||
class FLACMetadataIterator {
|
||||
FLAC__Metadata_Iterator *iterator;
|
||||
|
||||
public:
|
||||
FLACMetadataIterator():iterator(::FLAC__metadata_iterator_new()) {}
|
||||
|
||||
FLACMetadataIterator(FlacMetadataChain &chain)
|
||||
:iterator(::FLAC__metadata_iterator_new()) {
|
||||
::FLAC__metadata_iterator_init(iterator,
|
||||
(FLAC__Metadata_Chain *)chain);
|
||||
}
|
||||
|
||||
~FLACMetadataIterator() {
|
||||
::FLAC__metadata_iterator_delete(iterator);
|
||||
}
|
||||
|
||||
bool Next() {
|
||||
return ::FLAC__metadata_iterator_next(iterator);
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
FLAC__StreamMetadata *GetBlock() {
|
||||
return ::FLAC__metadata_iterator_get_block(iterator);
|
||||
}
|
||||
};
|
||||
|
||||
struct Tag;
|
||||
struct ReplayGainInfo;
|
||||
|
||||
static inline unsigned
|
||||
flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
|
||||
{
|
||||
assert(stream_info->sample_rate > 0);
|
||||
|
||||
return (stream_info->total_samples + stream_info->sample_rate - 1) /
|
||||
stream_info->sample_rate;
|
||||
}
|
||||
|
||||
bool
|
||||
flac_parse_replay_gain(ReplayGainInfo &rgi,
|
||||
const FLAC__StreamMetadata *block);
|
||||
|
||||
MixRampInfo
|
||||
flac_parse_mixramp(const FLAC__StreamMetadata *block);
|
||||
|
||||
Tag
|
||||
flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment);
|
||||
|
||||
void
|
||||
flac_scan_metadata(const FLAC__StreamMetadata *block,
|
||||
const tag_handler *handler, void *handler_ctx);
|
||||
|
||||
#endif
|
110
src/decoder/plugins/FlacPcm.cxx
Normal file
110
src/decoder/plugins/FlacPcm.cxx
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "FlacPcm.hxx"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
void
|
||||
flac_convert(void *dest,
|
||||
unsigned int num_channels, SampleFormat sample_format,
|
||||
const FLAC__int32 *const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
switch (sample_format) {
|
||||
case SampleFormat::S16:
|
||||
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 SampleFormat::S24_P32:
|
||||
case SampleFormat::S32:
|
||||
flac_convert_32((int32_t*)dest, num_channels, buf,
|
||||
position, end);
|
||||
break;
|
||||
|
||||
case SampleFormat::S8:
|
||||
flac_convert_8((int8_t*)dest, num_channels, buf,
|
||||
position, end);
|
||||
break;
|
||||
|
||||
case SampleFormat::FLOAT:
|
||||
case SampleFormat::DSD:
|
||||
case SampleFormat::UNDEFINED:
|
||||
assert(false);
|
||||
gcc_unreachable();
|
||||
}
|
||||
}
|
33
src/decoder/plugins/FlacPcm.hxx
Normal file
33
src/decoder/plugins/FlacPcm.hxx
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FLAC_PCM_HXX
|
||||
#define MPD_FLAC_PCM_HXX
|
||||
|
||||
#include "AudioFormat.hxx"
|
||||
|
||||
#include <FLAC/ordinals.h>
|
||||
|
||||
void
|
||||
flac_convert(void *dest,
|
||||
unsigned int num_channels, SampleFormat sample_format,
|
||||
const FLAC__int32 *const buf[],
|
||||
unsigned int position, unsigned int end);
|
||||
|
||||
#endif
|
224
src/decoder/plugins/FluidsynthDecoderPlugin.cxx
Normal file
224
src/decoder/plugins/FluidsynthDecoderPlugin.cxx
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "FluidsynthDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <fluidsynth.h>
|
||||
|
||||
static constexpr Domain fluidsynth_domain("fluidsynth");
|
||||
|
||||
static unsigned sample_rate;
|
||||
static const char *soundfont_path;
|
||||
|
||||
/**
|
||||
* Convert a fluidsynth log level to a GLib log level.
|
||||
*/
|
||||
static LogLevel
|
||||
fluidsynth_level_to_mpd(enum fluid_log_level level)
|
||||
{
|
||||
switch (level) {
|
||||
case FLUID_PANIC:
|
||||
case FLUID_ERR:
|
||||
return LogLevel::ERROR;
|
||||
|
||||
case FLUID_WARN:
|
||||
return LogLevel::WARNING;
|
||||
|
||||
case FLUID_INFO:
|
||||
return LogLevel::INFO;
|
||||
|
||||
case FLUID_DBG:
|
||||
case LAST_LOG_LEVEL:
|
||||
return LogLevel::DEBUG;
|
||||
}
|
||||
|
||||
/* invalid fluidsynth log level */
|
||||
return LogLevel::INFO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The fluidsynth logging callback. It forwards messages to the GLib
|
||||
* logging library.
|
||||
*/
|
||||
static void
|
||||
fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data)
|
||||
{
|
||||
Log(fluidsynth_domain,
|
||||
fluidsynth_level_to_mpd(fluid_log_level(level)),
|
||||
message);
|
||||
}
|
||||
|
||||
static bool
|
||||
fluidsynth_init(const config_param ¶m)
|
||||
{
|
||||
Error error;
|
||||
|
||||
sample_rate = param.GetBlockValue("sample_rate", 48000u);
|
||||
if (!audio_check_sample_rate(sample_rate, error)) {
|
||||
LogError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
soundfont_path = param.GetBlockValue("soundfont",
|
||||
"/usr/share/sounds/sf2/FluidR3_GM.sf2");
|
||||
|
||||
fluid_set_log_function(LAST_LOG_LEVEL,
|
||||
fluidsynth_mpd_log_function, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
fluidsynth_file_decode(Decoder &decoder, const char *path_fs)
|
||||
{
|
||||
char setting_sample_rate[] = "synth.sample-rate";
|
||||
/*
|
||||
char setting_verbose[] = "synth.verbose";
|
||||
char setting_yes[] = "yes";
|
||||
*/
|
||||
fluid_settings_t *settings;
|
||||
fluid_synth_t *synth;
|
||||
fluid_player_t *player;
|
||||
int ret;
|
||||
|
||||
/* set up fluid settings */
|
||||
|
||||
settings = new_fluid_settings();
|
||||
if (settings == nullptr)
|
||||
return;
|
||||
|
||||
fluid_settings_setnum(settings, setting_sample_rate, sample_rate);
|
||||
|
||||
/*
|
||||
fluid_settings_setstr(settings, setting_verbose, setting_yes);
|
||||
*/
|
||||
|
||||
/* create the fluid synth */
|
||||
|
||||
synth = new_fluid_synth(settings);
|
||||
if (synth == nullptr) {
|
||||
delete_fluid_settings(settings);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = fluid_synth_sfload(synth, soundfont_path, true);
|
||||
if (ret < 0) {
|
||||
LogWarning(fluidsynth_domain, "fluid_synth_sfload() failed");
|
||||
delete_fluid_synth(synth);
|
||||
delete_fluid_settings(settings);
|
||||
return;
|
||||
}
|
||||
|
||||
/* create the fluid player */
|
||||
|
||||
player = new_fluid_player(synth);
|
||||
if (player == nullptr) {
|
||||
delete_fluid_synth(synth);
|
||||
delete_fluid_settings(settings);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = fluid_player_add(player, path_fs);
|
||||
if (ret != 0) {
|
||||
LogWarning(fluidsynth_domain, "fluid_player_add() failed");
|
||||
delete_fluid_player(player);
|
||||
delete_fluid_synth(synth);
|
||||
delete_fluid_settings(settings);
|
||||
return;
|
||||
}
|
||||
|
||||
/* start the player */
|
||||
|
||||
ret = fluid_player_play(player);
|
||||
if (ret != 0) {
|
||||
LogWarning(fluidsynth_domain, "fluid_player_play() failed");
|
||||
delete_fluid_player(player);
|
||||
delete_fluid_synth(synth);
|
||||
delete_fluid_settings(settings);
|
||||
return;
|
||||
}
|
||||
|
||||
/* initialization complete - announce the audio format to the
|
||||
MPD core */
|
||||
|
||||
const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2);
|
||||
decoder_initialized(decoder, audio_format, false, -1);
|
||||
|
||||
DecoderCommand cmd;
|
||||
while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
|
||||
int16_t buffer[2048];
|
||||
const unsigned max_frames = ARRAY_SIZE(buffer) / 2;
|
||||
|
||||
/* read samples from fluidsynth and send them to the
|
||||
MPD core */
|
||||
|
||||
ret = fluid_synth_write_s16(synth, max_frames,
|
||||
buffer, 0, 2,
|
||||
buffer, 1, 2);
|
||||
if (ret != 0)
|
||||
break;
|
||||
|
||||
cmd = decoder_data(decoder, nullptr, buffer, sizeof(buffer),
|
||||
0);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
break;
|
||||
}
|
||||
|
||||
/* clean up */
|
||||
|
||||
fluid_player_stop(player);
|
||||
fluid_player_join(player);
|
||||
|
||||
delete_fluid_player(player);
|
||||
delete_fluid_synth(synth);
|
||||
delete_fluid_settings(settings);
|
||||
}
|
||||
|
||||
static bool
|
||||
fluidsynth_scan_file(const char *file,
|
||||
gcc_unused const struct tag_handler *handler,
|
||||
gcc_unused void *handler_ctx)
|
||||
{
|
||||
return fluid_is_midifile(file);
|
||||
}
|
||||
|
||||
static const char *const fluidsynth_suffixes[] = {
|
||||
"mid",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin fluidsynth_decoder_plugin = {
|
||||
"fluidsynth",
|
||||
fluidsynth_init,
|
||||
nullptr,
|
||||
nullptr,
|
||||
fluidsynth_file_decode,
|
||||
fluidsynth_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
fluidsynth_suffixes,
|
||||
nullptr,
|
||||
};
|
25
src/decoder/plugins/FluidsynthDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/FluidsynthDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_FLUIDSYNTH_HXX
|
||||
#define MPD_DECODER_FLUIDSYNTH_HXX
|
||||
|
||||
extern const struct DecoderPlugin fluidsynth_decoder_plugin;
|
||||
|
||||
#endif
|
295
src/decoder/plugins/GmeDecoderPlugin.cxx
Normal file
295
src/decoder/plugins/GmeDecoderPlugin.cxx
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "GmeDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Alloc.hxx"
|
||||
#include "util/FormatString.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <glib.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <gme/gme.h>
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
static constexpr Domain gme_domain("gme");
|
||||
|
||||
static constexpr unsigned GME_SAMPLE_RATE = 44100;
|
||||
static constexpr unsigned GME_CHANNELS = 2;
|
||||
static constexpr unsigned GME_BUFFER_FRAMES = 2048;
|
||||
static constexpr unsigned GME_BUFFER_SAMPLES =
|
||||
GME_BUFFER_FRAMES * GME_CHANNELS;
|
||||
|
||||
/**
|
||||
* returns the file path stripped of any /tune_xxx.* subtune
|
||||
* suffix
|
||||
*/
|
||||
static char *
|
||||
get_container_name(const char *path_fs)
|
||||
{
|
||||
const char *subtune_suffix = uri_get_suffix(path_fs);
|
||||
char *path_container = xstrdup(path_fs);
|
||||
|
||||
char pat[64];
|
||||
snprintf(pat, sizeof(pat), "%s%s",
|
||||
"*/" SUBTUNE_PREFIX "???.",
|
||||
subtune_suffix);
|
||||
GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
|
||||
if (!g_pattern_match(path_with_subtune,
|
||||
strlen(path_container), path_container, nullptr)) {
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
return path_container;
|
||||
}
|
||||
|
||||
char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
|
||||
if (ptr != nullptr)
|
||||
*ptr='\0';
|
||||
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
return path_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
|
||||
* is appended.
|
||||
*/
|
||||
static int
|
||||
get_song_num(const char *path_fs)
|
||||
{
|
||||
const char *subtune_suffix = uri_get_suffix(path_fs);
|
||||
|
||||
char pat[64];
|
||||
snprintf(pat, sizeof(pat), "%s%s",
|
||||
"*/" SUBTUNE_PREFIX "???.",
|
||||
subtune_suffix);
|
||||
GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
|
||||
|
||||
if (g_pattern_match(path_with_subtune,
|
||||
strlen(path_fs), path_fs, nullptr)) {
|
||||
char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
if (!sub)
|
||||
return 0;
|
||||
|
||||
sub += strlen("/" SUBTUNE_PREFIX);
|
||||
int song_num = strtol(sub, nullptr, 10);
|
||||
|
||||
return song_num - 1;
|
||||
} else {
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static char *
|
||||
gme_container_scan(const char *path_fs, const unsigned int tnum)
|
||||
{
|
||||
Music_Emu *emu;
|
||||
const char *gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const unsigned num_songs = gme_track_count(emu);
|
||||
/* if it only contains a single tune, don't treat as container */
|
||||
if (num_songs < 2)
|
||||
return nullptr;
|
||||
|
||||
const char *subtune_suffix = uri_get_suffix(path_fs);
|
||||
if (tnum <= num_songs){
|
||||
return FormatNew(SUBTUNE_PREFIX "%03u.%s",
|
||||
tnum, subtune_suffix);
|
||||
} else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void
|
||||
gme_file_decode(Decoder &decoder, const char *path_fs)
|
||||
{
|
||||
char *path_container = get_container_name(path_fs);
|
||||
|
||||
Music_Emu *emu;
|
||||
const char *gme_err =
|
||||
gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
|
||||
free(path_container);
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
return;
|
||||
}
|
||||
|
||||
gme_info_t *ti;
|
||||
const int song_num = get_song_num(path_fs);
|
||||
gme_err = gme_track_info(emu, &ti, song_num);
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
gme_delete(emu);
|
||||
return;
|
||||
}
|
||||
|
||||
const float song_len = ti->length > 0
|
||||
? ti->length / 1000.0
|
||||
: -1.0;
|
||||
|
||||
/* initialize the MPD decoder */
|
||||
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE,
|
||||
SampleFormat::S16, GME_CHANNELS,
|
||||
error)) {
|
||||
LogError(error);
|
||||
gme_free_info(ti);
|
||||
gme_delete(emu);
|
||||
return;
|
||||
}
|
||||
|
||||
decoder_initialized(decoder, audio_format, true, song_len);
|
||||
|
||||
gme_err = gme_start_track(emu, song_num);
|
||||
if (gme_err != nullptr)
|
||||
LogWarning(gme_domain, gme_err);
|
||||
|
||||
if (ti->length > 0)
|
||||
gme_set_fade(emu, ti->length);
|
||||
|
||||
/* play */
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
short buf[GME_BUFFER_SAMPLES];
|
||||
gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
return;
|
||||
}
|
||||
|
||||
cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0);
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
float where = decoder_seek_where(decoder);
|
||||
gme_err = gme_seek(emu, int(where * 1000));
|
||||
if (gme_err != nullptr)
|
||||
LogWarning(gme_domain, gme_err);
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
|
||||
if (gme_track_ended(emu))
|
||||
break;
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
gme_free_info(ti);
|
||||
gme_delete(emu);
|
||||
}
|
||||
|
||||
static bool
|
||||
gme_scan_file(const char *path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
char *path_container = get_container_name(path_fs);
|
||||
|
||||
Music_Emu *emu;
|
||||
const char *gme_err =
|
||||
gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
|
||||
g_free(path_container);
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
return false;
|
||||
}
|
||||
|
||||
const int song_num = get_song_num(path_fs);
|
||||
|
||||
gme_info_t *ti;
|
||||
gme_err = gme_track_info(emu, &ti, song_num);
|
||||
if (gme_err != nullptr) {
|
||||
LogWarning(gme_domain, gme_err);
|
||||
gme_delete(emu);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(ti != nullptr);
|
||||
|
||||
if (ti->length > 0)
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
ti->length / 100);
|
||||
|
||||
if (ti->song != nullptr) {
|
||||
if (gme_track_count(emu) > 1) {
|
||||
/* start numbering subtunes from 1 */
|
||||
char tag_title[1024];
|
||||
snprintf(tag_title, sizeof(tag_title),
|
||||
"%s (%d/%d)",
|
||||
ti->song, song_num + 1,
|
||||
gme_track_count(emu));
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_TITLE, tag_title);
|
||||
} else
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_TITLE, ti->song);
|
||||
}
|
||||
|
||||
if (ti->author != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_ARTIST, ti->author);
|
||||
|
||||
if (ti->game != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_ALBUM, ti->game);
|
||||
|
||||
if (ti->comment != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_COMMENT, ti->comment);
|
||||
|
||||
if (ti->copyright != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_DATE, ti->copyright);
|
||||
|
||||
gme_free_info(ti);
|
||||
gme_delete(emu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const gme_suffixes[] = {
|
||||
"ay", "gbs", "gym", "hes", "kss", "nsf",
|
||||
"nsfe", "sap", "spc", "vgm", "vgz",
|
||||
nullptr
|
||||
};
|
||||
|
||||
extern const struct DecoderPlugin gme_decoder_plugin;
|
||||
const struct DecoderPlugin gme_decoder_plugin = {
|
||||
"gme",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
gme_file_decode,
|
||||
gme_scan_file,
|
||||
nullptr,
|
||||
gme_container_scan,
|
||||
gme_suffixes,
|
||||
nullptr,
|
||||
};
|
25
src/decoder/plugins/GmeDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/GmeDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_GME_HXX
|
||||
#define MPD_DECODER_GME_HXX
|
||||
|
||||
extern const struct DecoderPlugin gme_decoder_plugin;
|
||||
|
||||
#endif
|
1155
src/decoder/plugins/MadDecoderPlugin.cxx
Normal file
1155
src/decoder/plugins/MadDecoderPlugin.cxx
Normal file
File diff suppressed because it is too large
Load Diff
25
src/decoder/plugins/MadDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/MadDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_MAD_HXX
|
||||
#define MPD_DECODER_MAD_HXX
|
||||
|
||||
extern const struct DecoderPlugin mad_decoder_plugin;
|
||||
|
||||
#endif
|
248
src/decoder/plugins/MikmodDecoderPlugin.cxx
Normal file
248
src/decoder/plugins/MikmodDecoderPlugin.cxx
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "MikmodDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <mikmod.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static constexpr Domain mikmod_domain("mikmod");
|
||||
|
||||
/* this is largely copied from alsaplayer */
|
||||
|
||||
static constexpr size_t MIKMOD_FRAME_SIZE = 4096;
|
||||
|
||||
static BOOL
|
||||
mikmod_mpd_init(void)
|
||||
{
|
||||
return VC_Init();
|
||||
}
|
||||
|
||||
static void
|
||||
mikmod_mpd_exit(void)
|
||||
{
|
||||
VC_Exit();
|
||||
}
|
||||
|
||||
static void
|
||||
mikmod_mpd_update(void)
|
||||
{
|
||||
}
|
||||
|
||||
static BOOL
|
||||
mikmod_mpd_is_present(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static char drv_name[] = PACKAGE_NAME;
|
||||
static char drv_version[] = VERSION;
|
||||
|
||||
#if (LIBMIKMOD_VERSION > 0x030106)
|
||||
static char drv_alias[] = PACKAGE;
|
||||
#endif
|
||||
|
||||
static MDRIVER drv_mpd = {
|
||||
nullptr,
|
||||
drv_name,
|
||||
drv_version,
|
||||
0,
|
||||
255,
|
||||
#if (LIBMIKMOD_VERSION > 0x030106)
|
||||
drv_alias,
|
||||
#if (LIBMIKMOD_VERSION >= 0x030200)
|
||||
nullptr, /* CmdLineHelp */
|
||||
#endif
|
||||
nullptr, /* CommandLine */
|
||||
#endif
|
||||
mikmod_mpd_is_present,
|
||||
VC_SampleLoad,
|
||||
VC_SampleUnload,
|
||||
VC_SampleSpace,
|
||||
VC_SampleLength,
|
||||
mikmod_mpd_init,
|
||||
mikmod_mpd_exit,
|
||||
nullptr,
|
||||
VC_SetNumVoices,
|
||||
VC_PlayStart,
|
||||
VC_PlayStop,
|
||||
mikmod_mpd_update,
|
||||
nullptr,
|
||||
VC_VoiceSetVolume,
|
||||
VC_VoiceGetVolume,
|
||||
VC_VoiceSetFrequency,
|
||||
VC_VoiceGetFrequency,
|
||||
VC_VoiceSetPanning,
|
||||
VC_VoiceGetPanning,
|
||||
VC_VoicePlay,
|
||||
VC_VoiceStop,
|
||||
VC_VoiceStopped,
|
||||
VC_VoiceGetPosition,
|
||||
VC_VoiceRealVolume
|
||||
};
|
||||
|
||||
static bool mikmod_loop;
|
||||
static unsigned mikmod_sample_rate;
|
||||
|
||||
static bool
|
||||
mikmod_decoder_init(const config_param ¶m)
|
||||
{
|
||||
static char params[] = "";
|
||||
|
||||
mikmod_loop = param.GetBlockValue("loop", false);
|
||||
mikmod_sample_rate = param.GetBlockValue("sample_rate", 44100u);
|
||||
if (!audio_valid_sample_rate(mikmod_sample_rate))
|
||||
FormatFatalError("Invalid sample rate in line %d: %u",
|
||||
param.line, mikmod_sample_rate);
|
||||
|
||||
md_device = 0;
|
||||
md_reverb = 0;
|
||||
|
||||
MikMod_RegisterDriver(&drv_mpd);
|
||||
MikMod_RegisterAllLoaders();
|
||||
|
||||
md_pansep = 64;
|
||||
md_mixfreq = mikmod_sample_rate;
|
||||
md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
|
||||
DMODE_16BITS);
|
||||
|
||||
if (MikMod_Init(params)) {
|
||||
FormatError(mikmod_domain,
|
||||
"Could not init MikMod: %s",
|
||||
MikMod_strerror(MikMod_errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
mikmod_decoder_finish(void)
|
||||
{
|
||||
MikMod_Exit();
|
||||
}
|
||||
|
||||
static void
|
||||
mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs)
|
||||
{
|
||||
/* deconstify the path because libmikmod wants a non-const
|
||||
string pointer */
|
||||
char *const path2 = const_cast<char *>(path_fs);
|
||||
|
||||
MODULE *handle;
|
||||
int ret;
|
||||
SBYTE buffer[MIKMOD_FRAME_SIZE];
|
||||
|
||||
handle = Player_Load(path2, 128, 0);
|
||||
|
||||
if (handle == nullptr) {
|
||||
FormatError(mikmod_domain,
|
||||
"failed to open mod: %s", path_fs);
|
||||
return;
|
||||
}
|
||||
|
||||
handle->loop = mikmod_loop;
|
||||
|
||||
const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2);
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
decoder_initialized(decoder, audio_format, false, 0);
|
||||
|
||||
Player_Start(handle);
|
||||
|
||||
DecoderCommand cmd = DecoderCommand::NONE;
|
||||
while (cmd == DecoderCommand::NONE && Player_Active()) {
|
||||
ret = VC_WriteBytes(buffer, sizeof(buffer));
|
||||
cmd = decoder_data(decoder, nullptr, buffer, ret, 0);
|
||||
}
|
||||
|
||||
Player_Stop();
|
||||
Player_Free(handle);
|
||||
}
|
||||
|
||||
static bool
|
||||
mikmod_decoder_scan_file(const char *path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
/* deconstify the path because libmikmod wants a non-const
|
||||
string pointer */
|
||||
char *const path2 = const_cast<char *>(path_fs);
|
||||
|
||||
MODULE *handle = Player_Load(path2, 128, 0);
|
||||
|
||||
if (handle == nullptr) {
|
||||
FormatDebug(mikmod_domain,
|
||||
"Failed to open file: %s", path_fs);
|
||||
return false;
|
||||
}
|
||||
|
||||
Player_Free(handle);
|
||||
|
||||
char *title = Player_LoadTitle(path2);
|
||||
if (title != nullptr) {
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_TITLE, title);
|
||||
#if (LIBMIKMOD_VERSION >= 0x030200)
|
||||
MikMod_free(title);
|
||||
#else
|
||||
free(title);
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const mikmod_decoder_suffixes[] = {
|
||||
"amf",
|
||||
"dsm",
|
||||
"far",
|
||||
"gdm",
|
||||
"imf",
|
||||
"it",
|
||||
"med",
|
||||
"mod",
|
||||
"mtm",
|
||||
"s3m",
|
||||
"stm",
|
||||
"stx",
|
||||
"ult",
|
||||
"uni",
|
||||
"xm",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin mikmod_decoder_plugin = {
|
||||
"mikmod",
|
||||
mikmod_decoder_init,
|
||||
mikmod_decoder_finish,
|
||||
nullptr,
|
||||
mikmod_decoder_file_decode,
|
||||
mikmod_decoder_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
mikmod_decoder_suffixes,
|
||||
nullptr,
|
||||
};
|
25
src/decoder/plugins/MikmodDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/MikmodDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_MIKMOD_HXX
|
||||
#define MPD_DECODER_MIKMOD_HXX
|
||||
|
||||
extern const struct DecoderPlugin mikmod_decoder_plugin;
|
||||
|
||||
#endif
|
215
src/decoder/plugins/ModplugDecoderPlugin.cxx
Normal file
215
src/decoder/plugins/ModplugDecoderPlugin.cxx
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ModplugDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <libmodplug/modplug.h>
|
||||
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static constexpr Domain modplug_domain("modplug");
|
||||
|
||||
static constexpr size_t MODPLUG_FRAME_SIZE = 4096;
|
||||
static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024;
|
||||
static constexpr InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024;
|
||||
|
||||
static int modplug_loop_count;
|
||||
|
||||
static bool
|
||||
modplug_decoder_init(const config_param ¶m)
|
||||
{
|
||||
modplug_loop_count = param.GetBlockValue("loop_count", 0);
|
||||
if (modplug_loop_count < -1)
|
||||
FormatFatalError("Invalid loop count in line %d: %i",
|
||||
param.line, modplug_loop_count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static WritableBuffer<uint8_t>
|
||||
mod_loadfile(Decoder *decoder, InputStream &is)
|
||||
{
|
||||
const InputStream::offset_type size = is.GetSize();
|
||||
|
||||
if (size == 0) {
|
||||
LogWarning(modplug_domain, "file is empty");
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
|
||||
if (size > MODPLUG_FILE_LIMIT) {
|
||||
LogWarning(modplug_domain, "file too large");
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
|
||||
//known/unknown size, preallocate array, lets read in chunks
|
||||
|
||||
const bool is_stream = size < 0;
|
||||
|
||||
WritableBuffer<uint8_t> buffer;
|
||||
buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size;
|
||||
buffer.data = new uint8_t[buffer.size];
|
||||
|
||||
uint8_t *const end = buffer.end();
|
||||
uint8_t *p = buffer.begin();
|
||||
|
||||
while (true) {
|
||||
size_t ret = decoder_read(decoder, is, p, end - p);
|
||||
if (ret == 0) {
|
||||
if (is.LockIsEOF())
|
||||
/* end of file */
|
||||
break;
|
||||
|
||||
/* I/O error - skip this song */
|
||||
delete[] buffer.data;
|
||||
buffer.data = nullptr;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
p += ret;
|
||||
if (p == end) {
|
||||
if (!is_stream)
|
||||
break;
|
||||
|
||||
LogWarning(modplug_domain, "stream too large");
|
||||
delete[] buffer.data;
|
||||
buffer.data = nullptr;
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.size = p - buffer.data;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static ModPlugFile *
|
||||
LoadModPlugFile(Decoder *decoder, InputStream &is)
|
||||
{
|
||||
const auto buffer = mod_loadfile(decoder, is);
|
||||
if (buffer.IsNull()) {
|
||||
LogWarning(modplug_domain, "could not load stream");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModPlugFile *f = ModPlug_Load(buffer.data, buffer.size);
|
||||
delete[] buffer.data;
|
||||
return f;
|
||||
}
|
||||
|
||||
static void
|
||||
mod_decode(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
ModPlug_Settings settings;
|
||||
int ret;
|
||||
char audio_buffer[MODPLUG_FRAME_SIZE];
|
||||
|
||||
ModPlug_GetSettings(&settings);
|
||||
/* alter setting */
|
||||
settings.mResamplingMode = MODPLUG_RESAMPLE_FIR; /* RESAMP */
|
||||
settings.mChannels = 2;
|
||||
settings.mBits = 16;
|
||||
settings.mFrequency = 44100;
|
||||
settings.mLoopCount = modplug_loop_count;
|
||||
/* insert more setting changes here */
|
||||
ModPlug_SetSettings(&settings);
|
||||
|
||||
ModPlugFile *f = LoadModPlugFile(&decoder, is);
|
||||
if (f == nullptr) {
|
||||
LogWarning(modplug_domain, "could not decode stream");
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr AudioFormat audio_format(44100, SampleFormat::S16, 2);
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
decoder_initialized(decoder, audio_format,
|
||||
is.IsSeekable(),
|
||||
ModPlug_GetLength(f) / 1000.0);
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE);
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
cmd = decoder_data(decoder, nullptr,
|
||||
audio_buffer, ret,
|
||||
0);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
float where = decoder_seek_where(decoder);
|
||||
|
||||
ModPlug_Seek(f, (int)(where * 1000.0));
|
||||
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
ModPlug_Unload(f);
|
||||
}
|
||||
|
||||
static bool
|
||||
modplug_scan_stream(InputStream &is,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
ModPlugFile *f = LoadModPlugFile(nullptr, is);
|
||||
if (f == nullptr)
|
||||
return false;
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
ModPlug_GetLength(f) / 1000);
|
||||
|
||||
const char *title = ModPlug_GetName(f);
|
||||
if (title != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_TITLE, title);
|
||||
|
||||
ModPlug_Unload(f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const mod_suffixes[] = {
|
||||
"669", "amf", "ams", "dbm", "dfm", "dsm", "far", "it",
|
||||
"med", "mdl", "mod", "mtm", "mt2", "okt", "s3m", "stm",
|
||||
"ult", "umx", "xm",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin modplug_decoder_plugin = {
|
||||
"modplug",
|
||||
modplug_decoder_init,
|
||||
nullptr,
|
||||
mod_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
modplug_scan_stream,
|
||||
nullptr,
|
||||
mod_suffixes,
|
||||
nullptr,
|
||||
};
|
25
src/decoder/plugins/ModplugDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/ModplugDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_MODPLUG_HXX
|
||||
#define MPD_DECODER_MODPLUG_HXX
|
||||
|
||||
extern const struct DecoderPlugin modplug_decoder_plugin;
|
||||
|
||||
#endif
|
278
src/decoder/plugins/MpcdecDecoderPlugin.cxx
Normal file
278
src/decoder/plugins/MpcdecDecoderPlugin.cxx
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "MpcdecDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <mpc/mpcdec.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
struct mpc_decoder_data {
|
||||
InputStream &is;
|
||||
Decoder *decoder;
|
||||
|
||||
mpc_decoder_data(InputStream &_is, Decoder *_decoder)
|
||||
:is(_is), decoder(_decoder) {}
|
||||
};
|
||||
|
||||
static constexpr Domain mpcdec_domain("mpcdec");
|
||||
|
||||
static mpc_int32_t
|
||||
mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size)
|
||||
{
|
||||
struct mpc_decoder_data *data =
|
||||
(struct mpc_decoder_data *)reader->data;
|
||||
|
||||
return decoder_read(data->decoder, data->is, ptr, size);
|
||||
}
|
||||
|
||||
static mpc_bool_t
|
||||
mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset)
|
||||
{
|
||||
struct mpc_decoder_data *data =
|
||||
(struct mpc_decoder_data *)reader->data;
|
||||
|
||||
return data->is.LockSeek(offset, SEEK_SET, IgnoreError());
|
||||
}
|
||||
|
||||
static mpc_int32_t
|
||||
mpc_tell_cb(mpc_reader *reader)
|
||||
{
|
||||
struct mpc_decoder_data *data =
|
||||
(struct mpc_decoder_data *)reader->data;
|
||||
|
||||
return (long)data->is.GetOffset();
|
||||
}
|
||||
|
||||
static mpc_bool_t
|
||||
mpc_canseek_cb(mpc_reader *reader)
|
||||
{
|
||||
struct mpc_decoder_data *data =
|
||||
(struct mpc_decoder_data *)reader->data;
|
||||
|
||||
return data->is.IsSeekable();
|
||||
}
|
||||
|
||||
static mpc_int32_t
|
||||
mpc_getsize_cb(mpc_reader *reader)
|
||||
{
|
||||
struct mpc_decoder_data *data =
|
||||
(struct mpc_decoder_data *)reader->data;
|
||||
|
||||
return data->is.GetSize();
|
||||
}
|
||||
|
||||
/* this _looks_ performance-critical, don't de-inline -- eric */
|
||||
static inline int32_t
|
||||
mpc_to_mpd_sample(MPC_SAMPLE_FORMAT sample)
|
||||
{
|
||||
/* only doing 16-bit audio for now */
|
||||
int32_t val;
|
||||
|
||||
enum {
|
||||
bits = 24,
|
||||
};
|
||||
|
||||
const int clip_min = -1 << (bits - 1);
|
||||
const int clip_max = (1 << (bits - 1)) - 1;
|
||||
|
||||
#ifdef MPC_FIXED_POINT
|
||||
const int shift = bits - MPC_FIXED_POINT_SCALE_SHIFT;
|
||||
|
||||
if (shift < 0)
|
||||
val = sample >> -shift;
|
||||
else
|
||||
val = sample << shift;
|
||||
#else
|
||||
const int float_scale = 1 << (bits - 1);
|
||||
|
||||
val = sample * float_scale;
|
||||
#endif
|
||||
|
||||
if (val < clip_min)
|
||||
val = clip_min;
|
||||
else if (val > clip_max)
|
||||
val = clip_max;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void
|
||||
mpc_to_mpd_buffer(int32_t *dest, const MPC_SAMPLE_FORMAT *src,
|
||||
unsigned num_samples)
|
||||
{
|
||||
while (num_samples-- > 0)
|
||||
*dest++ = mpc_to_mpd_sample(*src++);
|
||||
}
|
||||
|
||||
static void
|
||||
mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
|
||||
{
|
||||
MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH];
|
||||
|
||||
mpc_decoder_data data(is, &mpd_decoder);
|
||||
|
||||
mpc_reader reader;
|
||||
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_demux *demux = mpc_demux_init(&reader);
|
||||
if (demux == nullptr) {
|
||||
if (decoder_get_command(mpd_decoder) != DecoderCommand::STOP)
|
||||
LogWarning(mpcdec_domain,
|
||||
"Not a valid musepack stream");
|
||||
return;
|
||||
}
|
||||
|
||||
mpc_streaminfo info;
|
||||
mpc_demux_get_info(demux, &info);
|
||||
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, info.sample_freq,
|
||||
SampleFormat::S24_P32,
|
||||
info.channels, error)) {
|
||||
LogError(error);
|
||||
mpc_demux_exit(demux);
|
||||
return;
|
||||
}
|
||||
|
||||
ReplayGainInfo rgi;
|
||||
rgi.Clear();
|
||||
rgi.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
|
||||
rgi.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767;
|
||||
rgi.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
|
||||
rgi.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767;
|
||||
|
||||
decoder_replay_gain(mpd_decoder, &rgi);
|
||||
|
||||
decoder_initialized(mpd_decoder, audio_format,
|
||||
is.IsSeekable(),
|
||||
mpc_streaminfo_get_length(&info));
|
||||
|
||||
DecoderCommand cmd = DecoderCommand::NONE;
|
||||
do {
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
mpc_int64_t where = decoder_seek_where(mpd_decoder) *
|
||||
audio_format.sample_rate;
|
||||
bool success;
|
||||
|
||||
success = mpc_demux_seek_sample(demux, where)
|
||||
== MPC_STATUS_OK;
|
||||
if (success)
|
||||
decoder_command_finished(mpd_decoder);
|
||||
else
|
||||
decoder_seek_error(mpd_decoder);
|
||||
}
|
||||
|
||||
mpc_uint32_t vbr_update_bits = 0;
|
||||
|
||||
mpc_frame_info frame;
|
||||
frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer;
|
||||
mpc_status status = mpc_demux_decode(demux, &frame);
|
||||
if (status != MPC_STATUS_OK) {
|
||||
LogWarning(mpcdec_domain,
|
||||
"Failed to decode sample");
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame.bits == -1)
|
||||
break;
|
||||
|
||||
mpc_uint32_t ret = frame.samples;
|
||||
ret *= info.channels;
|
||||
|
||||
int32_t chunk[ARRAY_SIZE(sample_buffer)];
|
||||
mpc_to_mpd_buffer(chunk, sample_buffer, ret);
|
||||
|
||||
long bit_rate = vbr_update_bits * audio_format.sample_rate
|
||||
/ 1152 / 1000;
|
||||
|
||||
cmd = decoder_data(mpd_decoder, is,
|
||||
chunk, ret * sizeof(chunk[0]),
|
||||
bit_rate);
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
mpc_demux_exit(demux);
|
||||
}
|
||||
|
||||
static float
|
||||
mpcdec_get_file_duration(InputStream &is)
|
||||
{
|
||||
mpc_decoder_data data(is, nullptr);
|
||||
|
||||
mpc_reader reader;
|
||||
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_demux *demux = mpc_demux_init(&reader);
|
||||
if (demux == nullptr)
|
||||
return -1;
|
||||
|
||||
mpc_streaminfo info;
|
||||
mpc_demux_get_info(demux, &info);
|
||||
mpc_demux_exit(demux);
|
||||
|
||||
return mpc_streaminfo_get_length(&info);
|
||||
}
|
||||
|
||||
static bool
|
||||
mpcdec_scan_stream(InputStream &is,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
float total_time = mpcdec_get_file_duration(is);
|
||||
|
||||
if (total_time < 0)
|
||||
return false;
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx, total_time);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const mpcdec_suffixes[] = { "mpc", nullptr };
|
||||
|
||||
const struct DecoderPlugin mpcdec_decoder_plugin = {
|
||||
"mpcdec",
|
||||
nullptr,
|
||||
nullptr,
|
||||
mpcdec_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
mpcdec_scan_stream,
|
||||
nullptr,
|
||||
mpcdec_suffixes,
|
||||
nullptr,
|
||||
};
|
25
src/decoder/plugins/MpcdecDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/MpcdecDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_MPCDEC_HXX
|
||||
#define MPD_DECODER_MPCDEC_HXX
|
||||
|
||||
extern const struct DecoderPlugin mpcdec_decoder_plugin;
|
||||
|
||||
#endif
|
255
src/decoder/plugins/Mpg123DecoderPlugin.cxx
Normal file
255
src/decoder/plugins/Mpg123DecoderPlugin.cxx
Normal file
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h" /* must be first for large file support */
|
||||
#include "Mpg123DecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <mpg123.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static constexpr Domain mpg123_domain("mpg123");
|
||||
|
||||
static bool
|
||||
mpd_mpg123_init(gcc_unused const config_param ¶m)
|
||||
{
|
||||
mpg123_init();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
mpd_mpg123_finish(void)
|
||||
{
|
||||
mpg123_exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file with an existing #mpg123_handle.
|
||||
*
|
||||
* @param handle a handle which was created before; on error, this
|
||||
* function will not free it
|
||||
* @param audio_format this parameter is filled after successful
|
||||
* return
|
||||
* @return true on success
|
||||
*/
|
||||
static bool
|
||||
mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
|
||||
AudioFormat &audio_format)
|
||||
{
|
||||
int error;
|
||||
int channels, encoding;
|
||||
long rate;
|
||||
|
||||
/* mpg123_open() wants a writable string :-( */
|
||||
char *const path2 = const_cast<char *>(path_fs);
|
||||
|
||||
error = mpg123_open(handle, path2);
|
||||
if (error != MPG123_OK) {
|
||||
FormatWarning(mpg123_domain,
|
||||
"libmpg123 failed to open %s: %s",
|
||||
path_fs, mpg123_plain_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* obtain the audio format */
|
||||
|
||||
error = mpg123_getformat(handle, &rate, &channels, &encoding);
|
||||
if (error != MPG123_OK) {
|
||||
FormatWarning(mpg123_domain,
|
||||
"mpg123_getformat() failed: %s",
|
||||
mpg123_plain_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (encoding != MPG123_ENC_SIGNED_16) {
|
||||
/* other formats not yet implemented */
|
||||
FormatWarning(mpg123_domain,
|
||||
"expected MPG123_ENC_SIGNED_16, got %d",
|
||||
encoding);
|
||||
return false;
|
||||
}
|
||||
|
||||
Error error2;
|
||||
if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16,
|
||||
channels, error2)) {
|
||||
LogError(error2);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs)
|
||||
{
|
||||
mpg123_handle *handle;
|
||||
int error;
|
||||
off_t num_samples;
|
||||
struct mpg123_frameinfo info;
|
||||
|
||||
/* open the file */
|
||||
|
||||
handle = mpg123_new(nullptr, &error);
|
||||
if (handle == nullptr) {
|
||||
FormatError(mpg123_domain,
|
||||
"mpg123_new() failed: %s",
|
||||
mpg123_plain_strerror(error));
|
||||
return;
|
||||
}
|
||||
|
||||
AudioFormat audio_format;
|
||||
if (!mpd_mpg123_open(handle, path_fs, audio_format)) {
|
||||
mpg123_delete(handle);
|
||||
return;
|
||||
}
|
||||
|
||||
num_samples = mpg123_length(handle);
|
||||
|
||||
/* tell MPD core we're ready */
|
||||
|
||||
decoder_initialized(decoder, audio_format, true,
|
||||
(float)num_samples /
|
||||
(float)audio_format.sample_rate);
|
||||
|
||||
if (mpg123_info(handle, &info) != MPG123_OK) {
|
||||
info.vbr = MPG123_CBR;
|
||||
info.bitrate = 0;
|
||||
}
|
||||
|
||||
switch (info.vbr) {
|
||||
case MPG123_ABR:
|
||||
info.bitrate = info.abr_rate;
|
||||
break;
|
||||
case MPG123_CBR:
|
||||
break;
|
||||
default:
|
||||
info.bitrate = 0;
|
||||
}
|
||||
|
||||
/* the decoder main loop */
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
unsigned char buffer[8192];
|
||||
size_t nbytes;
|
||||
|
||||
/* decode */
|
||||
|
||||
error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
|
||||
if (error != MPG123_OK) {
|
||||
if (error != MPG123_DONE)
|
||||
FormatWarning(mpg123_domain,
|
||||
"mpg123_read() failed: %s",
|
||||
mpg123_plain_strerror(error));
|
||||
break;
|
||||
}
|
||||
|
||||
/* update bitrate for ABR/VBR */
|
||||
if (info.vbr != MPG123_CBR) {
|
||||
/* FIXME: maybe skip, as too expensive? */
|
||||
/* FIXME: maybe, (info.vbr == MPG123_VBR) ? */
|
||||
if (mpg123_info (handle, &info) != MPG123_OK)
|
||||
info.bitrate = 0;
|
||||
}
|
||||
|
||||
/* send to MPD */
|
||||
|
||||
cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
off_t c = decoder_seek_where(decoder)*audio_format.sample_rate;
|
||||
c = mpg123_seek(handle, c, SEEK_SET);
|
||||
if (c < 0)
|
||||
decoder_seek_error(decoder);
|
||||
else {
|
||||
decoder_command_finished(decoder);
|
||||
decoder_timestamp(decoder, c/(double)audio_format.sample_rate);
|
||||
}
|
||||
|
||||
cmd = DecoderCommand::NONE;
|
||||
}
|
||||
} while (cmd == DecoderCommand::NONE);
|
||||
|
||||
/* cleanup */
|
||||
|
||||
mpg123_delete(handle);
|
||||
}
|
||||
|
||||
static bool
|
||||
mpd_mpg123_scan_file(const char *path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
mpg123_handle *handle;
|
||||
int error;
|
||||
off_t num_samples;
|
||||
|
||||
handle = mpg123_new(nullptr, &error);
|
||||
if (handle == nullptr) {
|
||||
FormatError(mpg123_domain,
|
||||
"mpg123_new() failed: %s",
|
||||
mpg123_plain_strerror(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioFormat audio_format;
|
||||
if (!mpd_mpg123_open(handle, path_fs, audio_format)) {
|
||||
mpg123_delete(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
num_samples = mpg123_length(handle);
|
||||
if (num_samples <= 0) {
|
||||
mpg123_delete(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ID3 tag support not yet implemented */
|
||||
|
||||
mpg123_delete(handle);
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
num_samples / audio_format.sample_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const mpg123_suffixes[] = {
|
||||
"mp3",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin mpg123_decoder_plugin = {
|
||||
"mpg123",
|
||||
mpd_mpg123_init,
|
||||
mpd_mpg123_finish,
|
||||
/* streaming not yet implemented */
|
||||
nullptr,
|
||||
mpd_mpg123_file_decode,
|
||||
mpd_mpg123_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
mpg123_suffixes,
|
||||
nullptr,
|
||||
};
|
25
src/decoder/plugins/Mpg123DecoderPlugin.hxx
Normal file
25
src/decoder/plugins/Mpg123DecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_MPG123_HXX
|
||||
#define MPD_DECODER_MPG123_HXX
|
||||
|
||||
extern const struct DecoderPlugin mpg123_decoder_plugin;
|
||||
|
||||
#endif
|
51
src/decoder/plugins/OggCodec.cxx
Normal file
51
src/decoder/plugins/OggCodec.cxx
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "OggCodec.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
enum ogg_codec
|
||||
ogg_codec_detect(Decoder *decoder, InputStream &is)
|
||||
{
|
||||
/* 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 = decoder_read(decoder, is, buf, sizeof(buf));
|
||||
if (r < sizeof(buf) || memcmp(buf, "OggS", 4) != 0)
|
||||
return OGG_CODEC_UNKNOWN;
|
||||
|
||||
if ((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 OGG_CODEC_FLAC;
|
||||
|
||||
if (memcmp(buf + 28, "Opus", 4) == 0)
|
||||
return OGG_CODEC_OPUS;
|
||||
|
||||
return OGG_CODEC_VORBIS;
|
||||
}
|
40
src/decoder/plugins/OggCodec.hxx
Normal file
40
src/decoder/plugins/OggCodec.hxx
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
|
||||
*/
|
||||
|
||||
#ifndef MPD_OGG_CODEC_HXX
|
||||
#define MPD_OGG_CODEC_HXX
|
||||
|
||||
struct Decoder;
|
||||
struct InputStream;
|
||||
|
||||
enum ogg_codec {
|
||||
OGG_CODEC_UNKNOWN,
|
||||
OGG_CODEC_VORBIS,
|
||||
OGG_CODEC_FLAC,
|
||||
OGG_CODEC_OPUS,
|
||||
};
|
||||
|
||||
enum ogg_codec
|
||||
ogg_codec_detect(Decoder *decoder, InputStream &is);
|
||||
|
||||
#endif /* _OGG_COMMON_H */
|
68
src/decoder/plugins/OggFind.cxx
Normal file
68
src/decoder/plugins/OggFind.cxx
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "OggFind.hxx"
|
||||
#include "OggSyncState.hxx"
|
||||
#include "util/Error.hxx"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
bool
|
||||
OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet)
|
||||
{
|
||||
while (true) {
|
||||
int r = ogg_stream_packetout(&os, &packet);
|
||||
if (r == 0) {
|
||||
if (!oy.ExpectPageIn(os))
|
||||
return false;
|
||||
|
||||
continue;
|
||||
} else if (r > 0 && packet.e_o_s)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
|
||||
InputStream::offset_type offset, int whence)
|
||||
{
|
||||
oy.Reset();
|
||||
|
||||
/* reset the stream to clear any previous partial packet
|
||||
data */
|
||||
ogg_stream_reset(&os);
|
||||
|
||||
return is.LockSeek(offset, whence, IgnoreError()) &&
|
||||
oy.ExpectPageSeekIn(os);
|
||||
}
|
||||
|
||||
bool
|
||||
OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
|
||||
InputStream &is)
|
||||
{
|
||||
if (is.size > 0 && is.size - is.offset < 65536)
|
||||
return OggFindEOS(oy, os, packet);
|
||||
|
||||
if (!is.CheapSeeking())
|
||||
return false;
|
||||
|
||||
return OggSeekPageAtOffset(oy, os, is, -65536, SEEK_END) &&
|
||||
OggFindEOS(oy, os, packet);
|
||||
}
|
56
src/decoder/plugins/OggFind.hxx
Normal file
56
src/decoder/plugins/OggFind.hxx
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_OGG_FIND_HXX
|
||||
#define MPD_OGG_FIND_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "InputStream.hxx"
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
class OggSyncState;
|
||||
|
||||
/**
|
||||
* Skip all pages/packets until an end-of-stream (EOS) packet for the
|
||||
* specified stream is found.
|
||||
*
|
||||
* @return true if the EOS packet was found
|
||||
*/
|
||||
bool
|
||||
OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet);
|
||||
|
||||
/**
|
||||
* Seek the #InputStream and find the next Ogg page.
|
||||
*/
|
||||
bool
|
||||
OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
|
||||
InputStream::offset_type offset, int whence);
|
||||
|
||||
/**
|
||||
* Try to find the end-of-stream (EOS) packet. Seek to the end of the
|
||||
* file if necessary.
|
||||
*
|
||||
* @return true if the EOS packet was found
|
||||
*/
|
||||
bool
|
||||
OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
|
||||
InputStream &is);
|
||||
|
||||
#endif
|
78
src/decoder/plugins/OggSyncState.hxx
Normal file
78
src/decoder/plugins/OggSyncState.hxx
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_OGG_SYNC_STATE_HXX
|
||||
#define MPD_OGG_SYNC_STATE_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "OggUtil.hxx"
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* Wrapper for an ogg_sync_state.
|
||||
*/
|
||||
class OggSyncState {
|
||||
ogg_sync_state oy;
|
||||
|
||||
InputStream &is;
|
||||
Decoder *const decoder;
|
||||
|
||||
public:
|
||||
OggSyncState(InputStream &_is, Decoder *const _decoder=nullptr)
|
||||
:is(_is), decoder(_decoder) {
|
||||
ogg_sync_init(&oy);
|
||||
}
|
||||
|
||||
~OggSyncState() {
|
||||
ogg_sync_clear(&oy);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
ogg_sync_reset(&oy);
|
||||
}
|
||||
|
||||
bool Feed(size_t size) {
|
||||
return OggFeed(oy, decoder, is, size);
|
||||
}
|
||||
|
||||
bool ExpectPage(ogg_page &page) {
|
||||
return OggExpectPage(oy, page, decoder, is);
|
||||
}
|
||||
|
||||
bool ExpectFirstPage(ogg_stream_state &os) {
|
||||
return OggExpectFirstPage(oy, os, decoder, is);
|
||||
}
|
||||
|
||||
bool ExpectPageIn(ogg_stream_state &os) {
|
||||
return OggExpectPageIn(oy, os, decoder, is);
|
||||
}
|
||||
|
||||
bool ExpectPageSeek(ogg_page &page) {
|
||||
return OggExpectPageSeek(oy, page, decoder, is);
|
||||
}
|
||||
|
||||
bool ExpectPageSeekIn(ogg_stream_state &os) {
|
||||
return OggExpectPageSeekIn(oy, os, decoder, is);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
118
src/decoder/plugins/OggUtil.cxx
Normal file
118
src/decoder/plugins/OggUtil.cxx
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "OggUtil.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
|
||||
bool
|
||||
OggFeed(ogg_sync_state &oy, Decoder *decoder,
|
||||
InputStream &input_stream, size_t size)
|
||||
{
|
||||
char *buffer = ogg_sync_buffer(&oy, size);
|
||||
if (buffer == nullptr)
|
||||
return false;
|
||||
|
||||
size_t nbytes = decoder_read(decoder, input_stream,
|
||||
buffer, size);
|
||||
if (nbytes == 0)
|
||||
return false;
|
||||
|
||||
ogg_sync_wrote(&oy, nbytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
OggExpectPage(ogg_sync_state &oy, ogg_page &page,
|
||||
Decoder *decoder, InputStream &input_stream)
|
||||
{
|
||||
while (true) {
|
||||
int r = ogg_sync_pageout(&oy, &page);
|
||||
if (r != 0)
|
||||
return r > 0;
|
||||
|
||||
if (!OggFeed(oy, decoder, input_stream, 1024))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os,
|
||||
Decoder *decoder, InputStream &is)
|
||||
{
|
||||
ogg_page page;
|
||||
if (!OggExpectPage(oy, page, decoder, is))
|
||||
return false;
|
||||
|
||||
ogg_stream_init(&os, ogg_page_serialno(&page));
|
||||
ogg_stream_pagein(&os, &page);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os,
|
||||
Decoder *decoder, InputStream &is)
|
||||
{
|
||||
ogg_page page;
|
||||
if (!OggExpectPage(oy, page, decoder, is))
|
||||
return false;
|
||||
|
||||
ogg_stream_pagein(&os, &page);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
|
||||
Decoder *decoder, InputStream &input_stream)
|
||||
{
|
||||
size_t remaining_skipped = 16384;
|
||||
|
||||
while (true) {
|
||||
int r = ogg_sync_pageseek(&oy, &page);
|
||||
if (r > 0)
|
||||
return true;
|
||||
|
||||
if (r < 0) {
|
||||
/* skipped -r bytes */
|
||||
size_t nbytes = -r;
|
||||
if (nbytes > remaining_skipped)
|
||||
/* still no ogg page - we lost our
|
||||
patience, abort */
|
||||
return false;
|
||||
|
||||
remaining_skipped -= nbytes;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!OggFeed(oy, decoder, input_stream, 1024))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os,
|
||||
Decoder *decoder, InputStream &is)
|
||||
{
|
||||
ogg_page page;
|
||||
if (!OggExpectPageSeek(oy, page, decoder, is))
|
||||
return false;
|
||||
|
||||
ogg_stream_pagein(&os, &page);
|
||||
return true;
|
||||
}
|
87
src/decoder/plugins/OggUtil.hxx
Normal file
87
src/decoder/plugins/OggUtil.hxx
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_OGG_UTIL_HXX
|
||||
#define MPD_OGG_UTIL_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct InputStream;
|
||||
struct Decoder;
|
||||
|
||||
/**
|
||||
* Feed data from the #InputStream into the #ogg_sync_state.
|
||||
*
|
||||
* @return false on error or end-of-file
|
||||
*/
|
||||
bool
|
||||
OggFeed(ogg_sync_state &oy, Decoder *decoder, InputStream &is,
|
||||
size_t size);
|
||||
|
||||
/**
|
||||
* Feed into the #ogg_sync_state until a page gets available. Garbage
|
||||
* data at the beginning is considered a fatal error.
|
||||
*
|
||||
* @return true if a page is available
|
||||
*/
|
||||
bool
|
||||
OggExpectPage(ogg_sync_state &oy, ogg_page &page,
|
||||
Decoder *decoder, InputStream &is);
|
||||
|
||||
/**
|
||||
* Combines OggExpectPage(), ogg_stream_init() and
|
||||
* ogg_stream_pagein().
|
||||
*
|
||||
* @return true if the stream was initialized and the first page was
|
||||
* delivered to it
|
||||
*/
|
||||
bool
|
||||
OggExpectFirstPage(ogg_sync_state &oy, ogg_stream_state &os,
|
||||
Decoder *decoder, InputStream &is);
|
||||
|
||||
/**
|
||||
* Combines OggExpectPage() and ogg_stream_pagein().
|
||||
*
|
||||
* @return true if a page was delivered to the stream
|
||||
*/
|
||||
bool
|
||||
OggExpectPageIn(ogg_sync_state &oy, ogg_stream_state &os,
|
||||
Decoder *decoder, InputStream &is);
|
||||
|
||||
/**
|
||||
* Like OggExpectPage(), but allow skipping garbage (after seeking).
|
||||
*/
|
||||
bool
|
||||
OggExpectPageSeek(ogg_sync_state &oy, ogg_page &page,
|
||||
Decoder *decoder, InputStream &is);
|
||||
|
||||
/**
|
||||
* Combines OggExpectPageSeek() and ogg_stream_pagein().
|
||||
*
|
||||
* @return true if a page was delivered to the stream
|
||||
*/
|
||||
bool
|
||||
OggExpectPageSeekIn(ogg_sync_state &oy, ogg_stream_state &os,
|
||||
Decoder *decoder, InputStream &is);
|
||||
|
||||
#endif
|
485
src/decoder/plugins/OpusDecoderPlugin.cxx
Normal file
485
src/decoder/plugins/OpusDecoderPlugin.cxx
Normal file
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h" /* must be first for large file support */
|
||||
#include "OpusDecoderPlugin.h"
|
||||
#include "OpusDomain.hxx"
|
||||
#include "OpusHead.hxx"
|
||||
#include "OpusTags.hxx"
|
||||
#include "OggFind.hxx"
|
||||
#include "OggSyncState.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "OggCodec.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "tag/TagBuilder.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <opus.h>
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static constexpr opus_int32 opus_sample_rate = 48000;
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
IsOpusHead(const ogg_packet &packet)
|
||||
{
|
||||
return packet.bytes >= 8 && memcmp(packet.packet, "OpusHead", 8) == 0;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
IsOpusTags(const ogg_packet &packet)
|
||||
{
|
||||
return packet.bytes >= 8 && memcmp(packet.packet, "OpusTags", 8) == 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
mpd_opus_init(gcc_unused const config_param ¶m)
|
||||
{
|
||||
LogDebug(opus_domain, opus_get_version_string());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class MPDOpusDecoder {
|
||||
Decoder &decoder;
|
||||
InputStream &input_stream;
|
||||
|
||||
ogg_stream_state os;
|
||||
|
||||
OpusDecoder *opus_decoder;
|
||||
opus_int16 *output_buffer;
|
||||
unsigned output_size;
|
||||
|
||||
bool os_initialized;
|
||||
bool found_opus;
|
||||
|
||||
int opus_serialno;
|
||||
|
||||
ogg_int64_t eos_granulepos;
|
||||
|
||||
size_t frame_size;
|
||||
|
||||
public:
|
||||
MPDOpusDecoder(Decoder &_decoder,
|
||||
InputStream &_input_stream)
|
||||
:decoder(_decoder), input_stream(_input_stream),
|
||||
opus_decoder(nullptr),
|
||||
output_buffer(nullptr), output_size(0),
|
||||
os_initialized(false), found_opus(false) {}
|
||||
~MPDOpusDecoder();
|
||||
|
||||
bool ReadFirstPage(OggSyncState &oy);
|
||||
bool ReadNextPage(OggSyncState &oy);
|
||||
|
||||
DecoderCommand HandlePackets();
|
||||
DecoderCommand HandlePacket(const ogg_packet &packet);
|
||||
DecoderCommand HandleBOS(const ogg_packet &packet);
|
||||
DecoderCommand HandleTags(const ogg_packet &packet);
|
||||
DecoderCommand HandleAudio(const ogg_packet &packet);
|
||||
|
||||
bool Seek(OggSyncState &oy, double where);
|
||||
};
|
||||
|
||||
MPDOpusDecoder::~MPDOpusDecoder()
|
||||
{
|
||||
g_free(output_buffer);
|
||||
|
||||
if (opus_decoder != nullptr)
|
||||
opus_decoder_destroy(opus_decoder);
|
||||
|
||||
if (os_initialized)
|
||||
ogg_stream_clear(&os);
|
||||
}
|
||||
|
||||
inline bool
|
||||
MPDOpusDecoder::ReadFirstPage(OggSyncState &oy)
|
||||
{
|
||||
assert(!os_initialized);
|
||||
|
||||
if (!oy.ExpectFirstPage(os))
|
||||
return false;
|
||||
|
||||
os_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool
|
||||
MPDOpusDecoder::ReadNextPage(OggSyncState &oy)
|
||||
{
|
||||
assert(os_initialized);
|
||||
|
||||
ogg_page page;
|
||||
if (!oy.ExpectPage(page))
|
||||
return false;
|
||||
|
||||
const auto page_serialno = ogg_page_serialno(&page);
|
||||
if (page_serialno != os.serialno)
|
||||
ogg_stream_reset_serialno(&os, page_serialno);
|
||||
|
||||
ogg_stream_pagein(&os, &page);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MPDOpusDecoder::HandlePackets()
|
||||
{
|
||||
ogg_packet packet;
|
||||
while (ogg_stream_packetout(&os, &packet) == 1) {
|
||||
auto cmd = HandlePacket(packet);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
return cmd;
|
||||
}
|
||||
|
||||
return DecoderCommand::NONE;
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
|
||||
{
|
||||
if (packet.e_o_s)
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
if (packet.b_o_s)
|
||||
return HandleBOS(packet);
|
||||
else if (!found_opus)
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
if (IsOpusTags(packet))
|
||||
return HandleTags(packet);
|
||||
|
||||
return HandleAudio(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the end-of-stream packet and restore the previous file
|
||||
* position.
|
||||
*/
|
||||
static bool
|
||||
LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
|
||||
ogg_packet &packet)
|
||||
{
|
||||
if (!is.CheapSeeking())
|
||||
/* we do this for local files only, because seeking
|
||||
around remote files is expensive and not worth the
|
||||
troubl */
|
||||
return -1;
|
||||
|
||||
const auto old_offset = is.offset;
|
||||
if (old_offset < 0)
|
||||
return -1;
|
||||
|
||||
/* create temporary Ogg objects for seeking and parsing the
|
||||
EOS packet */
|
||||
OggSyncState oy(is, decoder);
|
||||
ogg_stream_state os;
|
||||
ogg_stream_init(&os, serialno);
|
||||
|
||||
bool result = OggSeekFindEOS(oy, os, packet, is);
|
||||
ogg_stream_clear(&os);
|
||||
|
||||
/* restore the previous file position */
|
||||
is.Seek(old_offset, SEEK_SET, IgnoreError());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the end-of-stream granulepos and restore the previous file
|
||||
* position.
|
||||
*
|
||||
* @return -1 on error
|
||||
*/
|
||||
gcc_pure
|
||||
static ogg_int64_t
|
||||
LoadEOSGranulePos(InputStream &is, Decoder *decoder, int serialno)
|
||||
{
|
||||
ogg_packet packet;
|
||||
if (!LoadEOSPacket(is, decoder, serialno, packet))
|
||||
return -1;
|
||||
|
||||
return packet.granulepos;
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
|
||||
{
|
||||
assert(packet.b_o_s);
|
||||
|
||||
if (found_opus || !IsOpusHead(packet))
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
unsigned channels;
|
||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
|
||||
!audio_valid_channel_count(channels))
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
assert(opus_decoder == nullptr);
|
||||
assert(output_buffer == nullptr);
|
||||
|
||||
opus_serialno = os.serialno;
|
||||
found_opus = true;
|
||||
|
||||
/* TODO: parse attributes from the OpusHead (sample rate,
|
||||
channels, ...) */
|
||||
|
||||
int opus_error;
|
||||
opus_decoder = opus_decoder_create(opus_sample_rate, channels,
|
||||
&opus_error);
|
||||
if (opus_decoder == nullptr) {
|
||||
FormatError(opus_domain, "libopus error: %s",
|
||||
opus_strerror(opus_error));
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
|
||||
opus_serialno);
|
||||
const double duration = eos_granulepos >= 0
|
||||
? double(eos_granulepos) / opus_sample_rate
|
||||
: -1.0;
|
||||
|
||||
const AudioFormat audio_format(opus_sample_rate,
|
||||
SampleFormat::S16, channels);
|
||||
decoder_initialized(decoder, audio_format,
|
||||
eos_granulepos > 0, duration);
|
||||
frame_size = audio_format.GetFrameSize();
|
||||
|
||||
/* allocate an output buffer for 16 bit PCM samples big enough
|
||||
to hold a quarter second, larger than 120ms required by
|
||||
libopus */
|
||||
output_size = audio_format.sample_rate / 4;
|
||||
output_buffer = (opus_int16 *)
|
||||
g_malloc(sizeof(*output_buffer) * output_size *
|
||||
audio_format.channels);
|
||||
|
||||
return decoder_get_command(decoder);
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
|
||||
{
|
||||
ReplayGainInfo rgi;
|
||||
rgi.Clear();
|
||||
|
||||
TagBuilder tag_builder;
|
||||
|
||||
DecoderCommand cmd;
|
||||
if (ScanOpusTags(packet.packet, packet.bytes,
|
||||
&rgi,
|
||||
&add_tag_handler, &tag_builder) &&
|
||||
!tag_builder.IsEmpty()) {
|
||||
decoder_replay_gain(decoder, &rgi);
|
||||
|
||||
Tag tag = tag_builder.Commit();
|
||||
cmd = decoder_tag(decoder, input_stream, std::move(tag));
|
||||
} else
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
inline DecoderCommand
|
||||
MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
|
||||
{
|
||||
assert(opus_decoder != nullptr);
|
||||
|
||||
int nframes = opus_decode(opus_decoder,
|
||||
(const unsigned char*)packet.packet,
|
||||
packet.bytes,
|
||||
output_buffer, output_size,
|
||||
0);
|
||||
if (nframes < 0) {
|
||||
LogError(opus_domain, opus_strerror(nframes));
|
||||
return DecoderCommand::STOP;
|
||||
}
|
||||
|
||||
if (nframes > 0) {
|
||||
const size_t nbytes = nframes * frame_size;
|
||||
auto cmd = decoder_data(decoder, input_stream,
|
||||
output_buffer, nbytes,
|
||||
0);
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
return cmd;
|
||||
|
||||
if (packet.granulepos > 0)
|
||||
decoder_timestamp(decoder,
|
||||
double(packet.granulepos)
|
||||
/ opus_sample_rate);
|
||||
}
|
||||
|
||||
return DecoderCommand::NONE;
|
||||
}
|
||||
|
||||
bool
|
||||
MPDOpusDecoder::Seek(OggSyncState &oy, double where_s)
|
||||
{
|
||||
assert(eos_granulepos > 0);
|
||||
assert(input_stream.seekable);
|
||||
assert(input_stream.size > 0);
|
||||
assert(input_stream.offset >= 0);
|
||||
|
||||
const ogg_int64_t where_granulepos(where_s * opus_sample_rate);
|
||||
|
||||
/* interpolate the file offset where we expect to find the
|
||||
given granule position */
|
||||
/* TODO: implement binary search */
|
||||
InputStream::offset_type offset(where_granulepos * input_stream.size
|
||||
/ eos_granulepos);
|
||||
|
||||
if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET))
|
||||
return false;
|
||||
|
||||
decoder_timestamp(decoder, where_s);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
mpd_opus_stream_decode(Decoder &decoder,
|
||||
InputStream &input_stream)
|
||||
{
|
||||
if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_OPUS)
|
||||
return;
|
||||
|
||||
/* rewind the stream, because ogg_codec_detect() has
|
||||
moved it */
|
||||
input_stream.LockRewind(IgnoreError());
|
||||
|
||||
MPDOpusDecoder d(decoder, input_stream);
|
||||
OggSyncState oy(input_stream, &decoder);
|
||||
|
||||
if (!d.ReadFirstPage(oy))
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
auto cmd = d.HandlePackets();
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
if (d.Seek(oy, decoder_seek_where(decoder)))
|
||||
decoder_command_finished(decoder);
|
||||
else
|
||||
decoder_seek_error(decoder);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
break;
|
||||
|
||||
if (!d.ReadNextPage(oy))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
mpd_opus_scan_stream(InputStream &is,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
OggSyncState oy(is);
|
||||
|
||||
ogg_stream_state os;
|
||||
if (!oy.ExpectFirstPage(os))
|
||||
return false;
|
||||
|
||||
/* read at most two more pages */
|
||||
unsigned remaining_pages = 2;
|
||||
|
||||
bool result = false;
|
||||
|
||||
ogg_packet packet;
|
||||
while (true) {
|
||||
int r = ogg_stream_packetout(&os, &packet);
|
||||
if (r < 0) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (r == 0) {
|
||||
if (remaining_pages-- == 0)
|
||||
break;
|
||||
|
||||
if (!oy.ExpectPageIn(os)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packet.b_o_s) {
|
||||
if (!IsOpusHead(packet))
|
||||
break;
|
||||
|
||||
unsigned channels;
|
||||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
|
||||
!audio_valid_channel_count(channels)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
} else if (!result)
|
||||
break;
|
||||
else if (IsOpusTags(packet)) {
|
||||
if (!ScanOpusTags(packet.packet, packet.bytes,
|
||||
nullptr,
|
||||
handler, handler_ctx))
|
||||
result = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is))
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
packet.granulepos / opus_sample_rate);
|
||||
|
||||
ogg_stream_clear(&os);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *const opus_suffixes[] = {
|
||||
"opus",
|
||||
"ogg",
|
||||
"oga",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static const char *const opus_mime_types[] = {
|
||||
"audio/opus",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin opus_decoder_plugin = {
|
||||
"opus",
|
||||
mpd_opus_init,
|
||||
nullptr,
|
||||
mpd_opus_stream_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
mpd_opus_scan_stream,
|
||||
nullptr,
|
||||
opus_suffixes,
|
||||
opus_mime_types,
|
||||
};
|
25
src/decoder/plugins/OpusDecoderPlugin.h
Normal file
25
src/decoder/plugins/OpusDecoderPlugin.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_OPUS_H
|
||||
#define MPD_DECODER_OPUS_H
|
||||
|
||||
extern const struct DecoderPlugin opus_decoder_plugin;
|
||||
|
||||
#endif
|
24
src/decoder/plugins/OpusDomain.cxx
Normal file
24
src/decoder/plugins/OpusDomain.cxx
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "OpusDomain.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
const Domain opus_domain("opus");
|
27
src/decoder/plugins/OpusDomain.hxx
Normal file
27
src/decoder/plugins/OpusDomain.hxx
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_OPUS_DOMAIN_HXX
|
||||
#define MPD_OPUS_DOMAIN_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
extern const class Domain opus_domain;
|
||||
|
||||
#endif
|
43
src/decoder/plugins/OpusHead.cxx
Normal file
43
src/decoder/plugins/OpusHead.cxx
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "OpusHead.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct OpusHead {
|
||||
char signature[8];
|
||||
uint8_t version, channels;
|
||||
uint16_t pre_skip;
|
||||
uint32_t sample_rate;
|
||||
uint16_t output_gain;
|
||||
uint8_t channel_mapping;
|
||||
};
|
||||
|
||||
bool
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r)
|
||||
{
|
||||
const OpusHead *h = (const OpusHead *)data;
|
||||
if (size < 19 || (h->version & 0xf0) != 0)
|
||||
return false;
|
||||
|
||||
channels_r = h->channels;
|
||||
return true;
|
||||
}
|
30
src/decoder/plugins/OpusHead.hxx
Normal file
30
src/decoder/plugins/OpusHead.hxx
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_OPUS_HEAD_HXX
|
||||
#define MPD_OPUS_HEAD_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
bool
|
||||
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r);
|
||||
|
||||
#endif
|
101
src/decoder/plugins/OpusReader.hxx
Normal file
101
src/decoder/plugins/OpusReader.hxx
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_OPUS_READER_HXX
|
||||
#define MPD_OPUS_READER_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
class OpusReader {
|
||||
const uint8_t *p, *const end;
|
||||
|
||||
public:
|
||||
OpusReader(const void *_p, size_t size)
|
||||
:p((const uint8_t *)_p), end(p + size) {}
|
||||
|
||||
bool Skip(size_t length) {
|
||||
p += length;
|
||||
return p <= end;
|
||||
}
|
||||
|
||||
const void *Read(size_t length) {
|
||||
const uint8_t *result = p;
|
||||
return Skip(length)
|
||||
? result
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool Expect(const void *value, size_t length) {
|
||||
const void *data = Read(length);
|
||||
return data != nullptr && memcmp(value, data, length) == 0;
|
||||
}
|
||||
|
||||
bool ReadByte(uint8_t &value_r) {
|
||||
if (p >= end)
|
||||
return false;
|
||||
|
||||
value_r = *p++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadShort(uint16_t &value_r) {
|
||||
const uint8_t *value = (const uint8_t *)Read(sizeof(value_r));
|
||||
if (value == nullptr)
|
||||
return false;
|
||||
|
||||
value_r = value[0] | (value[1] << 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadWord(uint32_t &value_r) {
|
||||
const uint8_t *value = (const uint8_t *)Read(sizeof(value_r));
|
||||
if (value == nullptr)
|
||||
return false;
|
||||
|
||||
value_r = value[0] | (value[1] << 8)
|
||||
| (value[2] << 16) | (value[3] << 24);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipString() {
|
||||
uint32_t length;
|
||||
return ReadWord(length) && Skip(length);
|
||||
}
|
||||
|
||||
char *ReadString() {
|
||||
uint32_t length;
|
||||
if (!ReadWord(length))
|
||||
return nullptr;
|
||||
|
||||
const char *src = (const char *)Read(length);
|
||||
if (src == nullptr)
|
||||
return nullptr;
|
||||
|
||||
char *dest = new char[length + 1];
|
||||
*std::copy_n(src, length, dest) = 0;
|
||||
return dest;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
102
src/decoder/plugins/OpusTags.cxx
Normal file
102
src/decoder/plugins/OpusTags.cxx
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "OpusTags.hxx"
|
||||
#include "OpusReader.hxx"
|
||||
#include "XiphTags.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "ReplayGainInfo.hxx"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
gcc_pure
|
||||
static TagType
|
||||
ParseOpusTagName(const char *name)
|
||||
{
|
||||
TagType type = tag_name_parse_i(name);
|
||||
if (type != TAG_NUM_OF_ITEM_TYPES)
|
||||
return type;
|
||||
|
||||
return tag_table_lookup_i(xiph_tags, name);
|
||||
}
|
||||
|
||||
static void
|
||||
ScanOneOpusTag(const char *name, const char *value,
|
||||
ReplayGainInfo *rgi,
|
||||
const struct tag_handler *handler, void *ctx)
|
||||
{
|
||||
if (rgi != nullptr && strcmp(name, "R128_TRACK_GAIN") == 0) {
|
||||
/* R128_TRACK_GAIN is a Q7.8 fixed point number in
|
||||
dB */
|
||||
|
||||
char *endptr;
|
||||
long l = strtol(value, &endptr, 10);
|
||||
if (endptr > value && *endptr == 0)
|
||||
rgi->tuples[REPLAY_GAIN_TRACK].gain = double(l) / 256.;
|
||||
}
|
||||
|
||||
tag_handler_invoke_pair(handler, ctx, name, value);
|
||||
|
||||
if (handler->tag != nullptr) {
|
||||
TagType t = ParseOpusTagName(name);
|
||||
if (t != TAG_NUM_OF_ITEM_TYPES)
|
||||
tag_handler_invoke_tag(handler, ctx, t, value);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ScanOpusTags(const void *data, size_t size,
|
||||
ReplayGainInfo *rgi,
|
||||
const struct tag_handler *handler, void *ctx)
|
||||
{
|
||||
OpusReader r(data, size);
|
||||
if (!r.Expect("OpusTags", 8))
|
||||
return false;
|
||||
|
||||
if (handler->pair == nullptr && handler->tag == nullptr)
|
||||
return true;
|
||||
|
||||
if (!r.SkipString())
|
||||
return false;
|
||||
|
||||
uint32_t n;
|
||||
if (!r.ReadWord(n))
|
||||
return false;
|
||||
|
||||
while (n-- > 0) {
|
||||
char *p = r.ReadString();
|
||||
if (p == nullptr)
|
||||
return false;
|
||||
|
||||
char *eq = strchr(p, '=');
|
||||
if (eq != nullptr && eq > p) {
|
||||
*eq = 0;
|
||||
|
||||
ScanOneOpusTag(p, eq + 1, rgi, handler, ctx);
|
||||
}
|
||||
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
34
src/decoder/plugins/OpusTags.hxx
Normal file
34
src/decoder/plugins/OpusTags.hxx
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_OPUS_TAGS_HXX
|
||||
#define MPD_OPUS_TAGS_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
struct ReplayGainInfo;
|
||||
|
||||
bool
|
||||
ScanOpusTags(const void *data, size_t size,
|
||||
ReplayGainInfo *rgi,
|
||||
const struct tag_handler *handler, void *ctx);
|
||||
|
||||
#endif
|
112
src/decoder/plugins/PcmDecoderPlugin.cxx
Normal file
112
src/decoder/plugins/PcmDecoderPlugin.cxx
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "PcmDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/ByteReverse.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h> /* for SEEK_SET */
|
||||
|
||||
static void
|
||||
pcm_stream_decode(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
static constexpr AudioFormat audio_format = {
|
||||
44100,
|
||||
SampleFormat::S16,
|
||||
2,
|
||||
};
|
||||
|
||||
const char *const mime = is.GetMimeType();
|
||||
const bool reverse_endian = mime != nullptr &&
|
||||
strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0;
|
||||
|
||||
const double time_to_size = audio_format.GetTimeToSize();
|
||||
|
||||
float total_time = -1;
|
||||
const auto size = is.GetSize();
|
||||
if (size >= 0)
|
||||
total_time = size / time_to_size;
|
||||
|
||||
decoder_initialized(decoder, audio_format,
|
||||
is.IsSeekable(), total_time);
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
char buffer[4096];
|
||||
|
||||
size_t nbytes = decoder_read(decoder, is,
|
||||
buffer, sizeof(buffer));
|
||||
|
||||
if (nbytes == 0 && is.LockIsEOF())
|
||||
break;
|
||||
|
||||
if (reverse_endian)
|
||||
/* make sure we deliver samples in host byte order */
|
||||
reverse_bytes_16((uint16_t *)buffer,
|
||||
(uint16_t *)buffer,
|
||||
(uint16_t *)(buffer + nbytes));
|
||||
|
||||
cmd = nbytes > 0
|
||||
? decoder_data(decoder, is,
|
||||
buffer, nbytes, 0)
|
||||
: decoder_get_command(decoder);
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
InputStream::offset_type offset(time_to_size *
|
||||
decoder_seek_where(decoder));
|
||||
|
||||
Error error;
|
||||
if (is.LockSeek(offset, SEEK_SET, error)) {
|
||||
decoder_command_finished(decoder);
|
||||
} else {
|
||||
LogError(error);
|
||||
decoder_seek_error(decoder);
|
||||
}
|
||||
|
||||
cmd = DecoderCommand::NONE;
|
||||
}
|
||||
} while (cmd == DecoderCommand::NONE);
|
||||
}
|
||||
|
||||
static const char *const pcm_mime_types[] = {
|
||||
/* for streams obtained by the cdio_paranoia input plugin */
|
||||
"audio/x-mpd-cdda-pcm",
|
||||
|
||||
/* same as above, but with reverse byte order */
|
||||
"audio/x-mpd-cdda-pcm-reverse",
|
||||
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin pcm_decoder_plugin = {
|
||||
"pcm",
|
||||
nullptr,
|
||||
nullptr,
|
||||
pcm_stream_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
pcm_mime_types,
|
||||
};
|
33
src/decoder/plugins/PcmDecoderPlugin.hxx
Normal file
33
src/decoder/plugins/PcmDecoderPlugin.hxx
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Not really a decoder; this plugin forwards its input data "as-is".
|
||||
*
|
||||
* It was written only to support the "cdio_paranoia" input plugin,
|
||||
* which does not need a decoder.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_PCM_HXX
|
||||
#define MPD_DECODER_PCM_HXX
|
||||
|
||||
extern const struct DecoderPlugin pcm_decoder_plugin;
|
||||
|
||||
#endif
|
436
src/decoder/plugins/SidplayDecoderPlugin.cxx
Normal file
436
src/decoder/plugins/SidplayDecoderPlugin.cxx
Normal file
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "SidplayDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Alloc.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "system/ByteOrder.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
|
||||
#include <sidplay/sidplay2.h>
|
||||
#include <sidplay/builders/resid.h>
|
||||
#include <sidplay/utils/SidTuneMod.h>
|
||||
|
||||
#define SUBTUNE_PREFIX "tune_"
|
||||
|
||||
static constexpr Domain sidplay_domain("sidplay");
|
||||
|
||||
static GPatternSpec *path_with_subtune;
|
||||
static const char *songlength_file;
|
||||
static GKeyFile *songlength_database;
|
||||
|
||||
static bool all_files_are_containers;
|
||||
static unsigned default_songlength;
|
||||
|
||||
static bool filter_setting;
|
||||
|
||||
static GKeyFile *
|
||||
sidplay_load_songlength_db(const char *path)
|
||||
{
|
||||
GError *error = nullptr;
|
||||
gchar *data;
|
||||
gsize size;
|
||||
|
||||
if (!g_file_get_contents(path, &data, &size, &error)) {
|
||||
FormatError(sidplay_domain,
|
||||
"unable to read songlengths file %s: %s",
|
||||
path, error->message);
|
||||
g_error_free(error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* replace any ; comment characters with # */
|
||||
for (gsize i = 0; i < size; i++)
|
||||
if (data[i] == ';')
|
||||
data[i] = '#';
|
||||
|
||||
GKeyFile *db = g_key_file_new();
|
||||
bool success = g_key_file_load_from_data(db, data, size,
|
||||
G_KEY_FILE_NONE, &error);
|
||||
g_free(data);
|
||||
if (!success) {
|
||||
FormatError(sidplay_domain,
|
||||
"unable to parse songlengths file %s: %s",
|
||||
path, error->message);
|
||||
g_error_free(error);
|
||||
g_key_file_free(db);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
g_key_file_set_list_separator(db, ' ');
|
||||
return db;
|
||||
}
|
||||
|
||||
static bool
|
||||
sidplay_init(const config_param ¶m)
|
||||
{
|
||||
/* read the songlengths database file */
|
||||
songlength_file = param.GetBlockValue("songlength_database");
|
||||
if (songlength_file != nullptr)
|
||||
songlength_database = sidplay_load_songlength_db(songlength_file);
|
||||
|
||||
default_songlength = param.GetBlockValue("default_songlength", 0u);
|
||||
|
||||
all_files_are_containers =
|
||||
param.GetBlockValue("all_files_are_containers", true);
|
||||
|
||||
path_with_subtune=g_pattern_spec_new(
|
||||
"*/" SUBTUNE_PREFIX "???.sid");
|
||||
|
||||
filter_setting = param.GetBlockValue("filter", true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
sidplay_finish()
|
||||
{
|
||||
g_pattern_spec_free(path_with_subtune);
|
||||
|
||||
if(songlength_database)
|
||||
g_key_file_free(songlength_database);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the file path stripped of any /tune_xxx.sid subtune
|
||||
* suffix
|
||||
*/
|
||||
static char *
|
||||
get_container_name(const char *path_fs)
|
||||
{
|
||||
char *path_container = strdup(path_fs);
|
||||
|
||||
if(!g_pattern_match(path_with_subtune,
|
||||
strlen(path_container), path_container, nullptr))
|
||||
return path_container;
|
||||
|
||||
char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
|
||||
if(ptr) *ptr='\0';
|
||||
|
||||
return path_container;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns tune number from file.sid/tune_xxx.sid style path or 1 if
|
||||
* no subtune is appended
|
||||
*/
|
||||
static unsigned
|
||||
get_song_num(const char *path_fs)
|
||||
{
|
||||
if(g_pattern_match(path_with_subtune,
|
||||
strlen(path_fs), path_fs, nullptr)) {
|
||||
char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
|
||||
if(!sub) return 1;
|
||||
|
||||
sub+=strlen("/" SUBTUNE_PREFIX);
|
||||
int song_num=strtol(sub, nullptr, 10);
|
||||
|
||||
if (errno == EINVAL)
|
||||
return 1;
|
||||
else
|
||||
return song_num;
|
||||
} else
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* get the song length in seconds */
|
||||
static int
|
||||
get_song_length(const char *path_fs)
|
||||
{
|
||||
if (songlength_database == nullptr)
|
||||
return -1;
|
||||
|
||||
char *sid_file = get_container_name(path_fs);
|
||||
SidTuneMod tune(sid_file);
|
||||
free(sid_file);
|
||||
if(!tune) {
|
||||
LogWarning(sidplay_domain,
|
||||
"failed to load file for calculating md5 sum");
|
||||
return -1;
|
||||
}
|
||||
char md5sum[SIDTUNE_MD5_LENGTH+1];
|
||||
tune.createMD5(md5sum);
|
||||
|
||||
const unsigned song_num = get_song_num(path_fs);
|
||||
|
||||
gsize num_items;
|
||||
gchar **values=g_key_file_get_string_list(songlength_database,
|
||||
"Database", md5sum, &num_items, nullptr);
|
||||
if(!values || song_num>num_items) {
|
||||
g_strfreev(values);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int minutes=strtol(values[song_num-1], nullptr, 10);
|
||||
if(errno==EINVAL) minutes=0;
|
||||
|
||||
int seconds;
|
||||
char *ptr=strchr(values[song_num-1], ':');
|
||||
if(ptr) {
|
||||
seconds=strtol(ptr+1, nullptr, 10);
|
||||
if(errno==EINVAL) seconds=0;
|
||||
} else
|
||||
seconds=0;
|
||||
|
||||
g_strfreev(values);
|
||||
|
||||
return (minutes*60)+seconds;
|
||||
}
|
||||
|
||||
static void
|
||||
sidplay_file_decode(Decoder &decoder, const char *path_fs)
|
||||
{
|
||||
int channels;
|
||||
|
||||
/* load the tune */
|
||||
|
||||
char *path_container=get_container_name(path_fs);
|
||||
SidTune tune(path_container, nullptr, true);
|
||||
free(path_container);
|
||||
if (!tune) {
|
||||
LogWarning(sidplay_domain, "failed to load file");
|
||||
return;
|
||||
}
|
||||
|
||||
int song_num=get_song_num(path_fs);
|
||||
tune.selectSong(song_num);
|
||||
|
||||
int song_len=get_song_length(path_fs);
|
||||
if(song_len==-1) song_len=default_songlength;
|
||||
|
||||
/* initialize the player */
|
||||
|
||||
sidplay2 player;
|
||||
int iret = player.load(&tune);
|
||||
if (iret != 0) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"sidplay2.load() failed: %s", player.error());
|
||||
return;
|
||||
}
|
||||
|
||||
/* initialize the builder */
|
||||
|
||||
ReSIDBuilder builder("ReSID");
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain,
|
||||
"failed to initialize ReSIDBuilder");
|
||||
return;
|
||||
}
|
||||
|
||||
builder.create(player.info().maxsids);
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain, "ReSIDBuilder.create() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
builder.filter(filter_setting);
|
||||
if (!builder) {
|
||||
LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
/* configure the player */
|
||||
|
||||
sid2_config_t config = player.config();
|
||||
|
||||
config.clockDefault = SID2_CLOCK_PAL;
|
||||
config.clockForced = true;
|
||||
config.clockSpeed = SID2_CLOCK_CORRECT;
|
||||
config.frequency = 48000;
|
||||
config.optimisation = SID2_DEFAULT_OPTIMISATION;
|
||||
|
||||
config.precision = 16;
|
||||
config.sidDefault = SID2_MOS6581;
|
||||
config.sidEmulation = &builder;
|
||||
config.sidModel = SID2_MODEL_CORRECT;
|
||||
config.sidSamples = true;
|
||||
config.sampleFormat = IsLittleEndian()
|
||||
? SID2_LITTLE_SIGNED
|
||||
: SID2_BIG_SIGNED;
|
||||
if (tune.isStereo()) {
|
||||
config.playback = sid2_stereo;
|
||||
channels = 2;
|
||||
} else {
|
||||
config.playback = sid2_mono;
|
||||
channels = 1;
|
||||
}
|
||||
|
||||
iret = player.config(config);
|
||||
if (iret != 0) {
|
||||
FormatWarning(sidplay_domain,
|
||||
"sidplay2.config() failed: %s", player.error());
|
||||
return;
|
||||
}
|
||||
|
||||
/* initialize the MPD decoder */
|
||||
|
||||
const AudioFormat audio_format(48000, SampleFormat::S16, channels);
|
||||
assert(audio_format.IsValid());
|
||||
|
||||
decoder_initialized(decoder, audio_format, true, (float)song_len);
|
||||
|
||||
/* .. and play */
|
||||
|
||||
const unsigned timebase = player.timebase();
|
||||
song_len *= timebase;
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
char buffer[4096];
|
||||
size_t nbytes;
|
||||
|
||||
nbytes = player.play(buffer, sizeof(buffer));
|
||||
if (nbytes == 0)
|
||||
break;
|
||||
|
||||
decoder_timestamp(decoder, (double)player.time() / timebase);
|
||||
|
||||
cmd = decoder_data(decoder, nullptr, buffer, nbytes, 0);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
unsigned data_time = player.time();
|
||||
unsigned target_time = (unsigned)
|
||||
(decoder_seek_where(decoder) * timebase);
|
||||
|
||||
/* can't rewind so return to zero and seek forward */
|
||||
if(target_time<data_time) {
|
||||
player.stop();
|
||||
data_time=0;
|
||||
}
|
||||
|
||||
/* ignore data until target time is reached */
|
||||
while(data_time<target_time) {
|
||||
nbytes=player.play(buffer, sizeof(buffer));
|
||||
if(nbytes==0)
|
||||
break;
|
||||
data_time = player.time();
|
||||
}
|
||||
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
|
||||
if (song_len > 0 && player.time() >= (unsigned)song_len)
|
||||
break;
|
||||
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
}
|
||||
|
||||
static bool
|
||||
sidplay_scan_file(const char *path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
int song_num=get_song_num(path_fs);
|
||||
char *path_container=get_container_name(path_fs);
|
||||
|
||||
SidTune tune(path_container, nullptr, true);
|
||||
free(path_container);
|
||||
if (!tune)
|
||||
return false;
|
||||
|
||||
const SidTuneInfo &info = tune.getInfo();
|
||||
|
||||
/* title */
|
||||
const char *title;
|
||||
if (info.numberOfInfoStrings > 0 && info.infoString[0] != nullptr)
|
||||
title=info.infoString[0];
|
||||
else
|
||||
title="";
|
||||
|
||||
if(info.songs>1) {
|
||||
char tag_title[1024];
|
||||
snprintf(tag_title, sizeof(tag_title),
|
||||
"%s (%d/%d)",
|
||||
title, song_num, info.songs);
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_TITLE, tag_title);
|
||||
} else
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, title);
|
||||
|
||||
/* artist */
|
||||
if (info.numberOfInfoStrings > 1 && info.infoString[1] != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_ARTIST,
|
||||
info.infoString[1]);
|
||||
|
||||
/* track */
|
||||
char track[16];
|
||||
sprintf(track, "%d", song_num);
|
||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
|
||||
|
||||
/* time */
|
||||
int song_len=get_song_length(path_fs);
|
||||
if (song_len >= 0)
|
||||
tag_handler_invoke_duration(handler, handler_ctx, song_len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *
|
||||
sidplay_container_scan(const char *path_fs, const unsigned int tnum)
|
||||
{
|
||||
SidTune tune(path_fs, nullptr, true);
|
||||
if (!tune)
|
||||
return nullptr;
|
||||
|
||||
const SidTuneInfo &info=tune.getInfo();
|
||||
|
||||
/* Don't treat sids containing a single tune
|
||||
as containers */
|
||||
if(!all_files_are_containers && info.songs<2)
|
||||
return nullptr;
|
||||
|
||||
/* Construct container/tune path names, eg.
|
||||
Delta.sid/tune_001.sid */
|
||||
if(tnum<=info.songs) {
|
||||
char *subtune= g_strdup_printf(
|
||||
SUBTUNE_PREFIX "%03u.sid", tnum);
|
||||
return subtune;
|
||||
} else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const char *const sidplay_suffixes[] = {
|
||||
"sid",
|
||||
"mus",
|
||||
"str",
|
||||
"prg",
|
||||
"P00",
|
||||
nullptr
|
||||
};
|
||||
|
||||
extern const struct DecoderPlugin sidplay_decoder_plugin;
|
||||
const struct DecoderPlugin sidplay_decoder_plugin = {
|
||||
"sidplay",
|
||||
sidplay_init,
|
||||
sidplay_finish,
|
||||
nullptr, /* stream_decode() */
|
||||
sidplay_file_decode,
|
||||
sidplay_scan_file,
|
||||
nullptr, /* stream_tag() */
|
||||
sidplay_container_scan,
|
||||
sidplay_suffixes,
|
||||
nullptr, /* mime_types */
|
||||
};
|
25
src/decoder/plugins/SidplayDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/SidplayDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_SIDPLAY_HXX
|
||||
#define MPD_DECODER_SIDPLAY_HXX
|
||||
|
||||
extern const struct DecoderPlugin sidplay_decoder_plugin;
|
||||
|
||||
#endif
|
260
src/decoder/plugins/SndfileDecoderPlugin.cxx
Normal file
260
src/decoder/plugins/SndfileDecoderPlugin.cxx
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "SndfileDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <sndfile.h>
|
||||
|
||||
static constexpr Domain sndfile_domain("sndfile");
|
||||
|
||||
static sf_count_t
|
||||
sndfile_vio_get_filelen(void *user_data)
|
||||
{
|
||||
const InputStream &is = *(const InputStream *)user_data;
|
||||
|
||||
return is.GetSize();
|
||||
}
|
||||
|
||||
static sf_count_t
|
||||
sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
|
||||
{
|
||||
InputStream &is = *(InputStream *)user_data;
|
||||
|
||||
if (!is.LockSeek(offset, whence, IgnoreError()))
|
||||
return -1;
|
||||
|
||||
return is.GetOffset();
|
||||
}
|
||||
|
||||
static sf_count_t
|
||||
sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
|
||||
{
|
||||
InputStream &is = *(InputStream *)user_data;
|
||||
|
||||
Error error;
|
||||
size_t nbytes = is.LockRead(ptr, count, error);
|
||||
if (nbytes == 0 && error.IsDefined()) {
|
||||
LogError(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
static sf_count_t
|
||||
sndfile_vio_write(gcc_unused const void *ptr,
|
||||
gcc_unused sf_count_t count,
|
||||
gcc_unused void *user_data)
|
||||
{
|
||||
/* no writing! */
|
||||
return -1;
|
||||
}
|
||||
|
||||
static sf_count_t
|
||||
sndfile_vio_tell(void *user_data)
|
||||
{
|
||||
const InputStream &is = *(const InputStream *)user_data;
|
||||
|
||||
return is.GetOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a
|
||||
* libsndfile stream.
|
||||
*/
|
||||
static SF_VIRTUAL_IO vio = {
|
||||
sndfile_vio_get_filelen,
|
||||
sndfile_vio_seek,
|
||||
sndfile_vio_read,
|
||||
sndfile_vio_write,
|
||||
sndfile_vio_tell,
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a frame number to a timestamp (in seconds).
|
||||
*/
|
||||
static float
|
||||
frame_to_time(sf_count_t frame, const AudioFormat *audio_format)
|
||||
{
|
||||
return (float)frame / (float)audio_format->sample_rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a timestamp (in seconds) to a frame number.
|
||||
*/
|
||||
static sf_count_t
|
||||
time_to_frame(float t, const AudioFormat *audio_format)
|
||||
{
|
||||
return (sf_count_t)(t * audio_format->sample_rate);
|
||||
}
|
||||
|
||||
static void
|
||||
sndfile_stream_decode(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
SNDFILE *sf;
|
||||
SF_INFO info;
|
||||
size_t frame_size;
|
||||
sf_count_t read_frames, num_frames;
|
||||
int buffer[4096];
|
||||
|
||||
info.format = 0;
|
||||
|
||||
sf = sf_open_virtual(&vio, SFM_READ, &info, &is);
|
||||
if (sf == nullptr) {
|
||||
LogWarning(sndfile_domain, "sf_open_virtual() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
/* for now, always read 32 bit samples. Later, we could lower
|
||||
MPD's CPU usage by reading 16 bit samples with
|
||||
sf_readf_short() on low-quality source files. */
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, info.samplerate,
|
||||
SampleFormat::S32,
|
||||
info.channels, error)) {
|
||||
LogError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
decoder_initialized(decoder, audio_format, info.seekable,
|
||||
frame_to_time(info.frames, &audio_format));
|
||||
|
||||
frame_size = audio_format.GetFrameSize();
|
||||
read_frames = sizeof(buffer) / frame_size;
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
num_frames = sf_readf_int(sf, buffer, read_frames);
|
||||
if (num_frames <= 0)
|
||||
break;
|
||||
|
||||
cmd = decoder_data(decoder, is,
|
||||
buffer, num_frames * frame_size,
|
||||
0);
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
sf_count_t c =
|
||||
time_to_frame(decoder_seek_where(decoder),
|
||||
&audio_format);
|
||||
c = sf_seek(sf, c, SEEK_SET);
|
||||
if (c < 0)
|
||||
decoder_seek_error(decoder);
|
||||
else
|
||||
decoder_command_finished(decoder);
|
||||
cmd = DecoderCommand::NONE;
|
||||
}
|
||||
} while (cmd == DecoderCommand::NONE);
|
||||
|
||||
sf_close(sf);
|
||||
}
|
||||
|
||||
static bool
|
||||
sndfile_scan_file(const char *path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
SNDFILE *sf;
|
||||
SF_INFO info;
|
||||
const char *p;
|
||||
|
||||
info.format = 0;
|
||||
|
||||
sf = sf_open(path_fs, SFM_READ, &info);
|
||||
if (sf == nullptr)
|
||||
return false;
|
||||
|
||||
if (!audio_valid_sample_rate(info.samplerate)) {
|
||||
sf_close(sf);
|
||||
FormatWarning(sndfile_domain,
|
||||
"Invalid sample rate in %s", path_fs);
|
||||
return false;
|
||||
}
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
info.frames / info.samplerate);
|
||||
|
||||
p = sf_get_string(sf, SF_STR_TITLE);
|
||||
if (p != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_TITLE, p);
|
||||
|
||||
p = sf_get_string(sf, SF_STR_ARTIST);
|
||||
if (p != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_ARTIST, p);
|
||||
|
||||
p = sf_get_string(sf, SF_STR_DATE);
|
||||
if (p != nullptr)
|
||||
tag_handler_invoke_tag(handler, handler_ctx,
|
||||
TAG_DATE, p);
|
||||
|
||||
sf_close(sf);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const sndfile_suffixes[] = {
|
||||
"wav", "aiff", "aif", /* Microsoft / SGI / Apple */
|
||||
"au", "snd", /* Sun / DEC / NeXT */
|
||||
"paf", /* Paris Audio File */
|
||||
"iff", "svx", /* Commodore Amiga IFF / SVX */
|
||||
"sf", /* IRCAM */
|
||||
"voc", /* Creative */
|
||||
"w64", /* Soundforge */
|
||||
"pvf", /* Portable Voice Format */
|
||||
"xi", /* Fasttracker */
|
||||
"htk", /* HMM Tool Kit */
|
||||
"caf", /* Apple */
|
||||
"sd2", /* Sound Designer II */
|
||||
|
||||
/* libsndfile also supports FLAC and Ogg Vorbis, but only by
|
||||
linking with libFLAC and libvorbis - we can do better, we
|
||||
have native plugins for these libraries */
|
||||
|
||||
nullptr
|
||||
};
|
||||
|
||||
static const char *const sndfile_mime_types[] = {
|
||||
"audio/x-wav",
|
||||
"audio/x-aiff",
|
||||
|
||||
/* what are the MIME types of the other supported formats? */
|
||||
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin sndfile_decoder_plugin = {
|
||||
"sndfile",
|
||||
nullptr,
|
||||
nullptr,
|
||||
sndfile_stream_decode,
|
||||
nullptr,
|
||||
sndfile_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
sndfile_suffixes,
|
||||
sndfile_mime_types,
|
||||
};
|
25
src/decoder/plugins/SndfileDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/SndfileDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_SNDFILE_HXX
|
||||
#define MPD_DECODER_SNDFILE_HXX
|
||||
|
||||
extern const struct DecoderPlugin sndfile_decoder_plugin;
|
||||
|
||||
#endif
|
141
src/decoder/plugins/VorbisComments.cxx
Normal file
141
src/decoder/plugins/VorbisComments.cxx
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "VorbisComments.hxx"
|
||||
#include "XiphTags.hxx"
|
||||
#include "tag/TagTable.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "tag/TagBuilder.hxx"
|
||||
#include "ReplayGainInfo.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/SplitString.hxx"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char *
|
||||
vorbis_comment_value(const char *comment, const char *needle)
|
||||
{
|
||||
size_t len = strlen(needle);
|
||||
|
||||
if (StringEqualsCaseASCII(comment, needle, len) &&
|
||||
comment[len] == '=')
|
||||
return comment + len + 1;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments)
|
||||
{
|
||||
rgi.Clear();
|
||||
|
||||
const char *temp;
|
||||
bool found = false;
|
||||
|
||||
while (*comments) {
|
||||
if ((temp =
|
||||
vorbis_comment_value(*comments, "replaygain_track_gain"))) {
|
||||
rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(temp);
|
||||
found = true;
|
||||
} else if ((temp = vorbis_comment_value(*comments,
|
||||
"replaygain_album_gain"))) {
|
||||
rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(temp);
|
||||
found = true;
|
||||
} else if ((temp = vorbis_comment_value(*comments,
|
||||
"replaygain_track_peak"))) {
|
||||
rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(temp);
|
||||
found = true;
|
||||
} else if ((temp = vorbis_comment_value(*comments,
|
||||
"replaygain_album_peak"))) {
|
||||
rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(temp);
|
||||
found = true;
|
||||
}
|
||||
|
||||
comments++;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the comment's name equals the passed name, and if so, copy
|
||||
* the comment value into the tag.
|
||||
*/
|
||||
static bool
|
||||
vorbis_copy_comment(const char *comment,
|
||||
const char *name, TagType tag_type,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
const char *value;
|
||||
|
||||
value = vorbis_comment_value(comment, name);
|
||||
if (value != nullptr) {
|
||||
tag_handler_invoke_tag(handler, handler_ctx, tag_type, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
vorbis_scan_comment(const char *comment,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
if (handler->pair != nullptr) {
|
||||
const SplitString split(comment, '=');
|
||||
if (split.IsDefined() && !split.IsEmpty())
|
||||
tag_handler_invoke_pair(handler, handler_ctx,
|
||||
split.GetFirst(),
|
||||
split.GetSecond());
|
||||
}
|
||||
|
||||
for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i)
|
||||
if (vorbis_copy_comment(comment, i->name, i->type,
|
||||
handler, handler_ctx))
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
if (vorbis_copy_comment(comment,
|
||||
tag_item_names[i], TagType(i),
|
||||
handler, handler_ctx))
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
vorbis_comments_scan(char **comments,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
while (*comments)
|
||||
vorbis_scan_comment(*comments++,
|
||||
handler, handler_ctx);
|
||||
|
||||
}
|
||||
|
||||
Tag *
|
||||
vorbis_comments_to_tag(char **comments)
|
||||
{
|
||||
TagBuilder tag_builder;
|
||||
vorbis_comments_scan(comments, &add_tag_handler, &tag_builder);
|
||||
return tag_builder.IsEmpty()
|
||||
? nullptr
|
||||
: tag_builder.CommitNew();
|
||||
}
|
39
src/decoder/plugins/VorbisComments.hxx
Normal file
39
src/decoder/plugins/VorbisComments.hxx
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_VORBIS_COMMENTS_HXX
|
||||
#define MPD_VORBIS_COMMENTS_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
struct ReplayGainInfo;
|
||||
struct tag_handler;
|
||||
struct Tag;
|
||||
|
||||
bool
|
||||
vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments);
|
||||
|
||||
void
|
||||
vorbis_comments_scan(char **comments,
|
||||
const tag_handler *handler, void *handler_ctx);
|
||||
|
||||
Tag *
|
||||
vorbis_comments_to_tag(char **comments);
|
||||
|
||||
#endif
|
349
src/decoder/plugins/VorbisDecoderPlugin.cxx
Normal file
349
src/decoder/plugins/VorbisDecoderPlugin.cxx
Normal file
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "VorbisDecoderPlugin.h"
|
||||
#include "VorbisComments.hxx"
|
||||
#include "VorbisDomain.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "OggCodec.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#ifndef HAVE_TREMOR
|
||||
#define OV_EXCLUDE_STATIC_CALLBACKS
|
||||
#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 */
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
struct vorbis_input_stream {
|
||||
Decoder *decoder;
|
||||
|
||||
InputStream *input_stream;
|
||||
bool seekable;
|
||||
};
|
||||
|
||||
static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data)
|
||||
{
|
||||
struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
|
||||
size_t ret = decoder_read(vis->decoder, *vis->input_stream,
|
||||
ptr, size * nmemb);
|
||||
|
||||
errno = 0;
|
||||
|
||||
return ret / size;
|
||||
}
|
||||
|
||||
static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence)
|
||||
{
|
||||
struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
|
||||
|
||||
Error error;
|
||||
return vis->seekable &&
|
||||
(vis->decoder == nullptr ||
|
||||
decoder_get_command(*vis->decoder) != DecoderCommand::STOP) &&
|
||||
vis->input_stream->LockSeek(offset, whence, error)
|
||||
? 0 : -1;
|
||||
}
|
||||
|
||||
/* TODO: check Ogg libraries API and see if we can just not have this func */
|
||||
static int ogg_close_cb(gcc_unused void *data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long ogg_tell_cb(void *data)
|
||||
{
|
||||
struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
|
||||
|
||||
return (long)vis->input_stream->offset;
|
||||
}
|
||||
|
||||
static const ov_callbacks vorbis_is_callbacks = {
|
||||
ogg_read_cb,
|
||||
ogg_seek_cb,
|
||||
ogg_close_cb,
|
||||
ogg_tell_cb,
|
||||
};
|
||||
|
||||
static const char *
|
||||
vorbis_strerror(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case OV_EREAD:
|
||||
return "read error";
|
||||
|
||||
case OV_ENOTVORBIS:
|
||||
return "not vorbis stream";
|
||||
|
||||
case OV_EVERSION:
|
||||
return "vorbis version mismatch";
|
||||
|
||||
case OV_EBADHEADER:
|
||||
return "invalid vorbis header";
|
||||
|
||||
case OV_EFAULT:
|
||||
return "internal logic error";
|
||||
|
||||
default:
|
||||
return "unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf,
|
||||
Decoder *decoder, InputStream &input_stream)
|
||||
{
|
||||
vis->decoder = decoder;
|
||||
vis->input_stream = &input_stream;
|
||||
vis->seekable = input_stream.CheapSeeking();
|
||||
|
||||
int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks);
|
||||
if (ret < 0) {
|
||||
if (decoder == nullptr ||
|
||||
decoder_get_command(*decoder) == DecoderCommand::NONE)
|
||||
FormatWarning(vorbis_domain,
|
||||
"Failed to open Ogg Vorbis stream: %s",
|
||||
vorbis_strerror(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
vorbis_send_comments(Decoder &decoder, InputStream &is,
|
||||
char **comments)
|
||||
{
|
||||
Tag *tag = vorbis_comments_to_tag(comments);
|
||||
if (!tag)
|
||||
return;
|
||||
|
||||
decoder_tag(decoder, is, std::move(*tag));
|
||||
delete tag;
|
||||
}
|
||||
|
||||
#ifndef HAVE_TREMOR
|
||||
static void
|
||||
vorbis_interleave(float *dest, const float *const*src,
|
||||
unsigned nframes, unsigned channels)
|
||||
{
|
||||
for (const float *const*src_end = src + channels;
|
||||
src != src_end; ++src, ++dest) {
|
||||
float *d = dest;
|
||||
for (const float *s = *src, *s_end = s + nframes;
|
||||
s != s_end; ++s, d += channels)
|
||||
*d = *s;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* public */
|
||||
static void
|
||||
vorbis_stream_decode(Decoder &decoder,
|
||||
InputStream &input_stream)
|
||||
{
|
||||
if (ogg_codec_detect(&decoder, input_stream) != OGG_CODEC_VORBIS)
|
||||
return;
|
||||
|
||||
/* rewind the stream, because ogg_codec_detect() has
|
||||
moved it */
|
||||
input_stream.LockRewind(IgnoreError());
|
||||
|
||||
struct vorbis_input_stream vis;
|
||||
OggVorbis_File vf;
|
||||
if (!vorbis_is_open(&vis, &vf, &decoder, input_stream))
|
||||
return;
|
||||
|
||||
const vorbis_info *vi = ov_info(&vf, -1);
|
||||
if (vi == nullptr) {
|
||||
LogWarning(vorbis_domain, "ov_info() has failed");
|
||||
return;
|
||||
}
|
||||
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format, vi->rate,
|
||||
#ifdef HAVE_TREMOR
|
||||
SampleFormat::S16,
|
||||
#else
|
||||
SampleFormat::FLOAT,
|
||||
#endif
|
||||
vi->channels, error)) {
|
||||
LogError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
float total_time = ov_time_total(&vf, -1);
|
||||
if (total_time < 0)
|
||||
total_time = 0;
|
||||
|
||||
decoder_initialized(decoder, audio_format, vis.seekable, total_time);
|
||||
|
||||
#ifdef HAVE_TREMOR
|
||||
char buffer[4096];
|
||||
#else
|
||||
float buffer[2048];
|
||||
const int frames_per_buffer =
|
||||
ARRAY_SIZE(buffer) / audio_format.channels;
|
||||
const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels;
|
||||
#endif
|
||||
|
||||
int prev_section = -1;
|
||||
unsigned kbit_rate = 0;
|
||||
|
||||
DecoderCommand cmd = decoder_get_command(decoder);
|
||||
do {
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
double seek_where = decoder_seek_where(decoder);
|
||||
if (0 == ov_time_seek_page(&vf, seek_where)) {
|
||||
decoder_command_finished(decoder);
|
||||
} else
|
||||
decoder_seek_error(decoder);
|
||||
}
|
||||
|
||||
int current_section;
|
||||
|
||||
#ifdef HAVE_TREMOR
|
||||
long nbytes = ov_read(&vf, buffer, sizeof(buffer),
|
||||
IsBigEndian(), 2, 1,
|
||||
¤t_section);
|
||||
#else
|
||||
float **per_channel;
|
||||
long nframes = ov_read_float(&vf, &per_channel,
|
||||
frames_per_buffer,
|
||||
¤t_section);
|
||||
long nbytes = nframes;
|
||||
if (nframes > 0) {
|
||||
vorbis_interleave(buffer,
|
||||
(const float*const*)per_channel,
|
||||
nframes, audio_format.channels);
|
||||
nbytes *= frame_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (nbytes == OV_HOLE) /* bad packet */
|
||||
nbytes = 0;
|
||||
else if (nbytes <= 0)
|
||||
/* break on EOF or other error */
|
||||
break;
|
||||
|
||||
if (current_section != prev_section) {
|
||||
vi = ov_info(&vf, -1);
|
||||
if (vi == nullptr) {
|
||||
LogWarning(vorbis_domain,
|
||||
"ov_info() has failed");
|
||||
break;
|
||||
}
|
||||
|
||||
if (vi->rate != (long)audio_format.sample_rate ||
|
||||
vi->channels != (int)audio_format.channels) {
|
||||
/* we don't support audio format
|
||||
change yet */
|
||||
LogWarning(vorbis_domain,
|
||||
"audio format change, stopping here");
|
||||
break;
|
||||
}
|
||||
|
||||
char **comments = ov_comment(&vf, -1)->user_comments;
|
||||
vorbis_send_comments(decoder, input_stream, comments);
|
||||
|
||||
ReplayGainInfo rgi;
|
||||
if (vorbis_comments_to_replay_gain(rgi, comments))
|
||||
decoder_replay_gain(decoder, &rgi);
|
||||
|
||||
prev_section = current_section;
|
||||
}
|
||||
|
||||
long test = ov_bitrate_instant(&vf);
|
||||
if (test > 0)
|
||||
kbit_rate = test / 1000;
|
||||
|
||||
cmd = decoder_data(decoder, input_stream,
|
||||
buffer, nbytes,
|
||||
kbit_rate);
|
||||
} while (cmd != DecoderCommand::STOP);
|
||||
|
||||
ov_clear(&vf);
|
||||
}
|
||||
|
||||
static bool
|
||||
vorbis_scan_stream(InputStream &is,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
struct vorbis_input_stream vis;
|
||||
OggVorbis_File vf;
|
||||
|
||||
if (!vorbis_is_open(&vis, &vf, nullptr, is))
|
||||
return false;
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
(int)(ov_time_total(&vf, -1) + 0.5));
|
||||
|
||||
vorbis_comments_scan(ov_comment(&vf, -1)->user_comments,
|
||||
handler, handler_ctx);
|
||||
|
||||
ov_clear(&vf);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const vorbis_suffixes[] = {
|
||||
"ogg", "oga", nullptr
|
||||
};
|
||||
|
||||
static const char *const vorbis_mime_types[] = {
|
||||
"application/ogg",
|
||||
"application/x-ogg",
|
||||
"audio/ogg",
|
||||
"audio/vorbis",
|
||||
"audio/vorbis+ogg",
|
||||
"audio/x-ogg",
|
||||
"audio/x-vorbis",
|
||||
"audio/x-vorbis+ogg",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin vorbis_decoder_plugin = {
|
||||
"vorbis",
|
||||
nullptr,
|
||||
nullptr,
|
||||
vorbis_stream_decode,
|
||||
nullptr,
|
||||
nullptr,
|
||||
vorbis_scan_stream,
|
||||
nullptr,
|
||||
vorbis_suffixes,
|
||||
vorbis_mime_types
|
||||
};
|
25
src/decoder/plugins/VorbisDecoderPlugin.h
Normal file
25
src/decoder/plugins/VorbisDecoderPlugin.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_VORBIS_H
|
||||
#define MPD_DECODER_VORBIS_H
|
||||
|
||||
extern const struct DecoderPlugin vorbis_decoder_plugin;
|
||||
|
||||
#endif
|
24
src/decoder/plugins/VorbisDomain.cxx
Normal file
24
src/decoder/plugins/VorbisDomain.cxx
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "VorbisDomain.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
|
||||
const Domain vorbis_domain("vorbis");
|
29
src/decoder/plugins/VorbisDomain.hxx
Normal file
29
src/decoder/plugins/VorbisDomain.hxx
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_VORBIS_DOMAIN_HXX
|
||||
#define MPD_VORBIS_DOMAIN_HXX
|
||||
|
||||
#include "check.h"
|
||||
|
||||
class Domain;
|
||||
|
||||
extern const Domain vorbis_domain;
|
||||
|
||||
#endif
|
571
src/decoder/plugins/WavpackDecoderPlugin.cxx
Normal file
571
src/decoder/plugins/WavpackDecoderPlugin.cxx
Normal file
@@ -0,0 +1,571 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "WavpackDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "InputStream.hxx"
|
||||
#include "CheckAudioFormat.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "tag/ApeTag.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Macros.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
#include <wavpack/wavpack.h>
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ERRORLEN 80
|
||||
|
||||
static constexpr Domain wavpack_domain("wavpack");
|
||||
|
||||
/** A pointer type for format converter function. */
|
||||
typedef void (*format_samples_t)(
|
||||
int bytes_per_sample,
|
||||
void *buffer, uint32_t count
|
||||
);
|
||||
|
||||
/*
|
||||
* This function has been borrowed from the tiny player found on
|
||||
* wavpack.com. Modifications were required because mpd only handles
|
||||
* max 24-bit samples.
|
||||
*/
|
||||
static void
|
||||
format_samples_int(int bytes_per_sample, void *buffer, uint32_t count)
|
||||
{
|
||||
int32_t *src = (int32_t *)buffer;
|
||||
|
||||
switch (bytes_per_sample) {
|
||||
case 1: {
|
||||
int8_t *dst = (int8_t *)buffer;
|
||||
/*
|
||||
* The asserts like the following one are because we do the
|
||||
* formatting of samples within a single buffer. The size
|
||||
* of the output samples never can be greater than the size
|
||||
* of the input ones. Otherwise we would have an overflow.
|
||||
*/
|
||||
static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size");
|
||||
|
||||
/* pass through and align 8-bit samples */
|
||||
while (count--) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
uint16_t *dst = (uint16_t *)buffer;
|
||||
static_assert(sizeof(*dst) <= sizeof(*src), "Wrong size");
|
||||
|
||||
/* pass through and align 16-bit samples */
|
||||
while (count--) {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
/* do nothing */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function converts floating point sample data to 24-bit integer.
|
||||
*/
|
||||
static void
|
||||
format_samples_float(gcc_unused int bytes_per_sample, void *buffer,
|
||||
uint32_t count)
|
||||
{
|
||||
float *p = (float *)buffer;
|
||||
|
||||
while (count--) {
|
||||
*p /= (1 << 23);
|
||||
++p;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose a MPD sample format from libwavpacks' number of bits.
|
||||
*/
|
||||
static SampleFormat
|
||||
wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample)
|
||||
{
|
||||
if (is_float)
|
||||
return SampleFormat::FLOAT;
|
||||
|
||||
switch (bytes_per_sample) {
|
||||
case 1:
|
||||
return SampleFormat::S8;
|
||||
|
||||
case 2:
|
||||
return SampleFormat::S16;
|
||||
|
||||
case 3:
|
||||
return SampleFormat::S24_P32;
|
||||
|
||||
case 4:
|
||||
return SampleFormat::S32;
|
||||
|
||||
default:
|
||||
return SampleFormat::UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This does the main decoding thing.
|
||||
* Requires an already opened WavpackContext.
|
||||
*/
|
||||
static void
|
||||
wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek)
|
||||
{
|
||||
bool is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0;
|
||||
SampleFormat sample_format =
|
||||
wavpack_bits_to_sample_format(is_float,
|
||||
WavpackGetBytesPerSample(wpc));
|
||||
|
||||
Error error;
|
||||
AudioFormat audio_format;
|
||||
if (!audio_format_init_checked(audio_format,
|
||||
WavpackGetSampleRate(wpc),
|
||||
sample_format,
|
||||
WavpackGetNumChannels(wpc), error)) {
|
||||
LogError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const format_samples_t format_samples = is_float
|
||||
? format_samples_float
|
||||
: format_samples_int;
|
||||
|
||||
const float total_time = float(WavpackGetNumSamples(wpc))
|
||||
/ audio_format.sample_rate;
|
||||
|
||||
const int bytes_per_sample = WavpackGetBytesPerSample(wpc);
|
||||
const int output_sample_size = audio_format.GetFrameSize();
|
||||
|
||||
/* wavpack gives us all kind of samples in a 32-bit space */
|
||||
int32_t chunk[1024];
|
||||
const uint32_t samples_requested = ARRAY_SIZE(chunk) /
|
||||
audio_format.channels;
|
||||
|
||||
decoder_initialized(decoder, audio_format, can_seek, total_time);
|
||||
|
||||
DecoderCommand cmd = decoder_get_command(decoder);
|
||||
while (cmd != DecoderCommand::STOP) {
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
if (can_seek) {
|
||||
unsigned where = decoder_seek_where(decoder) *
|
||||
audio_format.sample_rate;
|
||||
|
||||
if (WavpackSeekSample(wpc, where)) {
|
||||
decoder_command_finished(decoder);
|
||||
} else {
|
||||
decoder_seek_error(decoder);
|
||||
}
|
||||
} else {
|
||||
decoder_seek_error(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t samples_got = WavpackUnpackSamples(wpc, chunk,
|
||||
samples_requested);
|
||||
if (samples_got == 0)
|
||||
break;
|
||||
|
||||
int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 +
|
||||
0.5);
|
||||
format_samples(bytes_per_sample, chunk,
|
||||
samples_got * audio_format.channels);
|
||||
|
||||
cmd = decoder_data(decoder, nullptr, chunk,
|
||||
samples_got * output_sample_size,
|
||||
bitrate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate and parse a floating point tag. Returns true if it was
|
||||
* found.
|
||||
*/
|
||||
static bool
|
||||
wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r)
|
||||
{
|
||||
char buffer[64];
|
||||
if (WavpackGetTagItem(wpc, key, buffer, sizeof(buffer)) <= 0)
|
||||
return false;
|
||||
|
||||
*value_r = atof(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
wavpack_replaygain(ReplayGainInfo &rgi,
|
||||
WavpackContext *wpc)
|
||||
{
|
||||
rgi.Clear();
|
||||
|
||||
bool found = false;
|
||||
found |= wavpack_tag_float(wpc, "replaygain_track_gain",
|
||||
&rgi.tuples[REPLAY_GAIN_TRACK].gain);
|
||||
found |= wavpack_tag_float(wpc, "replaygain_track_peak",
|
||||
&rgi.tuples[REPLAY_GAIN_TRACK].peak);
|
||||
found |= wavpack_tag_float(wpc, "replaygain_album_gain",
|
||||
&rgi.tuples[REPLAY_GAIN_ALBUM].gain);
|
||||
found |= wavpack_tag_float(wpc, "replaygain_album_peak",
|
||||
&rgi.tuples[REPLAY_GAIN_ALBUM].peak);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static void
|
||||
wavpack_scan_tag_item(WavpackContext *wpc, const char *name,
|
||||
TagType type,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
char buffer[1024];
|
||||
int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer));
|
||||
if (len <= 0 || (unsigned)len >= sizeof(buffer))
|
||||
return;
|
||||
|
||||
tag_handler_invoke_tag(handler, handler_ctx, type, buffer);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
wavpack_scan_pair(WavpackContext *wpc, const char *name,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
char buffer[8192];
|
||||
int len = WavpackGetTagItem(wpc, name, buffer, sizeof(buffer));
|
||||
if (len <= 0 || (unsigned)len >= sizeof(buffer))
|
||||
return;
|
||||
|
||||
tag_handler_invoke_pair(handler, handler_ctx, name, buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads metainfo from the specified file.
|
||||
*/
|
||||
static bool
|
||||
wavpack_scan_file(const char *fname,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
char error[ERRORLEN];
|
||||
WavpackContext *wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
|
||||
if (wpc == nullptr) {
|
||||
FormatError(wavpack_domain,
|
||||
"failed to open WavPack file \"%s\": %s",
|
||||
fname, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
tag_handler_invoke_duration(handler, handler_ctx,
|
||||
WavpackGetNumSamples(wpc) /
|
||||
WavpackGetSampleRate(wpc));
|
||||
|
||||
/* the WavPack format implies APEv2 tags, which means we can
|
||||
reuse the mapping from tag_ape.c */
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
|
||||
const char *name = tag_item_names[i];
|
||||
if (name != nullptr)
|
||||
wavpack_scan_tag_item(wpc, name, (TagType)i,
|
||||
handler, handler_ctx);
|
||||
}
|
||||
|
||||
for (const struct tag_table *i = ape_tags; i->name != nullptr; ++i)
|
||||
wavpack_scan_tag_item(wpc, i->name, i->type,
|
||||
handler, handler_ctx);
|
||||
|
||||
if (handler->pair != nullptr) {
|
||||
char name[64];
|
||||
|
||||
for (int i = 0, n = WavpackGetNumTagItems(wpc);
|
||||
i < n; ++i) {
|
||||
int len = WavpackGetTagItemIndexed(wpc, i, name,
|
||||
sizeof(name));
|
||||
if (len <= 0 || (unsigned)len >= sizeof(name))
|
||||
continue;
|
||||
|
||||
wavpack_scan_pair(wpc, name, handler, handler_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
WavpackCloseFile(wpc);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* mpd input_stream <=> WavpackStreamReader wrapper callbacks
|
||||
*/
|
||||
|
||||
/* This struct is needed for per-stream last_byte storage. */
|
||||
struct wavpack_input {
|
||||
Decoder *decoder;
|
||||
InputStream *is;
|
||||
/* Needed for push_back_byte() */
|
||||
int last_byte;
|
||||
};
|
||||
|
||||
/**
|
||||
* Little wrapper for struct wavpack_input to cast from void *.
|
||||
*/
|
||||
static struct wavpack_input *
|
||||
wpin(void *id)
|
||||
{
|
||||
assert(id);
|
||||
return (struct wavpack_input *)id;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
|
||||
{
|
||||
uint8_t *buf = (uint8_t *)data;
|
||||
int32_t i = 0;
|
||||
|
||||
if (wpin(id)->last_byte != EOF) {
|
||||
*buf++ = wpin(id)->last_byte;
|
||||
wpin(id)->last_byte = EOF;
|
||||
--bcount;
|
||||
++i;
|
||||
}
|
||||
|
||||
/* wavpack fails if we return a partial read, so we just wait
|
||||
until the buffer is full */
|
||||
while (bcount > 0) {
|
||||
size_t nbytes = decoder_read(
|
||||
wpin(id)->decoder, *wpin(id)->is, buf, bcount
|
||||
);
|
||||
if (nbytes == 0) {
|
||||
/* EOF, error or a decoder command */
|
||||
break;
|
||||
}
|
||||
|
||||
i += nbytes;
|
||||
bcount -= nbytes;
|
||||
buf += nbytes;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
wavpack_input_get_pos(void *id)
|
||||
{
|
||||
return wpin(id)->is->offset;
|
||||
}
|
||||
|
||||
static int
|
||||
wavpack_input_set_pos_abs(void *id, uint32_t pos)
|
||||
{
|
||||
return wpin(id)->is->LockSeek(pos, SEEK_SET, IgnoreError()) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
|
||||
{
|
||||
return wpin(id)->is->LockSeek(delta, mode, IgnoreError()) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
wavpack_input_push_back_byte(void *id, int c)
|
||||
{
|
||||
if (wpin(id)->last_byte == EOF) {
|
||||
wpin(id)->last_byte = c;
|
||||
return c;
|
||||
} else {
|
||||
return EOF;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
wavpack_input_get_length(void *id)
|
||||
{
|
||||
if (wpin(id)->is->size < 0)
|
||||
return 0;
|
||||
|
||||
return wpin(id)->is->size;
|
||||
}
|
||||
|
||||
static int
|
||||
wavpack_input_can_seek(void *id)
|
||||
{
|
||||
return wpin(id)->is->seekable;
|
||||
}
|
||||
|
||||
static WavpackStreamReader mpd_is_reader = {
|
||||
wavpack_input_read_bytes,
|
||||
wavpack_input_get_pos,
|
||||
wavpack_input_set_pos_abs,
|
||||
wavpack_input_set_pos_rel,
|
||||
wavpack_input_push_back_byte,
|
||||
wavpack_input_get_length,
|
||||
wavpack_input_can_seek,
|
||||
nullptr /* no need to write edited tags */
|
||||
};
|
||||
|
||||
static void
|
||||
wavpack_input_init(struct wavpack_input *isp, Decoder &decoder,
|
||||
InputStream &is)
|
||||
{
|
||||
isp->decoder = &decoder;
|
||||
isp->is = &is;
|
||||
isp->last_byte = EOF;
|
||||
}
|
||||
|
||||
static InputStream *
|
||||
wavpack_open_wvc(Decoder &decoder, const char *uri,
|
||||
Mutex &mutex, Cond &cond,
|
||||
struct wavpack_input *wpi)
|
||||
{
|
||||
/*
|
||||
* As we use dc->utf8url, this function will be bad for
|
||||
* single files. utf8url is not absolute file path :/
|
||||
*/
|
||||
if (uri == nullptr)
|
||||
return nullptr;
|
||||
|
||||
char *wvc_url = g_strconcat(uri, "c", nullptr);
|
||||
|
||||
InputStream *is_wvc = InputStream::Open(wvc_url, mutex, cond,
|
||||
IgnoreError());
|
||||
g_free(wvc_url);
|
||||
|
||||
if (is_wvc == nullptr)
|
||||
return nullptr;
|
||||
|
||||
/*
|
||||
* And we try to buffer in order to get know
|
||||
* about a possible 404 error.
|
||||
*/
|
||||
char first_byte;
|
||||
size_t nbytes = decoder_read(decoder, *is_wvc,
|
||||
&first_byte, sizeof(first_byte));
|
||||
if (nbytes == 0) {
|
||||
is_wvc->Close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* push it back */
|
||||
wavpack_input_init(wpi, decoder, *is_wvc);
|
||||
wpi->last_byte = first_byte;
|
||||
return is_wvc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decodes a stream.
|
||||
*/
|
||||
static void
|
||||
wavpack_streamdecode(Decoder &decoder, InputStream &is)
|
||||
{
|
||||
int open_flags = OPEN_NORMALIZE;
|
||||
bool can_seek = is.seekable;
|
||||
|
||||
wavpack_input isp_wvc;
|
||||
InputStream *is_wvc = wavpack_open_wvc(decoder, is.uri.c_str(),
|
||||
is.mutex, is.cond,
|
||||
&isp_wvc);
|
||||
if (is_wvc != nullptr) {
|
||||
open_flags |= OPEN_WVC;
|
||||
can_seek &= is_wvc->seekable;
|
||||
}
|
||||
|
||||
if (!can_seek) {
|
||||
open_flags |= OPEN_STREAMING;
|
||||
}
|
||||
|
||||
wavpack_input isp;
|
||||
wavpack_input_init(&isp, decoder, is);
|
||||
|
||||
char error[ERRORLEN];
|
||||
WavpackContext *wpc =
|
||||
WavpackOpenFileInputEx(&mpd_is_reader, &isp,
|
||||
open_flags & OPEN_WVC
|
||||
? &isp_wvc : nullptr,
|
||||
error, open_flags, 23);
|
||||
|
||||
if (wpc == nullptr) {
|
||||
FormatError(wavpack_domain,
|
||||
"failed to open WavPack stream: %s", error);
|
||||
return;
|
||||
}
|
||||
|
||||
wavpack_decode(decoder, wpc, can_seek);
|
||||
|
||||
WavpackCloseFile(wpc);
|
||||
if (open_flags & OPEN_WVC) {
|
||||
is_wvc->Close();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Decodes a file.
|
||||
*/
|
||||
static void
|
||||
wavpack_filedecode(Decoder &decoder, const char *fname)
|
||||
{
|
||||
char error[ERRORLEN];
|
||||
WavpackContext *wpc = WavpackOpenFileInput(fname, error,
|
||||
OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE,
|
||||
23);
|
||||
if (wpc == nullptr) {
|
||||
FormatWarning(wavpack_domain,
|
||||
"failed to open WavPack file \"%s\": %s",
|
||||
fname, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ReplayGainInfo rgi;
|
||||
if (wavpack_replaygain(rgi, wpc))
|
||||
decoder_replay_gain(decoder, &rgi);
|
||||
|
||||
wavpack_decode(decoder, wpc, true);
|
||||
|
||||
WavpackCloseFile(wpc);
|
||||
}
|
||||
|
||||
static char const *const wavpack_suffixes[] = {
|
||||
"wv",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static char const *const wavpack_mime_types[] = {
|
||||
"audio/x-wavpack",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin wavpack_decoder_plugin = {
|
||||
"wavpack",
|
||||
nullptr,
|
||||
nullptr,
|
||||
wavpack_streamdecode,
|
||||
wavpack_filedecode,
|
||||
wavpack_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
wavpack_suffixes,
|
||||
wavpack_mime_types
|
||||
};
|
25
src/decoder/plugins/WavpackDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/WavpackDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_WAVPACK_HXX
|
||||
#define MPD_DECODER_WAVPACK_HXX
|
||||
|
||||
extern const struct DecoderPlugin wavpack_decoder_plugin;
|
||||
|
||||
#endif
|
158
src/decoder/plugins/WildmidiDecoderPlugin.cxx
Normal file
158
src/decoder/plugins/WildmidiDecoderPlugin.cxx
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "WildmidiDecoderPlugin.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "tag/TagHandler.hxx"
|
||||
#include "util/Error.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "system/FatalError.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <wildmidi_lib.h>
|
||||
}
|
||||
|
||||
static constexpr Domain wildmidi_domain("wildmidi");
|
||||
|
||||
static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000;
|
||||
|
||||
static bool
|
||||
wildmidi_init(const config_param ¶m)
|
||||
{
|
||||
Error error;
|
||||
const AllocatedPath path =
|
||||
param.GetBlockPath("config_file",
|
||||
"/etc/timidity/timidity.cfg",
|
||||
error);
|
||||
if (path.IsNull())
|
||||
FatalError(error);
|
||||
|
||||
if (!FileExists(path)) {
|
||||
const auto utf8 = path.ToUTF8();
|
||||
FormatDebug(wildmidi_domain,
|
||||
"configuration file does not exist: %s",
|
||||
utf8.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return WildMidi_Init(path.c_str(), WILDMIDI_SAMPLE_RATE, 0) == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
wildmidi_finish(void)
|
||||
{
|
||||
WildMidi_Shutdown();
|
||||
}
|
||||
|
||||
static void
|
||||
wildmidi_file_decode(Decoder &decoder, const char *path_fs)
|
||||
{
|
||||
static constexpr AudioFormat audio_format = {
|
||||
WILDMIDI_SAMPLE_RATE,
|
||||
SampleFormat::S16,
|
||||
2,
|
||||
};
|
||||
midi *wm;
|
||||
const struct _WM_Info *info;
|
||||
|
||||
wm = WildMidi_Open(path_fs);
|
||||
if (wm == nullptr)
|
||||
return;
|
||||
|
||||
info = WildMidi_GetInfo(wm);
|
||||
if (info == nullptr) {
|
||||
WildMidi_Close(wm);
|
||||
return;
|
||||
}
|
||||
|
||||
decoder_initialized(decoder, audio_format, true,
|
||||
info->approx_total_samples / WILDMIDI_SAMPLE_RATE);
|
||||
|
||||
DecoderCommand cmd;
|
||||
do {
|
||||
char buffer[4096];
|
||||
int len;
|
||||
|
||||
info = WildMidi_GetInfo(wm);
|
||||
if (info == nullptr)
|
||||
break;
|
||||
|
||||
len = WildMidi_GetOutput(wm, buffer, sizeof(buffer));
|
||||
if (len <= 0)
|
||||
break;
|
||||
|
||||
cmd = decoder_data(decoder, nullptr, buffer, len, 0);
|
||||
|
||||
if (cmd == DecoderCommand::SEEK) {
|
||||
unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
|
||||
decoder_seek_where(decoder);
|
||||
|
||||
WildMidi_FastSeek(wm, &seek_where);
|
||||
decoder_command_finished(decoder);
|
||||
cmd = DecoderCommand::NONE;
|
||||
}
|
||||
|
||||
} while (cmd == DecoderCommand::NONE);
|
||||
|
||||
WildMidi_Close(wm);
|
||||
}
|
||||
|
||||
static bool
|
||||
wildmidi_scan_file(const char *path_fs,
|
||||
const struct tag_handler *handler, void *handler_ctx)
|
||||
{
|
||||
midi *wm = WildMidi_Open(path_fs);
|
||||
if (wm == nullptr)
|
||||
return false;
|
||||
|
||||
const struct _WM_Info *info = WildMidi_GetInfo(wm);
|
||||
if (info == nullptr) {
|
||||
WildMidi_Close(wm);
|
||||
return false;
|
||||
}
|
||||
|
||||
int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE;
|
||||
tag_handler_invoke_duration(handler, handler_ctx, duration);
|
||||
|
||||
WildMidi_Close(wm);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *const wildmidi_suffixes[] = {
|
||||
"mid",
|
||||
nullptr
|
||||
};
|
||||
|
||||
const struct DecoderPlugin wildmidi_decoder_plugin = {
|
||||
"wildmidi",
|
||||
wildmidi_init,
|
||||
wildmidi_finish,
|
||||
nullptr,
|
||||
wildmidi_file_decode,
|
||||
wildmidi_scan_file,
|
||||
nullptr,
|
||||
nullptr,
|
||||
wildmidi_suffixes,
|
||||
nullptr,
|
||||
};
|
25
src/decoder/plugins/WildmidiDecoderPlugin.hxx
Normal file
25
src/decoder/plugins/WildmidiDecoderPlugin.hxx
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_DECODER_WILDMIDI_HXX
|
||||
#define MPD_DECODER_WILDMIDI_HXX
|
||||
|
||||
extern const struct DecoderPlugin wildmidi_decoder_plugin;
|
||||
|
||||
#endif
|
28
src/decoder/plugins/XiphTags.cxx
Normal file
28
src/decoder/plugins/XiphTags.cxx
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "XiphTags.hxx"
|
||||
|
||||
const struct tag_table xiph_tags[] = {
|
||||
{ "tracknumber", TAG_TRACK },
|
||||
{ "discnumber", TAG_DISC },
|
||||
{ "album artist", TAG_ALBUM_ARTIST },
|
||||
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
|
||||
};
|
28
src/decoder/plugins/XiphTags.hxx
Normal file
28
src/decoder/plugins/XiphTags.hxx
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_XIPH_TAGS_HXX
|
||||
#define MPD_XIPH_TAGS_HXX
|
||||
|
||||
#include "check.h"
|
||||
#include "tag/TagTable.hxx"
|
||||
|
||||
extern const struct tag_table xiph_tags[];
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user