// SPDX-License-Identifier: GPL-2.0-or-later // Copyright The Music Player Daemon Project #ifndef MPD_PCM_REST_BUFFER_HXX #define MPD_PCM_REST_BUFFER_HXX #include "ChannelDefs.hxx" #include #include #include #include class PcmBuffer; /** * A buffer which helps with conversion classes which need to handle * multiple frames at a time; it stores the rest of a previous * operation in order to use it in the next call. */ template class PcmRestBuffer { unsigned size, capacity; T data[n_frames * MAX_CHANNELS]; public: void Open(unsigned channels) noexcept { assert(audio_valid_channel_count(channels)); capacity = n_frames * channels; size = 0; } /** * @return the size of one input block in #T samples */ size_t GetInputBlockSize() const noexcept { return capacity; } unsigned GetChannelCount() const noexcept { return capacity / n_frames; } void Reset() noexcept { assert(audio_valid_channel_count(capacity / n_frames)); size = 0; } private: std::span Complete(std::span &src) noexcept { assert(audio_valid_channel_count(GetChannelCount())); assert(src.size() % GetChannelCount() == 0); if (size == 0) return {}; size_t missing = capacity - size; size_t n = std::min(missing, src.size()); std::copy_n(src.begin(), n, &data[size]); src = src.subspan(n); size += n; if (size < capacity) return {}; size = 0; return {data, capacity}; } void Append(std::span src) noexcept { assert(audio_valid_channel_count(GetChannelCount())); assert(src.size() % GetChannelCount() == 0); assert(size + src.size() < capacity); std::copy(src.begin(), src.end(), &data[size]); size += src.size(); } public: /** * A helper function which attempts to complete the rest * buffer, allocates a destination buffer and invokes the * given function for both the rest buffer and the new source * buffer. In the end, it copies more remaining data to the * rest buffer. * * @param U the destination data type * @param F the inner process function * @param dest_block_size how many destination samples in one block? * @return the destination buffer (allocated from #buffer); * may be empty */ template std::span Process(PcmBuffer &buffer, std::span src, size_t dest_block_size, F &&f) { assert(dest_block_size % GetChannelCount() == 0); const auto previous_rest = Complete(src); assert(previous_rest.size() == 0 || previous_rest.size() == capacity); const size_t previous_rest_blocks = !previous_rest.empty(); const size_t src_blocks = src.size() / capacity; const size_t next_rest_samples = src.size() % capacity; const size_t dest_blocks = previous_rest_blocks + src_blocks; const size_t dest_samples = dest_blocks * dest_block_size; const auto dest0 = buffer.GetT(dest_samples); auto dest = dest0; if (!previous_rest.empty()) { f(dest, previous_rest.data(), previous_rest_blocks); dest += dest_block_size; } f(dest, src.data(), src_blocks); if (next_rest_samples > 0) Append({src.data() + src_blocks * capacity, next_rest_samples}); return { dest0, dest_samples }; } }; #endif