diff --git a/NEWS b/NEWS index 56b307992..509b0be15 100644 --- a/NEWS +++ b/NEWS @@ -13,9 +13,16 @@ ver 0.22 (not yet released) - hdcd: new plugin based on FFmpeg's "af_hdcd" for HDCD playback - volume: convert S16 to S24 to preserve quality and reduce dithering noise -ver 0.21.12 (not yet released) +ver 0.21.12 (2019/08/03) * decoder + - mad: update bit rate after seeking + - mad: fix several bugs preventing the plugin from decoding the last frame - opus: ignore case in replay gain tag names + - opus, vorbis: decode the "end of stream" packet +* output + - jack: fix mono-to-stereo conversion +* player + - don't restart unseekable song after failed seek attempt * Windows - support backslash in relative URIs loaded from playlists diff --git a/meson.build b/meson.build index 7b2637f5a..e3ec613cf 100644 --- a/meson.build +++ b/meson.build @@ -15,6 +15,12 @@ version_cxx = vcs_tag(input: 'src/GitVersion.cxx', output: 'GitVersion.cxx') compiler = meson.get_compiler('cpp') c_compiler = meson.get_compiler('c') +if compiler.get_id() == 'gcc' and compiler.version().version_compare('<6') + warning('Your GCC version is too old. You need at least version 6.') +elif compiler.get_id() == 'clang' and compiler.version().version_compare('<3') + warning('Your clang version is too old. You need at least version 3.') +endif + conf = configuration_data() conf.set_quoted('PACKAGE', meson.project_name()) conf.set_quoted('PACKAGE_NAME', meson.project_name()) diff --git a/src/decoder/Control.hxx b/src/decoder/Control.hxx index b9c021cc4..ee9dde73b 100644 --- a/src/decoder/Control.hxx +++ b/src/decoder/Control.hxx @@ -310,6 +310,11 @@ public: gcc_pure bool IsCurrentSong(const DetachedSong &_song) const noexcept; + gcc_pure + bool IsUnseekableCurrentSong(const DetachedSong &_song) const noexcept { + return !seekable && IsCurrentSong(_song); + } + gcc_pure bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept { return seekable && IsCurrentSong(_song); diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx index 1c4ae017c..f032e0810 100644 --- a/src/decoder/plugins/MadDecoderPlugin.cxx +++ b/src/decoder/plugins/MadDecoderPlugin.cxx @@ -27,6 +27,7 @@ #include "tag/ReplayGain.hxx" #include "tag/MixRamp.hxx" #include "CheckAudioFormat.hxx" +#include "util/Clamp.hxx" #include "util/StringCompare.hxx" #include "util/Domain.hxx" #include "Log.hxx" @@ -75,33 +76,26 @@ ToSongTime(mad_timer_t t) noexcept } static inline int32_t -mad_fixed_to_24_sample(mad_fixed_t sample) +mad_fixed_to_24_sample(mad_fixed_t sample) noexcept { static constexpr unsigned bits = 24; - static constexpr mad_fixed_t MIN = -MAD_F_ONE; - static constexpr mad_fixed_t MAX = MAD_F_ONE - 1; /* round */ sample = sample + (1L << (MAD_F_FRACBITS - bits)); - /* clip */ - if (gcc_unlikely(sample > MAX)) - sample = MAX; - else if (gcc_unlikely(sample < MIN)) - sample = MIN; - /* quantize */ - return sample >> (MAD_F_FRACBITS + 1 - bits); + return Clamp(sample, MAD_F_MIN, MAD_F_MAX) + >> (MAD_F_FRACBITS + 1 - bits); } static void -mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, - unsigned int start, unsigned int end, +mad_fixed_to_24_buffer(int32_t *dest, const struct mad_pcm &src, + size_t start, size_t end, unsigned int num_channels) { - for (unsigned i = start; i < end; ++i) + for (size_t i = start; i < end; ++i) for (unsigned c = 0; c < num_channels; ++c) - *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); + *dest++ = mad_fixed_to_24_sample(src.samples[c][i]); } static bool @@ -112,45 +106,56 @@ mad_plugin_init(const ConfigBlock &block) return true; } -struct MadDecoder { +class MadDecoder { static constexpr size_t READ_BUFFER_SIZE = 40960; - static constexpr size_t MP3_DATA_OUTPUT_BUFFER_SIZE = 2048; struct mad_stream stream; struct mad_frame frame; struct mad_synth synth; mad_timer_t timer; unsigned char input_buffer[READ_BUFFER_SIZE]; - int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; + int32_t output_buffer[sizeof(mad_pcm::samples) / sizeof(mad_fixed_t)]; SignedSongTime total_time; SongTime elapsed_time; SongTime seek_time; MadDecoderMuteFrame mute_frame = MadDecoderMuteFrame::NONE; long *frame_offsets = nullptr; mad_timer_t *times = nullptr; - unsigned long highest_frame = 0; - unsigned long max_frames = 0; - unsigned long current_frame = 0; - unsigned int drop_start_frames = 0; - unsigned int drop_end_frames = 0; + size_t highest_frame = 0; + size_t max_frames = 0; + size_t current_frame = 0; + unsigned int drop_start_frames; + unsigned int drop_end_frames; unsigned int drop_start_samples = 0; unsigned int drop_end_samples = 0; bool found_replay_gain = false; bool found_first_frame = false; bool decoded_first_frame = false; - unsigned long bit_rate; + + /** + * If this flag is true, then end-of-file was seen and a + * padding of 8 zero bytes were appended to #input_buffer, to + * allow libmad to decode the last frame. + */ + bool was_eof = false; + DecoderClient *const client; InputStream &input_stream; enum mad_layer layer = mad_layer(0); - MadDecoder(DecoderClient *client, InputStream &input_stream); - ~MadDecoder(); +public: + MadDecoder(DecoderClient *client, InputStream &input_stream) noexcept; + ~MadDecoder() noexcept; - bool Seek(long offset); - bool FillBuffer(); - void ParseId3(size_t tagsize, Tag *tag); - MadDecoderAction DecodeNextFrameHeader(Tag *tag); - MadDecoderAction DecodeNextFrame(); + void RunDecoder() noexcept; + bool RunScan(TagHandler &handler) noexcept; + +private: + bool Seek(long offset) noexcept; + bool FillBuffer() noexcept; + void ParseId3(size_t tagsize, Tag *tag) noexcept; + MadDecoderAction DecodeNextFrameHeader(Tag *tag) noexcept; + MadDecoderAction DecodeNextFrame() noexcept; gcc_pure offset_type ThisFrameOffset() const noexcept; @@ -161,11 +166,11 @@ struct MadDecoder { /** * Attempt to calulcate the length of the song from filesize */ - void FileSizeToSongLength(); + void FileSizeToSongLength() noexcept; - bool DecodeFirstFrame(Tag *tag); + bool DecodeFirstFrame(Tag *tag) noexcept; - void AllocateBuffers() { + void AllocateBuffers() noexcept { assert(max_frames > 0); assert(frame_offsets == nullptr); assert(times == nullptr); @@ -175,27 +180,39 @@ struct MadDecoder { } gcc_pure - long TimeToFrame(SongTime t) const noexcept; + size_t TimeToFrame(SongTime t) const noexcept; - void UpdateTimerNextFrame(); + /** + * Record the current frame's offset in the "frame_offsets" + * buffer and go forward to the next frame, updating the + * attributes "current_frame" and "timer". + */ + void UpdateTimerNextFrame() noexcept; /** * Sends the synthesized current frame via * DecoderClient::SubmitData(). */ - DecoderCommand SendPCM(unsigned i, unsigned pcm_length); + DecoderCommand SubmitPCM(size_t start, size_t n) noexcept; /** * Synthesize the current frame and send it via * DecoderClient::SubmitData(). */ - DecoderCommand SyncAndSend(); + DecoderCommand SynthAndSubmit() noexcept; - bool Read(); + /** + * @return false to stop decoding + */ + bool HandleCurrentFrame() noexcept; + + bool LoadNextFrame() noexcept; + + bool Read() noexcept; }; MadDecoder::MadDecoder(DecoderClient *_client, - InputStream &_input_stream) + InputStream &_input_stream) noexcept :client(_client), input_stream(_input_stream) { mad_stream_init(&stream); @@ -206,7 +223,7 @@ MadDecoder::MadDecoder(DecoderClient *_client, } inline bool -MadDecoder::Seek(long offset) +MadDecoder::Seek(long offset) noexcept { try { input_stream.LockSeek(offset); @@ -221,32 +238,38 @@ MadDecoder::Seek(long offset) } inline bool -MadDecoder::FillBuffer() +MadDecoder::FillBuffer() noexcept { - size_t remaining, length; - unsigned char *dest; + /* amount of rest data still residing in the buffer */ + size_t rest_size = 0; + + size_t max_read_size = sizeof(input_buffer); + unsigned char *dest = input_buffer; if (stream.next_frame != nullptr) { - remaining = stream.bufend - stream.next_frame; - memmove(input_buffer, stream.next_frame, remaining); - dest = input_buffer + remaining; - length = READ_BUFFER_SIZE - remaining; - } else { - remaining = 0; - length = READ_BUFFER_SIZE; - dest = input_buffer; + rest_size = stream.bufend - stream.next_frame; + memmove(input_buffer, stream.next_frame, rest_size); + dest += rest_size; + max_read_size -= rest_size; } /* we've exhausted the read buffer, so give up!, these potential * mp3 frames are way too big, and thus unlikely to be mp3 frames */ - if (length == 0) + if (max_read_size == 0) return false; - length = decoder_read(client, input_stream, dest, length); - if (length == 0) - return false; + size_t nbytes = decoder_read(client, input_stream, + dest, max_read_size); + if (nbytes == 0) { + if (was_eof || max_read_size < MAD_BUFFER_GUARD) + return false; - mad_stream_buffer(&stream, input_buffer, length + remaining); + was_eof = true; + nbytes = MAD_BUFFER_GUARD; + memset(dest, 0, nbytes); + } + + mad_stream_buffer(&stream, input_buffer, rest_size + nbytes); stream.error = MAD_ERROR_NONE; return true; @@ -282,7 +305,7 @@ parse_id3_mixramp(struct id3_tag *tag) noexcept #endif inline void -MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) +MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) noexcept { #ifdef ENABLE_ID3TAG std::unique_ptr allocated; @@ -350,7 +373,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag *mpd_tag) * of the ID3 frame. */ static signed long -id3_tag_query(const void *p0, size_t length) +id3_tag_query(const void *p0, size_t length) noexcept { const char *p = (const char *)p0; @@ -361,7 +384,7 @@ id3_tag_query(const void *p0, size_t length) #endif /* !ENABLE_ID3TAG */ static MadDecoderAction -RecoverFrameError(struct mad_stream &stream) +RecoverFrameError(const struct mad_stream &stream) noexcept { if (MAD_RECOVERABLE(stream.error)) return MadDecoderAction::SKIP; @@ -375,7 +398,7 @@ RecoverFrameError(struct mad_stream &stream) } MadDecoderAction -MadDecoder::DecodeNextFrameHeader(Tag *tag) +MadDecoder::DecodeNextFrameHeader(Tag *tag) noexcept { if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && !FillBuffer()) @@ -413,7 +436,7 @@ MadDecoder::DecodeNextFrameHeader(Tag *tag) } MadDecoderAction -MadDecoder::DecodeNextFrame() +MadDecoder::DecodeNextFrame() noexcept { if ((stream.buffer == nullptr || stream.error == MAD_ERROR_BUFLEN) && !FillBuffer()) @@ -472,7 +495,7 @@ struct lame { }; static bool -parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) +parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) noexcept { int bitlen = *oldbitlen; @@ -552,7 +575,7 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) } static bool -parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) +parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept { /* Unlike the xing header, the lame tag has a fixed length. Fail if * not all 36 bytes (288 bits) are there. */ @@ -643,7 +666,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) } static inline SongTime -mad_frame_duration(const struct mad_frame *frame) +mad_frame_duration(const struct mad_frame *frame) noexcept { return ToSongTime(frame->header.duration); } @@ -668,7 +691,7 @@ MadDecoder::RestIncludingThisFrame() const noexcept } inline void -MadDecoder::FileSizeToSongLength() +MadDecoder::FileSizeToSongLength() noexcept { if (input_stream.KnownSize()) { offset_type rest = RestIncludingThisFrame(); @@ -690,7 +713,7 @@ MadDecoder::FileSizeToSongLength() } inline bool -MadDecoder::DecodeFirstFrame(Tag *tag) +MadDecoder::DecodeFirstFrame(Tag *tag) noexcept { struct xing xing; @@ -732,9 +755,17 @@ MadDecoder::DecodeFirstFrame(Tag *tag) struct lame lame; if (parse_lame(&lame, &ptr, &bitlen)) { if (gapless_playback && input_stream.IsSeekable()) { + /* libmad inserts 529 samples of + silence at the beginning and + removes those 529 samples at the + end */ drop_start_samples = lame.encoder_delay + DECODERDELAY; drop_end_samples = lame.encoder_padding; + if (drop_end_samples > DECODERDELAY) + drop_end_samples -= DECODERDELAY; + else + drop_end_samples = 0; } /* Album gain isn't currently used. See comment in @@ -763,7 +794,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag) return true; } -MadDecoder::~MadDecoder() +MadDecoder::~MadDecoder() noexcept { mad_synth_finish(&synth); mad_frame_finish(&frame); @@ -773,10 +804,10 @@ MadDecoder::~MadDecoder() delete[] times; } -long +size_t MadDecoder::TimeToFrame(SongTime t) const noexcept { - unsigned long i; + size_t i; for (i = 0; i < highest_frame; ++i) { auto frame_time = ToSongTime(times[i]); @@ -788,12 +819,11 @@ MadDecoder::TimeToFrame(SongTime t) const noexcept } void -MadDecoder::UpdateTimerNextFrame() +MadDecoder::UpdateTimerNextFrame() noexcept { if (current_frame >= highest_frame) { /* record this frame's properties in frame_offsets (for seeking) and times */ - bit_rate = frame.header.bitrate; if (current_frame >= max_frames) /* cap current_frame */ @@ -814,36 +844,22 @@ MadDecoder::UpdateTimerNextFrame() } DecoderCommand -MadDecoder::SendPCM(unsigned i, unsigned pcm_length) +MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept { - unsigned max_samples = sizeof(output_buffer) / - sizeof(output_buffer[0]) / - MAD_NCHANNELS(&frame.header); + size_t num_samples = pcm_length - i; - while (i < pcm_length) { - unsigned int num_samples = pcm_length - i; - if (num_samples > max_samples) - num_samples = max_samples; + mad_fixed_to_24_buffer(output_buffer, synth.pcm, + i, i + num_samples, + MAD_NCHANNELS(&frame.header)); + num_samples *= MAD_NCHANNELS(&frame.header); - i += num_samples; - - mad_fixed_to_24_buffer(output_buffer, &synth, - i - num_samples, i, - MAD_NCHANNELS(&frame.header)); - num_samples *= MAD_NCHANNELS(&frame.header); - - auto cmd = client->SubmitData(input_stream, output_buffer, - sizeof(output_buffer[0]) * num_samples, - bit_rate / 1000); - if (cmd != DecoderCommand::NONE) - return cmd; - } - - return DecoderCommand::NONE; + return client->SubmitData(input_stream, output_buffer, + sizeof(output_buffer[0]) * num_samples, + frame.header.bitrate / 1000); } inline DecoderCommand -MadDecoder::SyncAndSend() +MadDecoder::SynthAndSubmit() noexcept { mad_synth_frame(&synth, &frame); @@ -860,33 +876,33 @@ MadDecoder::SyncAndSend() drop_start_frames--; return DecoderCommand::NONE; } else if ((drop_end_frames > 0) && - (current_frame == (max_frames + 1 - drop_end_frames))) { + current_frame == max_frames - drop_end_frames) { /* stop decoding, effectively dropping all remaining frames */ return DecoderCommand::STOP; } - unsigned i = 0; + size_t i = 0; if (!decoded_first_frame) { i = drop_start_samples; decoded_first_frame = true; } - unsigned pcm_length = synth.pcm.length; + size_t pcm_length = synth.pcm.length; if (drop_end_samples && - (current_frame == max_frames - drop_end_frames)) { + current_frame == max_frames - drop_end_frames - 1) { if (drop_end_samples >= pcm_length) - pcm_length = 0; - else - pcm_length -= drop_end_samples; + return DecoderCommand::STOP; + + pcm_length -= drop_end_samples; } - auto cmd = SendPCM(i, pcm_length); + auto cmd = SubmitPCM(i, pcm_length); if (cmd != DecoderCommand::NONE) return cmd; if (drop_end_samples && - (current_frame == max_frames - drop_end_frames)) + current_frame == max_frames - drop_end_frames - 1) /* stop decoding, effectively dropping * all remaining samples */ return DecoderCommand::STOP; @@ -895,10 +911,8 @@ MadDecoder::SyncAndSend() } inline bool -MadDecoder::Read() +MadDecoder::HandleCurrentFrame() noexcept { - UpdateTimerNextFrame(); - switch (mute_frame) { DecoderCommand cmd; @@ -908,17 +922,20 @@ MadDecoder::Read() case MadDecoderMuteFrame::SEEK: if (elapsed_time >= seek_time) mute_frame = MadDecoderMuteFrame::NONE; + UpdateTimerNextFrame(); break; case MadDecoderMuteFrame::NONE: - cmd = SyncAndSend(); + cmd = SynthAndSubmit(); + UpdateTimerNextFrame(); if (cmd == DecoderCommand::SEEK) { assert(input_stream.IsSeekable()); const auto t = client->GetSeekTime(); - unsigned long j = TimeToFrame(t); + size_t j = TimeToFrame(t); if (j < highest_frame) { if (Seek(frame_offsets[j])) { current_frame = j; + was_eof = false; client->CommandFinished(); } else client->SeekError(); @@ -931,6 +948,12 @@ MadDecoder::Read() return false; } + return true; +} + +inline bool +MadDecoder::LoadNextFrame() noexcept +{ while (true) { MadDecoderAction ret; do { @@ -960,51 +983,71 @@ MadDecoder::Read() } } +inline bool +MadDecoder::Read() noexcept +{ + return HandleCurrentFrame() && + LoadNextFrame(); +} + +inline void +MadDecoder::RunDecoder() noexcept +{ + assert(client != nullptr); + + Tag tag; + if (!DecodeFirstFrame(&tag)) { + if (client->GetCommand() == DecoderCommand::NONE) + LogError(mad_domain, + "input does not appear to be a mp3 bit stream"); + return; + } + + AllocateBuffers(); + + client->Ready(CheckAudioFormat(frame.header.samplerate, + SampleFormat::S24_P32, + MAD_NCHANNELS(&frame.header)), + input_stream.IsSeekable(), + total_time); + + if (!tag.IsEmpty()) + client->SubmitTag(input_stream, std::move(tag)); + + while (Read()) {} +} + static void mad_decode(DecoderClient &client, InputStream &input_stream) { MadDecoder data(&client, input_stream); + data.RunDecoder(); +} - Tag tag; - if (!data.DecodeFirstFrame(&tag)) { - if (client.GetCommand() == DecoderCommand::NONE) - LogError(mad_domain, - "input/Input does not appear to be a mp3 bit stream"); - return; +inline bool +MadDecoder::RunScan(TagHandler &handler) noexcept +{ + if (!DecodeFirstFrame(nullptr)) + return false; + + if (!total_time.IsNegative()) + handler.OnDuration(SongTime(total_time)); + + try { + handler.OnAudioFormat(CheckAudioFormat(frame.header.samplerate, + SampleFormat::S24_P32, + MAD_NCHANNELS(&frame.header))); + } catch (...) { } - data.AllocateBuffers(); - - client.Ready(CheckAudioFormat(data.frame.header.samplerate, - SampleFormat::S24_P32, - MAD_NCHANNELS(&data.frame.header)), - input_stream.IsSeekable(), - data.total_time); - - if (!tag.IsEmpty()) - client.SubmitTag(input_stream, std::move(tag)); - - while (data.Read()) {} + return true; } static bool mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept { MadDecoder data(nullptr, is); - if (!data.DecodeFirstFrame(nullptr)) - return false; - - if (!data.total_time.IsNegative()) - handler.OnDuration(SongTime(data.total_time)); - - try { - handler.OnAudioFormat(CheckAudioFormat(data.frame.header.samplerate, - SampleFormat::S24_P32, - MAD_NCHANNELS(&data.frame.header))); - } catch (...) { - } - - return true; + return data.RunScan(handler); } static const char *const mad_suffixes[] = { "mp3", "mp2", nullptr }; diff --git a/src/lib/xiph/OggVisitor.cxx b/src/lib/xiph/OggVisitor.cxx index 411d2bd40..339609cde 100644 --- a/src/lib/xiph/OggVisitor.cxx +++ b/src/lib/xiph/OggVisitor.cxx @@ -69,12 +69,12 @@ OggVisitor::HandlePacket(const ogg_packet &packet) /* fail if BOS is missing */ throw std::runtime_error("BOS packet expected"); + OnOggPacket(packet); + if (packet.e_o_s) { EndStream(); return; } - - OnOggPacket(packet); } inline void diff --git a/src/lib/xiph/OggVisitor.hxx b/src/lib/xiph/OggVisitor.hxx index 37f37003e..9600dea9c 100644 --- a/src/lib/xiph/OggVisitor.hxx +++ b/src/lib/xiph/OggVisitor.hxx @@ -67,8 +67,21 @@ private: void HandlePackets(); protected: + /** + * Called when the "beginning of stream" packet has been seen. + * + * @param packet the "beginning of stream" packet + */ virtual void OnOggBeginning(const ogg_packet &packet) = 0; + + /** + * Called for each follow-up packet. + */ virtual void OnOggPacket(const ogg_packet &packet) = 0; + + /** + * Called after the "end of stream" packet has been processed. + */ virtual void OnOggEnd() = 0; }; diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index 8b02ff3a8..686aa89aa 100644 --- a/src/output/plugins/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -539,7 +539,7 @@ JackOutput::Start() std::fill(dports + num_dports, dports + audio_format.channels, dports[0]); } else if (num_dports > audio_format.channels) { - if (audio_format.channels == 1 && num_dports > 2) { + if (audio_format.channels == 1 && num_dports >= 2) { /* mono input file: connect the one source channel to the both destination channels */ duplicate_port = dports[1]; diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx index 5f0962c0c..c09a00eca 100644 --- a/src/player/Thread.cxx +++ b/src/player/Thread.cxx @@ -602,6 +602,19 @@ Player::SeekDecoder(std::unique_lock &lock) noexcept { assert(pc.next_song != nullptr); + if (pc.seek_time > SongTime::zero() && // TODO: allow this only if the song duration is known + dc.IsUnseekableCurrentSong(*pc.next_song)) { + /* seeking into the current song; but we already know + it's not seekable, so let's fail early */ + /* note the seek_time>0 check: if seeking to the + beginning, we can simply restart the decoder */ + pc.next_song.reset(); + pc.SetError(PlayerError::DECODER, + std::make_exception_ptr(std::runtime_error("Not seekable"))); + pc.CommandFinished(); + return true; + } + CancelPendingSeek(); { diff --git a/src/util/Compiler.h b/src/util/Compiler.h index 275c04262..e441d3835 100644 --- a/src/util/Compiler.h +++ b/src/util/Compiler.h @@ -57,18 +57,6 @@ (GCC_VERSION > 0 && CLANG_VERSION == 0 && \ GCC_VERSION < GCC_MAKE_VERSION(major, minor, 0)) -#ifdef __clang__ -# if __clang_major__ < 3 -# error Sorry, your clang version is too old. You need at least version 3.1. -# endif -#elif defined(__GNUC__) -# if GCC_OLDER_THAN(6,0) -# error Sorry, your gcc version is too old. You need at least version 6.0. -# endif -#else -# warning Untested compiler. Use at your own risk! -#endif - /** * Are we building with the specified version of clang or newer? */ diff --git a/src/util/StaticFifoBuffer.hxx b/src/util/StaticFifoBuffer.hxx index 076a573bb..615f92e3f 100644 --- a/src/util/StaticFifoBuffer.hxx +++ b/src/util/StaticFifoBuffer.hxx @@ -56,11 +56,11 @@ protected: T data[size]; public: - constexpr size_type GetCapacity() const { + constexpr size_type GetCapacity() const noexcept { return size; } - void Shift() { + void Shift() noexcept { if (head == 0) return; @@ -74,15 +74,15 @@ public: head = 0; } - void Clear() { + void Clear() noexcept { head = tail = 0; } - bool empty() const { + constexpr bool empty() const noexcept { return head == tail; } - bool IsFull() const { + constexpr bool IsFull() const noexcept { return head == 0 && tail == size; } @@ -90,7 +90,7 @@ public: * Prepares writing. Returns a buffer range which may be written. * When you are finished, call Append(). */ - Range Write() { + Range Write() noexcept { if (empty()) Clear(); else if (tail == size) @@ -103,7 +103,7 @@ public: * Expands the tail of the buffer, after data has been written to * the buffer returned by Write(). */ - void Append(size_type n) { + void Append(size_type n) noexcept { assert(tail <= size); assert(n <= size); assert(tail + n <= size); @@ -111,18 +111,22 @@ public: tail += n; } + constexpr size_type GetAvailable() const noexcept { + return tail - head; + } + /** * Return a buffer range which may be read. The buffer pointer is * writable, to allow modifications while parsing. */ - Range Read() { + constexpr Range Read() noexcept { return Range(data + head, tail - head); } /** * Marks a chunk as consumed. */ - void Consume(size_type n) { + void Consume(size_type n) noexcept { assert(tail <= size); assert(head <= tail); assert(n <= tail);