diff --git a/configure.ac b/configure.ac index c64a0f515..24ce7f421 100644 --- a/configure.ac +++ b/configure.ac @@ -964,7 +964,7 @@ if test x$enable_tremor = xyes; then fi fi -MPD_AUTO_PKG(vorbis, VORBIS, [vorbisfile vorbis ogg], +MPD_AUTO_PKG(vorbis, VORBIS, [vorbis ogg], [Ogg Vorbis decoder], [libvorbis not found]) if test x$enable_vorbis = xyes; then AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support]) diff --git a/src/decoder/plugins/VorbisDecoderPlugin.cxx b/src/decoder/plugins/VorbisDecoderPlugin.cxx index f342a91fc..477041230 100644 --- a/src/decoder/plugins/VorbisDecoderPlugin.cxx +++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx @@ -19,9 +19,8 @@ #include "config.h" #include "VorbisDecoderPlugin.h" +#include "OggDecoder.hxx" #include "lib/xiph/VorbisComments.hxx" -#include "lib/xiph/OggSyncState.hxx" -#include "lib/xiph/OggStreamState.hxx" #include "lib/xiph/OggPacket.hxx" #include "lib/xiph/OggFind.hxx" #include "VorbisDomain.hxx" @@ -38,139 +37,116 @@ #include "Log.hxx" #ifndef HAVE_TREMOR -#define OV_EXCLUDE_STATIC_CALLBACKS -#include +#include #else -#include -/* Macros to make Tremor's API look like libogg. Tremor always - returns host-byte-order 16-bit signed data, and uses integer - milliseconds where libogg uses double seconds. -*/ -#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ - ov_read(VF, BUFFER, LENGTH, BITSTREAM) -#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) -#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) -#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) +#include #endif /* HAVE_TREMOR */ -#include +#include -struct VorbisInputStream { - Decoder *const decoder; +class VorbisDecoder final : public OggDecoder { +#ifdef HAVE_TREMOR + static constexpr SampleFormat sample_format = SampleFormat::S16; + typedef ogg_int32_t in_sample_t; + typedef int16_t out_sample_t; +#else + static constexpr SampleFormat sample_format = SampleFormat::FLOAT; + typedef float in_sample_t; + typedef float out_sample_t; +#endif - InputStream &input_stream; - bool seekable; + unsigned remaining_header_packets; - VorbisInputStream(Decoder *_decoder, InputStream &_is) - :decoder(_decoder), input_stream(_is), - seekable(input_stream.CheapSeeking()) {} -}; + vorbis_info vi; + vorbis_comment vc; + vorbis_dsp_state dsp; + vorbis_block block; -static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) -{ - VorbisInputStream *vis = (VorbisInputStream *)data; - size_t ret = decoder_read(vis->decoder, vis->input_stream, - ptr, size * nmemb); + /** + * If non-zero, then a previous Vorbis stream has been found + * already with this number of channels. + */ + AudioFormat audio_format = AudioFormat::Undefined(); + size_t frame_size; - errno = 0; + bool dsp_initialized = false; - return ret / size; -} - -static int ogg_seek_cb(void *data, ogg_int64_t _offset, int whence) -{ - VorbisInputStream *vis = (VorbisInputStream *)data; - InputStream &is = vis->input_stream; - - if (!vis->seekable || - (vis->decoder != nullptr && - decoder_get_command(*vis->decoder) == DecoderCommand::STOP)) - return -1; - - offset_type offset = _offset; - switch (whence) { - case SEEK_SET: - break; - - case SEEK_CUR: - offset += is.GetOffset(); - break; - - case SEEK_END: - if (!is.KnownSize()) - return -1; - - offset += is.GetSize(); - break; - - default: - return -1; +public: + explicit VorbisDecoder(DecoderReader &reader) + :OggDecoder(reader) { + InitVorbis(); } - return is.LockSeek(offset, IgnoreError()) - ? 0 : -1; -} + ~VorbisDecoder() { + DeinitVorbis(); + } -/* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(gcc_unused void *data) -{ - return 0; -} + bool Seek(uint64_t where_frame); -static long ogg_tell_cb(void *data) -{ - VorbisInputStream *vis = (VorbisInputStream *)data; +private: + void InitVorbis() { + vorbis_info_init(&vi); + vorbis_comment_init(&vc); + } - return (long)vis->input_stream.GetOffset(); -} + void DeinitVorbis() { + if (dsp_initialized) { + dsp_initialized = false; -static const ov_callbacks vorbis_is_callbacks = { - ogg_read_cb, - ogg_seek_cb, - ogg_close_cb, - ogg_tell_cb, + vorbis_block_clear(&block); + vorbis_dsp_clear(&dsp); + } + + vorbis_comment_clear(&vc); + vorbis_info_clear(&vi); + } + + void ReinitVorbis() { + DeinitVorbis(); + InitVorbis(); + } + + void SubmitInit(); + bool SubmitSomePcm(); + void SubmitPcm(); + +protected: + /* virtual methods from class OggVisitor */ + void OnOggBeginning(const ogg_packet &packet) override; + void OnOggPacket(const ogg_packet &packet) override; + void OnOggEnd() override; }; -static const char * -vorbis_strerror(int code) +bool +VorbisDecoder::Seek(uint64_t where_frame) { - switch (code) { - case OV_EREAD: - return "read error"; + assert(IsSeekable()); + assert(input_stream.IsSeekable()); + assert(input_stream.KnownSize()); - case OV_ENOTVORBIS: - return "not vorbis stream"; + const ogg_int64_t where_granulepos(where_frame); - case OV_EVERSION: - return "vorbis version mismatch"; - - case OV_EBADHEADER: - return "invalid vorbis header"; - - case OV_EFAULT: - return "internal logic error"; - - default: - return "unknown error"; - } -} - -static bool -vorbis_is_open(VorbisInputStream *vis, OggVorbis_File *vf) -{ - int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks); - if (ret < 0) { - if (vis->decoder == nullptr || - decoder_get_command(*vis->decoder) == DecoderCommand::NONE) - FormatWarning(vorbis_domain, - "Failed to open Ogg Vorbis stream: %s", - vorbis_strerror(ret)); + if (!SeekGranulePos(where_granulepos, IgnoreError())) return false; - } + vorbis_synthesis_restart(&dsp); return true; } +void +VorbisDecoder::OnOggBeginning(const ogg_packet &_packet) +{ + /* libvorbis wants non-const packets */ + ogg_packet &packet = const_cast(_packet); + + ReinitVorbis(); + + if (vorbis_synthesis_headerin(&vi, &vc, &packet) != 0) + throw std::runtime_error("Unrecognized Vorbis BOS packet"); + + remaining_header_packets = 2; +} + static void vorbis_send_comments(Decoder &decoder, InputStream &is, char **comments) @@ -183,16 +159,138 @@ vorbis_send_comments(Decoder &decoder, InputStream &is, delete tag; } -#ifndef HAVE_TREMOR -static void -vorbis_interleave(float *dest, const float *const*src, - unsigned nframes, unsigned channels) +void +VorbisDecoder::SubmitInit() { - PcmInterleaveFloat(dest, ConstBuffer(src, channels), - nframes); + assert(!dsp_initialized); + + Error error; + if (!audio_format_init_checked(audio_format, vi.rate, sample_format, + vi.channels, error)) + throw std::runtime_error(error.GetMessage()); + + frame_size = audio_format.GetFrameSize(); + + const auto eos_granulepos = UpdateEndGranulePos(); + const auto duration = eos_granulepos >= 0 + ? SignedSongTime::FromScale(eos_granulepos, + audio_format.sample_rate) + : SignedSongTime::Negative(); + + decoder_initialized(decoder, audio_format, + eos_granulepos > 0, duration); } + +bool +VorbisDecoder::SubmitSomePcm() +{ + in_sample_t **pcm; + int result = vorbis_synthesis_pcmout(&dsp, &pcm); + if (result <= 0) + return false; + + out_sample_t buffer[4096]; + const unsigned channels = audio_format.channels; + size_t max_frames = ARRAY_SIZE(buffer) / channels; + size_t n_frames = std::min(size_t(result), max_frames); + +#ifdef HAVE_TREMOR + for (unsigned c = 0; c < channels; ++c) { + const auto *src = pcm[c]; + auto *dest = &buffer[c]; + + for (size_t i = 0; i < n_frames; ++i) { + *dest = *src++; + dest += channels; + } + } +#else + PcmInterleaveFloat(buffer, + ConstBuffer(pcm, + channels), + n_frames); #endif + vorbis_synthesis_read(&dsp, n_frames); + + const size_t nbytes = n_frames * frame_size; + auto cmd = decoder_data(decoder, input_stream, + buffer, nbytes, + 0); + if (cmd != DecoderCommand::NONE) + throw cmd; + + return true; +} + +void +VorbisDecoder::SubmitPcm() +{ + while (SubmitSomePcm()) {} +} + +void +VorbisDecoder::OnOggPacket(const ogg_packet &_packet) +{ + /* libvorbis wants non-const packets */ + ogg_packet &packet = const_cast(_packet); + + if (remaining_header_packets > 0) { + if (vorbis_synthesis_headerin(&vi, &vc, &packet) != 0) + throw std::runtime_error("Unrecognized Vorbis header packet"); + + if (--remaining_header_packets > 0) + return; + + if (audio_format.IsDefined()) { + /* TODO: change the MPD decoder plugin API to + allow mid-song AudioFormat changes */ + if ((unsigned)vi.rate != audio_format.sample_rate || + (unsigned)vi.channels != audio_format.channels) + throw std::runtime_error("Next stream has different audio format"); + } else + SubmitInit(); + + vorbis_send_comments(decoder, input_stream, vc.user_comments); + + ReplayGainInfo rgi; + if (vorbis_comments_to_replay_gain(rgi, vc.user_comments)) + decoder_replay_gain(decoder, &rgi); + } else { + if (!dsp_initialized) { + dsp_initialized = true; + + vorbis_synthesis_init(&dsp, &vi); + vorbis_block_init(&dsp, &block); + } + + if (vorbis_synthesis(&block, &packet) != 0) { + /* ignore bad packets, but give the MPD core a + chance to stop us */ + auto cmd = decoder_get_command(decoder); + if (cmd != DecoderCommand::NONE) + throw cmd; + return; + } + + if (vorbis_synthesis_blockin(&dsp, &block) != 0) + throw std::runtime_error("vorbis_synthesis_blockin() failed"); + + SubmitPcm(); + +#ifndef HAVE_TREMOR + if (packet.granulepos > 0) + decoder_timestamp(decoder, + vorbis_granule_time(&dsp, packet.granulepos)); +#endif + } +} + +void +VorbisDecoder::OnOggEnd() +{ +} + /* public */ static bool @@ -204,16 +302,6 @@ vorbis_init(gcc_unused const ConfigBlock &block) return true; } -gcc_pure -static SignedSongTime -vorbis_duration(OggVorbis_File &vf) -{ - auto total = ov_time_total(&vf, -1); - return total >= 0 - ? SignedSongTime::FromS(total) - : SignedSongTime::Negative(); -} - static void vorbis_stream_decode(Decoder &decoder, InputStream &input_stream) @@ -225,118 +313,23 @@ vorbis_stream_decode(Decoder &decoder, moved it */ input_stream.LockRewind(IgnoreError()); - VorbisInputStream vis(&decoder, input_stream); - OggVorbis_File vf; - if (!vorbis_is_open(&vis, &vf)) - return; + DecoderReader reader(decoder, input_stream); + VorbisDecoder d(reader); - const vorbis_info *vi = ov_info(&vf, -1); - if (vi == nullptr) { - LogWarning(vorbis_domain, "ov_info() has failed"); - return; - } - - Error error; - AudioFormat audio_format; - if (!audio_format_init_checked(audio_format, vi->rate, -#ifdef HAVE_TREMOR - SampleFormat::S16, -#else - SampleFormat::FLOAT, -#endif - vi->channels, error)) { - LogError(error); - return; - } - - decoder_initialized(decoder, audio_format, vis.seekable, - vorbis_duration(vf)); - -#ifdef HAVE_TREMOR - char buffer[4096]; -#else - float buffer[2048]; - const int frames_per_buffer = - ARRAY_SIZE(buffer) / audio_format.channels; - const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels; -#endif - - int prev_section = -1; - unsigned kbit_rate = 0; - - DecoderCommand cmd = decoder_get_command(decoder); - while (cmd != DecoderCommand::STOP) { - if (cmd == DecoderCommand::SEEK) { - auto seek_where = decoder_seek_where_frame(decoder); - if (0 == ov_pcm_seek_page(&vf, seek_where)) { - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - - int current_section; - -#ifdef HAVE_TREMOR - long nbytes = ov_read(&vf, buffer, sizeof(buffer), - IsBigEndian(), 2, 1, - ¤t_section); -#else - float **per_channel; - long nframes = ov_read_float(&vf, &per_channel, - frames_per_buffer, - ¤t_section); - long nbytes = nframes; - if (nframes > 0) { - vorbis_interleave(buffer, - (const float*const*)per_channel, - nframes, audio_format.channels); - nbytes *= frame_size; - } -#endif - - if (nbytes == OV_HOLE) /* bad packet */ - nbytes = 0; - else if (nbytes <= 0) - /* break on EOF or other error */ + while (true) { + try { + d.Visit(); break; - - if (current_section != prev_section) { - vi = ov_info(&vf, -1); - if (vi == nullptr) { - LogWarning(vorbis_domain, - "ov_info() has failed"); + } catch (DecoderCommand cmd) { + if (cmd == DecoderCommand::SEEK) { + if (d.Seek(decoder_seek_where_frame(decoder))) + decoder_command_finished(decoder); + else + decoder_seek_error(decoder); + } else if (cmd != DecoderCommand::NONE) break; - } - - if (vi->rate != (long)audio_format.sample_rate || - vi->channels != (int)audio_format.channels) { - /* we don't support audio format - change yet */ - LogWarning(vorbis_domain, - "audio format change, stopping here"); - break; - } - - char **comments = ov_comment(&vf, -1)->user_comments; - vorbis_send_comments(decoder, input_stream, comments); - - ReplayGainInfo rgi; - if (vorbis_comments_to_replay_gain(rgi, comments)) - decoder_replay_gain(decoder, &rgi); - - prev_section = current_section; } - - long test = ov_bitrate_instant(&vf); - if (test > 0) - kbit_rate = test / 1000; - - cmd = decoder_data(decoder, input_stream, - buffer, nbytes, - kbit_rate); } - - ov_clear(&vf); } static void