aboutsummaryrefslogtreecommitdiffstats
path: root/src/decoder
diff options
context:
space:
mode:
Diffstat (limited to 'src/decoder')
-rw-r--r--src/decoder/DecoderList.cxx26
-rw-r--r--src/decoder/plugins/DsdLib.cxx4
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.cxx6
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.cxx8
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.cxx597
-rw-r--r--src/decoder/plugins/FfmpegIo.cxx98
-rw-r--r--src/decoder/plugins/FfmpegIo.hxx57
-rw-r--r--src/decoder/plugins/FfmpegMetaData.cxx38
-rw-r--r--src/decoder/plugins/FfmpegMetaData.hxx14
-rw-r--r--src/decoder/plugins/FlacMetadata.cxx4
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.cxx4
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.cxx188
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.cxx22
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.cxx180
-rw-r--r--src/decoder/plugins/VorbisComments.cxx4
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.cxx6
-rw-r--r--src/decoder/plugins/XiphTags.cxx1
17 files changed, 650 insertions, 607 deletions
diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx
index cd6881ce2..cd1665e58 100644
--- a/src/decoder/DecoderList.cxx
+++ b/src/decoder/DecoderList.cxx
@@ -48,44 +48,42 @@
#include <string.h>
const struct DecoderPlugin *const decoder_plugins[] = {
-#ifdef HAVE_MAD
+#ifdef ENABLE_MAD
&mad_decoder_plugin,
#endif
-#ifdef HAVE_MPG123
+#ifdef ENABLE_MPG123
&mpg123_decoder_plugin,
#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
-#if defined(HAVE_FLAC)
+#ifdef ENABLE_FLAC
&oggflac_decoder_plugin,
-#endif
-#ifdef HAVE_FLAC
&flac_decoder_plugin,
#endif
-#ifdef HAVE_OPUS
+#ifdef ENABLE_OPUS
&opus_decoder_plugin,
#endif
#ifdef ENABLE_SNDFILE
&sndfile_decoder_plugin,
#endif
-#ifdef HAVE_AUDIOFILE
+#ifdef ENABLE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
#ifdef ENABLE_DSD
&dsdiff_decoder_plugin,
&dsf_decoder_plugin,
#endif
-#ifdef HAVE_FAAD
+#ifdef ENABLE_FAAD
&faad_decoder_plugin,
#endif
-#ifdef HAVE_MPCDEC
+#ifdef ENABLE_MPCDEC
&mpcdec_decoder_plugin,
#endif
-#ifdef HAVE_WAVPACK
+#ifdef ENABLE_WAVPACK
&wavpack_decoder_plugin,
#endif
-#ifdef HAVE_MODPLUG
+#ifdef ENABLE_MODPLUG
&modplug_decoder_plugin,
#endif
#ifdef ENABLE_MIKMOD_DECODER
@@ -100,13 +98,13 @@ const struct DecoderPlugin *const decoder_plugins[] = {
#ifdef ENABLE_FLUIDSYNTH
&fluidsynth_decoder_plugin,
#endif
-#ifdef HAVE_ADPLUG
+#ifdef ENABLE_ADPLUG
&adplug_decoder_plugin,
#endif
-#ifdef HAVE_FFMPEG
+#ifdef ENABLE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
-#ifdef HAVE_GME
+#ifdef ENABLE_GME
&gme_decoder_plugin,
#endif
&pcm_decoder_plugin,
diff --git a/src/decoder/plugins/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx
index 7321261f6..9da08f3eb 100644
--- a/src/decoder/plugins/DsdLib.cxx
+++ b/src/decoder/plugins/DsdLib.cxx
@@ -34,7 +34,7 @@
#include <string.h>
#include <stdlib.h>
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
#include <id3tag.h>
#endif
@@ -103,7 +103,7 @@ dsdlib_valid_freq(uint32_t samplefreq)
}
}
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
void
dsdlib_tag_id3(InputStream &is,
const struct tag_handler *handler,
diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
index b6c79e11e..33f433330 100644
--- a/src/decoder/plugins/DsdiffDecoderPlugin.cxx
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
@@ -244,7 +244,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
/** offset for title tag */
offset_type title_offset = 0;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
offset_type id3_offset = 0;
#endif
@@ -269,7 +269,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
chunk_size = chunk_header->GetSize();
title_offset = is.GetOffset();
}
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
/* 'ID3 ' chunk, offspec. Used by sacdextract */
if (chunk_header->id.Equals("ID3 ")) {
chunk_size = chunk_header->GetSize();
@@ -283,7 +283,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
/* done processing chunk headers, process tags if any */
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
if (id3_offset != 0) {
/* a ID3 tag has preference over the other tags, do not process
other tags if we have one */
diff --git a/src/decoder/plugins/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx
index 690616d15..b8ae837f7 100644
--- a/src/decoder/plugins/DsfDecoderPlugin.cxx
+++ b/src/decoder/plugins/DsfDecoderPlugin.cxx
@@ -47,7 +47,7 @@ struct DsfMetaData {
unsigned sample_rate, channels;
bool bitreverse;
offset_type n_blocks;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
offset_type id3_offset;
#endif
};
@@ -111,7 +111,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
if (sizeof(dsf_header) != chunk_size)
return false;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
const offset_type metadata_offset = dsf_header.pmeta.Read();
#endif
@@ -174,7 +174,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
metadata->n_blocks = data_size / block_size;
metadata->channels = channels;
metadata->sample_rate = samplefreq;
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
metadata->id3_offset = metadata_offset;
#endif
/* check bits per sample format, determine if bitreverse is needed */
@@ -352,7 +352,7 @@ dsf_scan_stream(InputStream &is,
audio_format.sample_rate);
tag_handler_invoke_duration(handler, handler_ctx, songtime);
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
/* Add available tags from the ID3 tag */
dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset);
#endif
diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
index 722f954e2..85e852fb4 100644
--- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
@@ -20,14 +20,24 @@
/* necessary because libavutil/common.h uses UINT64_C */
#define __STDC_CONSTANT_MACROS
+#include "lib/ffmpeg/Time.hxx"
#include "config.h"
#include "FfmpegDecoderPlugin.hxx"
#include "lib/ffmpeg/Domain.hxx"
+#include "lib/ffmpeg/Error.hxx"
+#include "lib/ffmpeg/LogError.hxx"
+#include "lib/ffmpeg/Init.hxx"
+#include "lib/ffmpeg/Buffer.hxx"
#include "../DecoderAPI.hxx"
#include "FfmpegMetaData.hxx"
+#include "FfmpegIo.hxx"
+#include "tag/TagBuilder.hxx"
#include "tag/TagHandler.hxx"
+#include "tag/ReplayGain.hxx"
+#include "tag/MixRamp.hxx"
#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "LogV.hxx"
@@ -38,7 +48,6 @@ extern "C" {
#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>
@@ -48,205 +57,50 @@ extern "C" {
#include <assert.h>
#include <string.h>
-/* 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;
-
- switch (whence) {
- case SEEK_SET:
- break;
-
- case SEEK_CUR:
- pos += stream->input.GetOffset();
- break;
-
- case SEEK_END:
- if (!stream->input.KnownSize())
- return -1;
-
- pos += stream->input.GetSize();
- break;
-
- case AVSEEK_SIZE:
- if (!stream->input.KnownSize())
- return -1;
-
- return stream->input.GetSize();
-
- default:
- return -1;
- }
-
- if (!stream->input.LockSeek(pos, IgnoreError()))
- return -1;
-
- return stream->input.GetOffset();
-}
-
-bool
-AvioStream::Open()
-{
- io = avio_alloc_context(buffer, sizeof(buffer),
- false, this,
- mpd_ffmpeg_stream_read, nullptr,
- input.IsSeekable()
- ? 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)
+static AVFormatContext *
+FfmpegOpenInput(AVIOContext *pb,
+ const char *filename,
+ AVInputFormat *fmt)
{
AVFormatContext *context = avformat_alloc_context();
if (context == nullptr)
- return AVERROR(ENOMEM);
+ return nullptr;
context->pb = pb;
- *ic_ptr = context;
- return avformat_open_input(ic_ptr, filename, fmt, nullptr);
+
+ avformat_open_input(&context, filename, fmt, nullptr);
+ return context;
}
static bool
ffmpeg_init(gcc_unused const config_param &param)
{
- av_log_set_callback(mpd_ffmpeg_log_callback);
-
- av_register_all();
+ FfmpegInit();
return true;
}
+gcc_pure
static int
-ffmpeg_find_audio_stream(const AVFormatContext *format_context)
+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 ==
+ 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;
-}
-
-template<typename Ratio>
-static constexpr AVRational
-RatioToAVRational()
-{
- return { Ratio::num, Ratio::den };
-}
-
-gcc_const
-static int64_t
-time_to_ffmpeg(SongTime t, const AVRational time_base)
-{
- return av_rescale_q(t.count(),
- RatioToAVRational<SongTime::period>(),
- time_base);
-}
-
-/**
- * Replace #AV_NOPTS_VALUE with the given fallback.
- */
-static constexpr int64_t
-timestamp_fallback(int64_t t, int64_t fallback)
-{
- return gcc_likely(t != int64_t(AV_NOPTS_VALUE))
- ? t
- : fallback;
-}
-
/**
* Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with
* zero. We can't use AV_NOPTS_VALUE in calculations, and we simply
* assume that the stream's start time is zero, which appears to be
* the best way out of that situation.
*/
-static int64_t
+static constexpr int64_t
start_time_fallback(const AVStream &stream)
{
- return timestamp_fallback(stream.start_time, 0);
+ return FfmpegTimestampFallback(stream.start_time, 0);
}
static void
@@ -264,99 +118,103 @@ copy_interleave_frame2(uint8_t *dest, uint8_t **src,
}
/**
- * Copy PCM data from a AVFrame to an interleaved buffer.
+ * Copy PCM data from a non-empty 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)
+static ConstBuffer<void>
+copy_interleave_frame(const AVCodecContext &codec_context,
+ const AVFrame &frame,
+ FfmpegBuffer &global_buffer,
+ Error &error)
{
+ assert(frame.nb_samples > 0);
+
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;
+ codec_context.channels,
+ frame.nb_samples,
+ codec_context.sample_fmt, 1);
+ assert(data_size != 0);
+ if (data_size < 0) {
+ SetFfmpegError(error, data_size);
+ return 0;
+ }
+
+ void *output_buffer;
+ if (av_sample_fmt_is_planar(codec_context.sample_fmt) &&
+ codec_context.channels > 1) {
+ output_buffer = global_buffer.GetT<uint8_t>(data_size);
+ if (output_buffer == nullptr) {
+ /* Not enough memory - shouldn't happen */
+ error.SetErrno(ENOMEM);
+ return 0;
}
- *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));
+
+ copy_interleave_frame2((uint8_t *)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];
+ output_buffer = frame.extended_data[0];
}
- return data_size;
+ return { output_buffer, (size_t)data_size };
}
+/**
+ * Decode an #AVPacket and send the resulting PCM data to the decoder
+ * API.
+ */
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)
+ AVPacket packet,
+ AVCodecContext &codec_context,
+ const AVStream &stream,
+ AVFrame &frame,
+ FfmpegBuffer &buffer)
{
- if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) {
- auto start = start_time_fallback(*stream);
- if (packet->pts >= start)
+ if (packet.pts >= 0 && packet.pts != (int64_t)AV_NOPTS_VALUE) {
+ auto start = start_time_fallback(stream);
+ if (packet.pts >= start)
decoder_timestamp(decoder,
- time_from_ffmpeg(packet->pts - start,
- stream->time_base));
+ FfmpegTimeToDouble(packet.pts - start,
+ stream.time_base));
}
- AVPacket packet2 = *packet;
-
- uint8_t *output_buffer;
+ Error error;
DecoderCommand cmd = DecoderCommand::NONE;
- while (packet2.size > 0 && cmd == DecoderCommand::NONE) {
- int audio_size = 0;
+ while (packet.size > 0 && cmd == DecoderCommand::NONE) {
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;
- }
-
+ int len = avcodec_decode_audio4(&codec_context,
+ &frame, &got_frame,
+ &packet);
if (len < 0) {
/* if error, we skip the frame */
- LogDefault(ffmpeg_domain,
- "decoding failed, frame skipped");
+ LogFfmpegError(len, "decoding failed, frame skipped");
break;
}
- packet2.data += len;
- packet2.size -= len;
+ packet.data += len;
+ packet.size -= len;
- if (audio_size <= 0)
+ if (!got_frame || frame.nb_samples <= 0)
continue;
+ auto output_buffer =
+ copy_interleave_frame(codec_context, frame,
+ buffer, error);
+ if (output_buffer.IsNull()) {
+ /* this must be a serious error,
+ e.g. OOM */
+ LogError(error);
+ return DecoderCommand::STOP;
+ }
+
cmd = decoder_data(decoder, is,
- output_buffer, audio_size,
- codec_context->bit_rate / 1000);
+ output_buffer.data, output_buffer.size,
+ codec_context.bit_rate / 1000);
}
return cmd;
}
@@ -399,10 +257,8 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
static AVInputFormat *
ffmpeg_probe(Decoder *decoder, InputStream &is)
{
- enum {
- BUFFER_SIZE = 16384,
- PADDING = 16,
- };
+ constexpr size_t BUFFER_SIZE = 16384;
+ constexpr size_t PADDING = 16;
unsigned char buffer[BUFFER_SIZE];
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
@@ -442,85 +298,163 @@ ffmpeg_probe(Decoder *decoder, InputStream &is)
}
static void
-ffmpeg_decode(Decoder &decoder, InputStream &input)
+FfmpegParseMetaData(AVDictionary &dict, ReplayGainInfo &rg, MixRampInfo &mr)
{
- AVInputFormat *input_format = ffmpeg_probe(&decoder, input);
- if (input_format == nullptr)
- return;
+ AVDictionaryEntry *i = nullptr;
- FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)",
- input_format->name, input_format->long_name);
+ while ((i = av_dict_get(&dict, "", i,
+ AV_DICT_IGNORE_SUFFIX)) != nullptr) {
+ const char *name = i->key;
+ const char *value = i->value;
- AvioStream stream(&decoder, input);
- if (!stream.Open()) {
- LogError(ffmpeg_domain, "Failed to open stream");
- return;
+ if (!ParseReplayGainTag(rg, name, value))
+ ParseMixRampTag(mr, name, value);
}
+}
- //ffmpeg works with ours "fileops" helper
- AVFormatContext *format_context = nullptr;
- if (mpd_ffmpeg_open_input(&format_context, stream.io,
- input.GetURI(),
- input_format) != 0) {
- LogError(ffmpeg_domain, "Open failed");
+static void
+FfmpegParseMetaData(const AVStream &stream,
+ ReplayGainInfo &rg, MixRampInfo &mr)
+{
+ FfmpegParseMetaData(*stream.metadata, rg, mr);
+}
+
+static void
+FfmpegParseMetaData(const AVFormatContext &format_context, int audio_stream,
+ ReplayGainInfo &rg, MixRampInfo &mr)
+{
+ assert(audio_stream >= 0);
+
+ FfmpegParseMetaData(*format_context.metadata, rg, mr);
+ FfmpegParseMetaData(*format_context.streams[audio_stream],
+ rg, mr);
+}
+
+static void
+FfmpegParseMetaData(Decoder &decoder,
+ const AVFormatContext &format_context, int audio_stream)
+{
+ ReplayGainInfo rg;
+ rg.Clear();
+
+ MixRampInfo mr;
+ mr.Clear();
+
+ FfmpegParseMetaData(format_context, audio_stream, rg, mr);
+
+ if (rg.IsDefined())
+ decoder_replay_gain(decoder, &rg);
+
+ if (mr.IsDefined())
+ decoder_mixramp(decoder, std::move(mr));
+}
+
+static void
+FfmpegScanMetadata(const AVStream &stream,
+ const tag_handler &handler, void *handler_ctx)
+{
+ FfmpegScanDictionary(stream.metadata, &handler, handler_ctx);
+}
+
+static void
+FfmpegScanMetadata(const AVFormatContext &format_context, int audio_stream,
+ const tag_handler &handler, void *handler_ctx)
+{
+ assert(audio_stream >= 0);
+
+ FfmpegScanDictionary(format_context.metadata, &handler, handler_ctx);
+ FfmpegScanMetadata(*format_context.streams[audio_stream],
+ handler, handler_ctx);
+}
+
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56, 1, 0)
+
+static void
+FfmpegScanTag(const AVFormatContext &format_context, int audio_stream,
+ TagBuilder &tag)
+{
+ FfmpegScanMetadata(format_context, audio_stream,
+ full_tag_handler, &tag);
+}
+
+/**
+ * Check if a new stream tag was received and pass it to
+ * decoder_tag().
+ */
+static void
+FfmpegCheckTag(Decoder &decoder, InputStream &is,
+ AVFormatContext &format_context, int audio_stream)
+{
+ AVStream &stream = *format_context.streams[audio_stream];
+ if ((stream.event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) == 0)
+ /* no new metadata */
return;
- }
+ /* clear the flag */
+ stream.event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
+
+ TagBuilder tag;
+ FfmpegScanTag(format_context, audio_stream, tag);
+ if (!tag.IsEmpty())
+ decoder_tag(decoder, is, tag.Commit());
+}
+
+#endif
+
+static void
+FfmpegDecode(Decoder &decoder, InputStream &input,
+ AVFormatContext &format_context)
+{
const int find_result =
- avformat_find_stream_info(format_context, nullptr);
+ 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];
+ AVStream &av_stream = *format_context.streams[audio_stream];
- AVCodecContext *codec_context = av_stream->codec;
+ AVCodecContext &codec_context = *av_stream.codec;
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0)
const AVCodecDescriptor *codec_descriptor =
- avcodec_descriptor_get(codec_context->codec_id);
+ avcodec_descriptor_get(codec_context.codec_id);
if (codec_descriptor != nullptr)
FormatDebug(ffmpeg_domain, "codec '%s'",
codec_descriptor->name);
#else
- if (codec_context->codec_name[0] != 0)
+ if (codec_context.codec_name[0] != 0)
FormatDebug(ffmpeg_domain, "codec '%s'",
- codec_context->codec_name);
+ codec_context.codec_name);
#endif
- AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
+ 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);
+ ffmpeg_sample_format(codec_context.sample_fmt);
if (sample_format == SampleFormat::UNDEFINED) {
// (error message already done by ffmpeg_sample_format())
- avformat_close_input(&format_context);
return;
}
Error error;
AudioFormat audio_format;
if (!audio_format_init_checked(audio_format,
- codec_context->sample_rate,
+ codec_context.sample_rate,
sample_format,
- codec_context->channels, error)) {
+ codec_context.channels, error)) {
LogError(error);
- avformat_close_input(&format_context);
return;
}
@@ -529,22 +463,20 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
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);
+ 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;
}
const SignedSongTime total_time =
- format_context->duration != (int64_t)AV_NOPTS_VALUE
- ? SignedSongTime::FromScale<uint64_t>(format_context->duration,
- AV_TIME_BASE)
- : SignedSongTime::Negative();
+ FromFfmpegTimeChecked(av_stream.duration, av_stream.time_base);
decoder_initialized(decoder, audio_format,
input.IsSeekable(), total_time);
+ FfmpegParseMetaData(decoder, format_context, audio_stream);
+
#if LIBAVUTIL_VERSION_MAJOR >= 53
AVFrame *frame = av_frame_alloc();
#else
@@ -552,26 +484,28 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
#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;
+ FfmpegBuffer interleaved_buffer;
DecoderCommand cmd;
do {
AVPacket packet;
- if (av_read_frame(format_context, &packet) < 0)
+ if (av_read_frame(&format_context, &packet) < 0)
/* end of file */
break;
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56, 1, 0)
+ FfmpegCheckTag(decoder, input, format_context, audio_stream);
+#endif
+
if (packet.stream_index == audio_stream)
cmd = ffmpeg_send_packet(decoder, input,
- &packet, codec_context,
+ packet, codec_context,
av_stream,
- frame,
- &interleaved_buffer, &interleaved_buffer_size);
+ *frame,
+ interleaved_buffer);
else
cmd = decoder_get_command(decoder);
@@ -579,15 +513,15 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
if (cmd == DecoderCommand::SEEK) {
int64_t where =
- time_to_ffmpeg(decoder_seek_time(decoder),
- av_stream->time_base) +
- start_time_fallback(*av_stream);
+ ToFfmpegTime(decoder_seek_time(decoder),
+ av_stream.time_base) +
+ start_time_fallback(av_stream);
- if (av_seek_frame(format_context, audio_stream, where,
+ if (av_seek_frame(&format_context, audio_stream, where,
AVSEEK_FLAG_ANY) < 0)
decoder_seek_error(decoder);
else {
- avcodec_flush_buffers(codec_context);
+ avcodec_flush_buffers(&codec_context);
decoder_command_finished(decoder);
}
}
@@ -598,15 +532,63 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0)
avcodec_free_frame(&frame);
#else
- av_freep(&frame);
+ av_free(frame);
#endif
- av_freep(&interleaved_buffer);
- avcodec_close(codec_context);
+ avcodec_close(&codec_context);
+}
+
+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;
+ }
+
+ AVFormatContext *format_context =
+ FfmpegOpenInput(stream.io, input.GetURI(), input_format);
+ if (format_context == nullptr) {
+ LogError(ffmpeg_domain, "Open failed");
+ return;
+ }
+
+ FfmpegDecode(decoder, input, *format_context);
avformat_close_input(&format_context);
}
-//no tag reading in ffmpeg, check if playable
+static bool
+FfmpegScanStream(AVFormatContext &format_context,
+ const struct tag_handler &handler, void *handler_ctx)
+{
+ const int find_result =
+ avformat_find_stream_info(&format_context, nullptr);
+ if (find_result < 0)
+ return false;
+
+ const int audio_stream = ffmpeg_find_audio_stream(format_context);
+ if (audio_stream < 0)
+ return false;
+
+ const AVStream &stream = *format_context.streams[audio_stream];
+ if (stream.duration != (int64_t)AV_NOPTS_VALUE)
+ tag_handler_invoke_duration(&handler, handler_ctx,
+ FromFfmpegTime(stream.duration,
+ stream.time_base));
+
+ FfmpegScanMetadata(format_context, audio_stream, handler, handler_ctx);
+
+ return true;
+}
+
static bool
ffmpeg_scan_stream(InputStream &is,
const struct tag_handler *handler, void *handler_ctx)
@@ -619,33 +601,14 @@ ffmpeg_scan_stream(InputStream &is,
if (!stream.Open())
return false;
- AVFormatContext *f = nullptr;
- if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(),
- input_format) != 0)
+ AVFormatContext *f =
+ FfmpegOpenInput(stream.io, is.GetURI(), input_format);
+ if (f == nullptr)
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) {
- const auto duration =
- SongTime::FromScale<uint64_t>(f->duration,
- AV_TIME_BASE);
- tag_handler_invoke_duration(handler, handler_ctx, duration);
- }
-
- 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);
-
+ bool result = FfmpegScanStream(*f, *handler, handler_ctx);
avformat_close_input(&f);
- return true;
+ return result;
}
/**
diff --git a/src/decoder/plugins/FfmpegIo.cxx b/src/decoder/plugins/FfmpegIo.cxx
new file mode 100644
index 000000000..d47e575bc
--- /dev/null
+++ b/src/decoder/plugins/FfmpegIo.cxx
@@ -0,0 +1,98 @@
+/*
+ * 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 "FfmpegIo.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "util/Error.hxx"
+
+AvioStream::~AvioStream()
+{
+ av_free(io);
+}
+
+inline int
+AvioStream::Read(void *dest, int size)
+{
+ return decoder_read(decoder, input, dest, size);
+}
+
+inline int64_t
+AvioStream::Seek(int64_t pos, int whence)
+{
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ pos += input.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!input.KnownSize())
+ return -1;
+
+ pos += input.GetSize();
+ break;
+
+ case AVSEEK_SIZE:
+ if (!input.KnownSize())
+ return -1;
+
+ return input.GetSize();
+
+ default:
+ return -1;
+ }
+
+ if (!input.LockSeek(pos, IgnoreError()))
+ return -1;
+
+ return input.GetOffset();
+}
+
+int
+AvioStream::_Read(void *opaque, uint8_t *buf, int size)
+{
+ AvioStream &stream = *(AvioStream *)opaque;
+
+ return stream.Read(buf, size);
+}
+
+int64_t
+AvioStream::_Seek(void *opaque, int64_t pos, int whence)
+{
+ AvioStream &stream = *(AvioStream *)opaque;
+
+ return stream.Seek(pos, whence);
+}
+
+bool
+AvioStream::Open()
+{
+ io = avio_alloc_context(buffer, sizeof(buffer),
+ false, this,
+ _Read, nullptr,
+ input.IsSeekable() ? _Seek : nullptr);
+ return io != nullptr;
+}
diff --git a/src/decoder/plugins/FfmpegIo.hxx b/src/decoder/plugins/FfmpegIo.hxx
new file mode 100644
index 000000000..bbd3a5b62
--- /dev/null
+++ b/src/decoder/plugins/FfmpegIo.hxx
@@ -0,0 +1,57 @@
+/*
+ * 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_IO_HXX
+#define MPD_FFMPEG_IO_HXX
+
+#include "check.h"
+
+extern "C" {
+#include "libavformat/avio.h"
+}
+
+#include <stdint.h>
+
+class InputStream;
+struct Decoder;
+
+struct AvioStream {
+ Decoder *const decoder;
+ InputStream &input;
+
+ AVIOContext *io;
+
+ uint8_t buffer[8192];
+
+ AvioStream(Decoder *_decoder, InputStream &_input)
+ :decoder(_decoder), input(_input), io(nullptr) {}
+
+ ~AvioStream();
+
+ bool Open();
+
+private:
+ int Read(void *buffer, int size);
+ int64_t Seek(int64_t pos, int whence);
+
+ static int _Read(void *opaque, uint8_t *buf, int size);
+ static int64_t _Seek(void *opaque, int64_t pos, int whence);
+};
+
+#endif
diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx
index a39466945..b8f7a12a3 100644
--- a/src/decoder/plugins/FfmpegMetaData.cxx
+++ b/src/decoder/plugins/FfmpegMetaData.cxx
@@ -25,7 +25,11 @@
#include "tag/TagTable.hxx"
#include "tag/TagHandler.hxx"
-static const struct tag_table ffmpeg_tags[] = {
+extern "C" {
+#include <libavutil/dict.h>
+}
+
+static constexpr struct tag_table ffmpeg_tags[] = {
{ "year", TAG_DATE },
{ "author-sort", TAG_ARTIST_SORT },
{ "album_artist", TAG_ALBUM_ARTIST },
@@ -36,9 +40,9 @@ static const struct tag_table ffmpeg_tags[] = {
};
static void
-ffmpeg_copy_metadata(TagType type,
- AVDictionary *m, const char *name,
- const struct tag_handler *handler, void *handler_ctx)
+FfmpegScanTag(TagType type,
+ AVDictionary *m, const char *name,
+ const struct tag_handler *handler, void *handler_ctx)
{
AVDictionaryEntry *mt = nullptr;
@@ -48,8 +52,8 @@ ffmpeg_copy_metadata(TagType type,
}
static void
-ffmpeg_scan_pairs(AVDictionary *dict,
- const struct tag_handler *handler, void *handler_ctx)
+FfmpegScanPairs(AVDictionary *dict,
+ const struct tag_handler *handler, void *handler_ctx)
{
AVDictionaryEntry *i = nullptr;
@@ -59,18 +63,20 @@ ffmpeg_scan_pairs(AVDictionary *dict,
}
void
-ffmpeg_scan_dictionary(AVDictionary *dict,
- const struct tag_handler *handler, void *handler_ctx)
+FfmpegScanDictionary(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);
+ if (handler->tag != nullptr) {
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ FfmpegScanTag(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);
+ for (const struct tag_table *i = ffmpeg_tags;
+ i->name != nullptr; ++i)
+ FfmpegScanTag(i->type, dict, i->name,
+ handler, handler_ctx);
+ }
if (handler->pair != nullptr)
- ffmpeg_scan_pairs(dict, handler, handler_ctx);
+ FfmpegScanPairs(dict, handler, handler_ctx);
}
diff --git a/src/decoder/plugins/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx
index 5eb41db68..4b77adf5d 100644
--- a/src/decoder/plugins/FfmpegMetaData.hxx
+++ b/src/decoder/plugins/FfmpegMetaData.hxx
@@ -20,19 +20,11 @@
#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 AVDictionary;
struct tag_handler;
void
-ffmpeg_scan_dictionary(AVDictionary *dict,
- const tag_handler *handler, void *handler_ctx);
+FfmpegScanDictionary(AVDictionary *dict,
+ const tag_handler *handler, void *handler_ctx);
#endif
diff --git a/src/decoder/plugins/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx
index 03e276dce..86c6da04b 100644
--- a/src/decoder/plugins/FlacMetadata.cxx
+++ b/src/decoder/plugins/FlacMetadata.cxx
@@ -30,7 +30,7 @@
#include "tag/MixRamp.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
-#include "util/SplitString.hxx"
+#include "util/DivideString.hxx"
bool
flac_parse_replay_gain(ReplayGainInfo &rgi,
@@ -97,7 +97,7 @@ flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
{
if (handler->pair != nullptr) {
const char *comment = (const char *)entry->entry;
- const SplitString split(comment, '=');
+ const DivideString split(comment, '=');
if (split.IsDefined() && !split.IsEmpty())
tag_handler_invoke_pair(handler, handler_ctx,
split.GetFirst(),
diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
index f19ac5bf4..55247549b 100644
--- a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
@@ -35,7 +35,7 @@ static unsigned sample_rate;
static const char *soundfont_path;
/**
- * Convert a fluidsynth log level to a GLib log level.
+ * Convert a fluidsynth log level to a MPD log level.
*/
static LogLevel
fluidsynth_level_to_mpd(enum fluid_log_level level)
@@ -61,7 +61,7 @@ fluidsynth_level_to_mpd(enum fluid_log_level level)
}
/**
- * The fluidsynth logging callback. It forwards messages to the GLib
+ * The fluidsynth logging callback. It forwards messages to the MPD
* logging library.
*/
static void
diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx
index cc6ce5e5d..8d3116ae1 100644
--- a/src/decoder/plugins/GmeDecoderPlugin.cxx
+++ b/src/decoder/plugins/GmeDecoderPlugin.cxx
@@ -23,6 +23,7 @@
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "fs/Path.hxx"
+#include "fs/AllocatedPath.hxx"
#include "util/Alloc.hxx"
#include "util/FormatString.hxx"
#include "util/UriUtil.hxx"
@@ -30,7 +31,6 @@
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
@@ -47,65 +47,42 @@ 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(Path path_fs)
+struct GmeContainerPath {
+ AllocatedPath path;
+ unsigned track;
+};
+
+gcc_pure
+static unsigned
+ParseSubtuneName(const char *base)
{
- const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
- char *path_container = xstrdup(path_fs.c_str());
-
- 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;
- }
+ if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
+ return 0;
- char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX);
- if (ptr != nullptr)
- *ptr='\0';
+ base += sizeof(SUBTUNE_PREFIX) - 1;
- g_pattern_spec_free(path_with_subtune);
- return path_container;
+ char *endptr;
+ auto track = strtoul(base, &endptr, 10);
+ if (endptr == base || *endptr != '.')
+ return 0;
+
+ return track;
}
/**
- * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune
- * is appended.
+ * returns the file path stripped of any /tune_xxx.* subtune suffix
+ * and the track number (or 0 if no "tune_xxx" suffix is present).
*/
-static int
-get_song_num(Path path_fs)
+static GmeContainerPath
+ParseContainerPath(Path path_fs)
{
- const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
+ const Path base = path_fs.GetBase();
+ unsigned track;
+ if (base.IsNull() ||
+ (track = ParseSubtuneName(base.c_str())) < 1)
+ return { AllocatedPath(path_fs), 0 };
- 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,
- path_fs.length(), path_fs.data(), nullptr)) {
- char *sub = g_strrstr(path_fs.c_str(), "/" 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;
- }
+ return { path_fs.GetDirectoryName(), track - 1 };
}
static char *
@@ -136,20 +113,18 @@ gme_container_scan(Path path_fs, const unsigned int tnum)
static void
gme_file_decode(Decoder &decoder, Path path_fs)
{
- char *path_container = get_container_name(path_fs);
+ const auto container = ParseContainerPath(path_fs);
Music_Emu *emu;
const char *gme_err =
- gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
- free(path_container);
+ gme_open_file(container.path.c_str(), &emu, GME_SAMPLE_RATE);
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);
+ gme_err = gme_track_info(emu, &ti, container.track);
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
gme_delete(emu);
@@ -175,7 +150,7 @@ gme_file_decode(Decoder &decoder, Path path_fs)
decoder_initialized(decoder, audio_format, true, song_len);
- gme_err = gme_start_track(emu, song_num);
+ gme_err = gme_start_track(emu, container.track);
if (gme_err != nullptr)
LogWarning(gme_domain, gme_err);
@@ -209,72 +184,85 @@ gme_file_decode(Decoder &decoder, Path path_fs)
gme_delete(emu);
}
-static bool
-gme_scan_file(Path path_fs,
- const struct tag_handler *handler, void *handler_ctx)
+static void
+ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
+ 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);
- 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)
+ if (info.length > 0)
tag_handler_invoke_duration(handler, handler_ctx,
- SongTime::FromMS(ti->length));
+ SongTime::FromMS(info.length));
- if (ti->song != nullptr) {
- if (gme_track_count(emu) > 1) {
+ if (info.song != nullptr) {
+ if (track_count > 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));
+ "%s (%u/%d)",
+ info.song, song_num + 1,
+ track_count);
tag_handler_invoke_tag(handler, handler_ctx,
TAG_TITLE, tag_title);
} else
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, ti->song);
+ TAG_TITLE, info.song);
}
- if (ti->author != nullptr)
+ if (info.author != nullptr)
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_ARTIST, ti->author);
+ TAG_ARTIST, info.author);
- if (ti->game != nullptr)
+ if (info.game != nullptr)
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_ALBUM, ti->game);
+ TAG_ALBUM, info.game);
- if (ti->comment != nullptr)
+ if (info.comment != nullptr)
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_COMMENT, ti->comment);
+ TAG_COMMENT, info.comment);
- if (ti->copyright != nullptr)
+ if (info.copyright != nullptr)
tag_handler_invoke_tag(handler, handler_ctx,
- TAG_DATE, ti->copyright);
+ TAG_DATE, info.copyright);
+}
+
+static bool
+ScanMusicEmu(Music_Emu *emu, unsigned song_num,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ gme_info_t *ti;
+ const char *gme_err = gme_track_info(emu, &ti, song_num);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ return false;
+ }
+
+ assert(ti != nullptr);
+
+ ScanGmeInfo(*ti, song_num, gme_track_count(emu),
+ handler, handler_ctx);
gme_free_info(ti);
+ return true;
+}
+
+static bool
+gme_scan_file(Path path_fs,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const auto container = ParseContainerPath(path_fs);
+
+ Music_Emu *emu;
+ const char *gme_err =
+ gme_open_file(container.path.c_str(), &emu, GME_SAMPLE_RATE);
+ if (gme_err != nullptr) {
+ LogWarning(gme_domain, gme_err);
+ return false;
+ }
+
+ const bool result = ScanMusicEmu(emu, container.track, handler, handler_ctx);
+
gme_delete(emu);
- return true;
+ return result;
}
static const char *const gme_suffixes[] = {
diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx
index de6c9b127..669b57817 100644
--- a/src/decoder/plugins/MadDecoderPlugin.cxx
+++ b/src/decoder/plugins/MadDecoderPlugin.cxx
@@ -36,7 +36,7 @@
#include <mad.h>
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
#include <id3tag.h>
#endif
@@ -251,7 +251,7 @@ MadDecoder::FillBuffer()
return true;
}
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
static bool
parse_id3_replay_gain_info(ReplayGainInfo &rgi,
struct id3_tag *tag)
@@ -285,7 +285,7 @@ parse_id3_replay_gain_info(ReplayGainInfo &rgi,
}
#endif
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
gcc_pure
static MixRampInfo
parse_id3_mixramp(struct id3_tag *tag)
@@ -317,7 +317,7 @@ parse_id3_mixramp(struct id3_tag *tag)
inline void
MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
{
-#ifdef HAVE_ID3TAG
+#ifdef ENABLE_ID3TAG
id3_byte_t *allocated = nullptr;
const id3_length_t count = stream.bufend - stream.this_frame;
@@ -369,7 +369,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
id3_tag_delete(id3_tag);
delete[] allocated;
-#else /* !HAVE_ID3TAG */
+#else /* !ENABLE_ID3TAG */
(void)mpd_tag;
/* This code is enabled when libid3tag is disabled. Instead
@@ -386,7 +386,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
#endif
}
-#ifndef HAVE_ID3TAG
+#ifndef ENABLE_ID3TAG
/**
* This function emulates libid3tag when it is disabled. Instead of
* doing a real analyzation of the frame, it just checks whether the
@@ -402,7 +402,7 @@ id3_tag_query(const void *p0, size_t length)
? (p[8] << 7) + p[9] + 10
: 0;
}
-#endif /* !HAVE_ID3TAG */
+#endif /* !ENABLE_ID3TAG */
static enum mp3_action
RecoverFrameError(struct mad_stream &stream)
@@ -504,10 +504,10 @@ struct xing {
enum xing_magic magic; /* header magic */
};
-static const unsigned XING_FRAMES = 1;
-static const unsigned XING_BYTES = 2;
-static const unsigned XING_TOC = 4;
-static const unsigned XING_SCALE = 8;
+static constexpr unsigned XING_FRAMES = 1;
+static constexpr unsigned XING_BYTES = 2;
+static constexpr unsigned XING_TOC = 4;
+static constexpr unsigned XING_SCALE = 8;
struct lame_version {
unsigned major;
diff --git a/src/decoder/plugins/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx
index 8435f095f..395945157 100644
--- a/src/decoder/plugins/SidplayDecoderPlugin.cxx
+++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx
@@ -22,67 +22,44 @@
#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
#include "fs/Path.hxx"
+#include "fs/AllocatedPath.hxx"
#include "util/FormatString.hxx"
#include "util/Domain.hxx"
+#include "util/Error.hxx"
#include "system/ByteOrder.hxx"
+#include "system/FatalError.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>
+#include <sidplay/utils/SidDatabase.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 SidDatabase *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)
+static SidDatabase *
+sidplay_load_songlength_db(const Path path)
{
- GError *error = nullptr;
- gchar *data;
- gsize size;
-
- if (!g_file_get_contents(path, &data, &size, &error)) {
+ SidDatabase *db = new SidDatabase();
+ if (db->open(path.c_str()) < 0) {
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);
+ path.c_str(), db->error());
+ delete db;
return nullptr;
}
- g_key_file_set_list_separator(db, ' ');
return db;
}
@@ -90,18 +67,18 @@ static bool
sidplay_init(const config_param &param)
{
/* read the songlengths database file */
- songlength_file = param.GetBlockValue("songlength_database");
- if (songlength_file != nullptr)
- songlength_database = sidplay_load_songlength_db(songlength_file);
+ Error error;
+ const auto database_path = param.GetBlockPath("songlength_database", error);
+ if (!database_path.IsNull())
+ songlength_database = sidplay_load_songlength_db(database_path);
+ else if (error.IsDefined())
+ FatalError(error);
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;
@@ -110,96 +87,61 @@ sidplay_init(const config_param &param)
static void
sidplay_finish()
{
- g_pattern_spec_free(path_with_subtune);
-
- if(songlength_database)
- g_key_file_free(songlength_database);
+ delete songlength_database;
}
-/**
- * returns the file path stripped of any /tune_xxx.sid subtune
- * suffix
- */
-static char *
-get_container_name(Path path_fs)
+struct SidplayContainerPath {
+ AllocatedPath path;
+ unsigned track;
+};
+
+gcc_pure
+static unsigned
+ParseSubtuneName(const char *base)
{
- char *path_container = strdup(path_fs.c_str());
+ if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
+ return 0;
- if(!g_pattern_match(path_with_subtune,
- strlen(path_container), path_container, nullptr))
- return path_container;
+ base += sizeof(SUBTUNE_PREFIX) - 1;
- char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
- if(ptr) *ptr='\0';
+ char *endptr;
+ auto track = strtoul(base, &endptr, 10);
+ if (endptr == base || *endptr != '.')
+ return 0;
- return path_container;
+ return track;
}
/**
- * returns tune number from file.sid/tune_xxx.sid style path or 1 if
- * no subtune is appended
+ * returns the file path stripped of any /tune_xxx.* subtune suffix
+ * and the track number (or 1 if no "tune_xxx" suffix is present).
*/
-static unsigned
-get_song_num(const char *path_fs)
+static SidplayContainerPath
+ParseContainerPath(Path 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;
+ const Path base = path_fs.GetBase();
+ unsigned track;
+ if (base.IsNull() ||
+ (track = ParseSubtuneName(base.c_str())) < 1)
+ return { AllocatedPath(path_fs), 1 };
+
+ return { path_fs.GetDirectoryName(), track };
}
/* get the song length in seconds */
static SignedSongTime
-get_song_length(Path path_fs)
+get_song_length(SidTuneMod &tune)
{
+ assert(tune);
+
if (songlength_database == nullptr)
return SignedSongTime::Negative();
- 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");
+ const auto length = songlength_database->length(tune);
+ if (length < 0)
return SignedSongTime::Negative();
- }
- char md5sum[SIDTUNE_MD5_LENGTH+1];
- tune.createMD5(md5sum);
-
- const unsigned song_num = get_song_num(path_fs.c_str());
- 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 SignedSongTime::Negative();
- }
-
- 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 SignedSongTime::FromS((minutes * 60) + seconds);
+ return SignedSongTime::FromS(length);
}
static void
@@ -209,18 +151,17 @@ sidplay_file_decode(Decoder &decoder, Path path_fs)
/* load the tune */
- char *path_container=get_container_name(path_fs);
- SidTune tune(path_container, nullptr, true);
- free(path_container);
+ const auto container = ParseContainerPath(path_fs);
+ SidTuneMod tune(container.path.c_str());
if (!tune) {
LogWarning(sidplay_domain, "failed to load file");
return;
}
- const int song_num = get_song_num(path_fs.c_str());
+ const int song_num = container.track;
tune.selectSong(song_num);
- auto duration = get_song_length(path_fs);
+ auto duration = get_song_length(tune);
if (duration.IsNegative() && default_songlength > 0)
duration = SongTime::FromS(default_songlength);
@@ -347,14 +288,15 @@ static bool
sidplay_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
- const int song_num = get_song_num(path_fs.c_str());
- char *path_container=get_container_name(path_fs);
+ const auto container = ParseContainerPath(path_fs);
+ const unsigned song_num = container.track;
- SidTune tune(path_container, nullptr, true);
- free(path_container);
+ SidTuneMod tune(container.path.c_str());
if (!tune)
return false;
+ tune.selectSong(song_num);
+
const SidTuneInfo &info = tune.getInfo();
/* title */
@@ -385,7 +327,7 @@ sidplay_scan_file(Path path_fs,
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
/* time */
- const auto duration = get_song_length(path_fs);
+ const auto duration = get_song_length(tune);
if (!duration.IsNegative())
tag_handler_invoke_duration(handler, handler_ctx,
SongTime(duration));
diff --git a/src/decoder/plugins/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx
index 062f46acf..10652f129 100644
--- a/src/decoder/plugins/VorbisComments.cxx
+++ b/src/decoder/plugins/VorbisComments.cxx
@@ -27,7 +27,7 @@
#include "tag/ReplayGain.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
-#include "util/SplitString.hxx"
+#include "util/DivideString.hxx"
#include <stddef.h>
#include <stdlib.h>
@@ -74,7 +74,7 @@ vorbis_scan_comment(const char *comment,
const struct tag_handler *handler, void *handler_ctx)
{
if (handler->pair != nullptr) {
- const SplitString split(comment, '=');
+ const DivideString split(comment, '=');
if (split.IsDefined() && !split.IsEmpty())
tag_handler_invoke_pair(handler, handler_ctx,
split.GetFirst(),
diff --git a/src/decoder/plugins/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx
index 67859bbd2..0b41d052e 100644
--- a/src/decoder/plugins/WavpackDecoderPlugin.cxx
+++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx
@@ -28,10 +28,10 @@
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
+#include "util/Alloc.hxx"
#include "Log.hxx"
#include <wavpack/wavpack.h>
-#include <glib.h>
#include <assert.h>
#include <stdio.h>
@@ -484,10 +484,10 @@ wavpack_open_wvc(Decoder &decoder, const char *uri)
if (uri == nullptr)
return nullptr;
- char *wvc_url = g_strconcat(uri, "c", nullptr);
+ char *wvc_url = xstrcatdup(uri, "c");
InputStream *is_wvc = decoder_open_uri(decoder, uri, IgnoreError());
- g_free(wvc_url);
+ free(wvc_url);
if (is_wvc == nullptr)
return nullptr;
diff --git a/src/decoder/plugins/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx
index 11a0bcd42..db5f6c5e1 100644
--- a/src/decoder/plugins/XiphTags.cxx
+++ b/src/decoder/plugins/XiphTags.cxx
@@ -27,7 +27,6 @@
const struct tag_table xiph_tags[] = {
{ "tracknumber", TAG_TRACK },
{ "discnumber", TAG_DISC },
- { "album artist", TAG_ALBUM_ARTIST },
{ "description", TAG_COMMENT },
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
};