diff options
Diffstat (limited to 'src/decoder')
-rw-r--r-- | src/decoder/DecoderList.cxx | 26 | ||||
-rw-r--r-- | src/decoder/plugins/DsdLib.cxx | 4 | ||||
-rw-r--r-- | src/decoder/plugins/DsdiffDecoderPlugin.cxx | 6 | ||||
-rw-r--r-- | src/decoder/plugins/DsfDecoderPlugin.cxx | 8 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegDecoderPlugin.cxx | 597 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegIo.cxx | 98 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegIo.hxx | 57 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegMetaData.cxx | 38 | ||||
-rw-r--r-- | src/decoder/plugins/FfmpegMetaData.hxx | 14 | ||||
-rw-r--r-- | src/decoder/plugins/FlacMetadata.cxx | 4 | ||||
-rw-r--r-- | src/decoder/plugins/FluidsynthDecoderPlugin.cxx | 4 | ||||
-rw-r--r-- | src/decoder/plugins/GmeDecoderPlugin.cxx | 188 | ||||
-rw-r--r-- | src/decoder/plugins/MadDecoderPlugin.cxx | 22 | ||||
-rw-r--r-- | src/decoder/plugins/SidplayDecoderPlugin.cxx | 180 | ||||
-rw-r--r-- | src/decoder/plugins/VorbisComments.cxx | 4 | ||||
-rw-r--r-- | src/decoder/plugins/WavpackDecoderPlugin.cxx | 6 | ||||
-rw-r--r-- | src/decoder/plugins/XiphTags.cxx | 1 |
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 ¶m) { - 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 ¶m) { /* 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 ¶m) 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 } }; |