output/Source: add Fill(), ReadTag(), PeekData(), ConsumeData()

Don't expose MusicChunk instances, provide higher-level access to
chunk contents.
This commit is contained in:
Max Kellermann 2016-12-26 13:53:22 +01:00
parent 8a407bfbb0
commit 86d3b25aec
4 changed files with 130 additions and 44 deletions

View File

@ -30,7 +30,6 @@
#include "system/PeriodClock.hxx" #include "system/PeriodClock.hxx"
class PreparedFilter; class PreparedFilter;
class Filter;
class MusicPipe; class MusicPipe;
class EventLoop; class EventLoop;
class Mixer; class Mixer;
@ -455,7 +454,9 @@ private:
*/ */
bool WaitForDelay(); bool WaitForDelay();
bool PlayChunk(const MusicChunk &chunk); bool FillSourceOrClose();
bool PlayChunk();
/** /**
* Plays all remaining chunks, until the tail of the pipe has * Plays all remaining chunks, until the tail of the pipe has

View File

@ -261,36 +261,43 @@ AudioOutput::WaitForDelay()
} }
} }
bool
AudioOutput::FillSourceOrClose()
try {
return source.Fill();
} catch (const std::runtime_error &e) {
FormatError(e, "Failed to filter for output \"%s\" [%s]",
name, plugin.name);
Close(false);
/* don't automatically reopen this device for 10
seconds */
fail_timer.Update();
return false;
}
inline bool inline bool
AudioOutput::PlayChunk(const MusicChunk &chunk) AudioOutput::PlayChunk()
{ {
if (tags && gcc_unlikely(chunk.tag != nullptr)) { if (tags) {
const ScopeUnlock unlock(mutex); const auto *tag = source.ReadTag();
try { if (tag != nullptr) {
ao_plugin_send_tag(this, *chunk.tag); const ScopeUnlock unlock(mutex);
} catch (const std::runtime_error &e) { try {
FormatError(e, "Failed to send tag to \"%s\" [%s]", ao_plugin_send_tag(this, *tag);
name, plugin.name); } catch (const std::runtime_error &e) {
FormatError(e, "Failed to send tag to \"%s\" [%s]",
name, plugin.name);
}
} }
} }
ConstBuffer<uint8_t> data; while (command == Command::NONE) {
const auto data = source.PeekData();
if (data.IsEmpty())
break;
try {
data = data.FromVoid(source.FilterChunk(chunk));
} catch (const std::runtime_error &e) {
FormatError(e, "Failed to filter for output \"%s\" [%s]",
name, plugin.name);
Close(false);
/* don't automatically reopen this device for 10
seconds */
fail_timer.Update();
return false;
}
while (!data.IsEmpty() && command == Command::NONE) {
if (!WaitForDelay()) if (!WaitForDelay())
break; break;
@ -319,7 +326,7 @@ AudioOutput::PlayChunk(const MusicChunk &chunk)
assert(nbytes % out_audio_format.GetFrameSize() == 0); assert(nbytes % out_audio_format.GetFrameSize() == 0);
data.skip_front(nbytes); source.ConsumeData(nbytes);
} }
return true; return true;
@ -328,8 +335,7 @@ AudioOutput::PlayChunk(const MusicChunk &chunk)
inline bool inline bool
AudioOutput::Play() AudioOutput::Play()
{ {
const MusicChunk *chunk = source.Get(); if (!FillSourceOrClose())
if (chunk == nullptr)
/* no chunk available */ /* no chunk available */
return false; return false;
@ -356,12 +362,9 @@ AudioOutput::Play()
n = 0; n = 0;
} }
if (!PlayChunk(*chunk)) if (!PlayChunk())
break; break;
} while (FillSourceOrClose());
source.Consume(*chunk);
chunk = source.Get();
} while (chunk != nullptr);
const ScopeUnlock unlock(mutex); const ScopeUnlock unlock(mutex);
client->ChunksConsumed(); client->ChunksConsumed();

View File

@ -185,3 +185,38 @@ AudioOutputSource::FilterChunk(const MusicChunk &chunk)
return filter_instance->FilterPCM(data); return filter_instance->FilterPCM(data);
} }
bool
AudioOutputSource::Fill()
{
if (current_chunk != nullptr && pending_tag == nullptr &&
pending_data.IsEmpty())
pipe.Consume(*std::exchange(current_chunk, nullptr));
if (current_chunk != nullptr)
return true;
current_chunk = pipe.Get();
if (current_chunk == nullptr)
return false;
pending_tag = current_chunk->tag;
try {
pending_data = pending_data.FromVoid(FilterChunk(*current_chunk));
} catch (...) {
current_chunk = nullptr;
throw;
}
return true;
}
void
AudioOutputSource::ConsumeData(size_t nbytes) noexcept
{
pending_data.skip_front(nbytes);
if (pending_data.IsEmpty())
pipe.Consume(*std::exchange(current_chunk, nullptr));
}

View File

@ -27,11 +27,15 @@
#include "ReplayGainMode.hxx" #include "ReplayGainMode.hxx"
#include "pcm/PcmBuffer.hxx" #include "pcm/PcmBuffer.hxx"
#include "pcm/PcmDither.hxx" #include "pcm/PcmDither.hxx"
#include "util/ConstBuffer.hxx"
#include <utility>
#include <assert.h> #include <assert.h>
#include <stdint.h>
template<typename T> struct ConstBuffer;
struct MusicChunk; struct MusicChunk;
struct Tag;
class Filter; class Filter;
class PreparedFilter; class PreparedFilter;
@ -96,6 +100,23 @@ class AudioOutputSource {
*/ */
Filter *filter_instance = nullptr; Filter *filter_instance = nullptr;
/**
* The #MusicChunk currently being processed (see
* #pending_tag, #pending_data).
*/
const MusicChunk *current_chunk = nullptr;
/**
* The #Tag to be processed by the #AudioOutput.
*/
const Tag *pending_tag;
/**
* Filtered #MusicChunk PCM data to be processed by the
* #AudioOutput.
*/
ConstBuffer<uint8_t> pending_data;
public: public:
void SetReplayGainMode(ReplayGainMode _mode) { void SetReplayGainMode(ReplayGainMode _mode) {
replay_gain_mode = _mode; replay_gain_mode = _mode;
@ -117,21 +138,47 @@ public:
void Close(); void Close();
void Cancel() { void Cancel() {
current_chunk = nullptr;
pipe.Cancel(); pipe.Cancel();
} }
const MusicChunk *Get() { /**
assert(IsOpen()); * Ensure that ReadTag() or PeekData() return any input.
*
* Throws std::runtime_error on error
*
* @return true if any input is available, false if the source
* has (temporarily?) run empty
*/
bool Fill();
return pipe.Get(); /**
* Reads the #Tag to be processed. Be sure to call Fill()
* successfully before calling this metohd.
*/
const Tag *ReadTag() noexcept {
assert(current_chunk != nullptr);
return std::exchange(pending_tag, nullptr);
} }
void Consume(const MusicChunk &chunk) { /**
assert(IsOpen()); * Returns the remaining filtered PCM data be played. The
* caller shall use ConsumeData() to mark portions of the
pipe.Consume(chunk); * return value as "consumed".
*
* Be sure to call Fill() successfully before calling this
* metohd.
*/
ConstBuffer<void> PeekData() const noexcept {
return pending_data.ToVoid();
} }
/**
* Mark portions of the PeekData() return value as "consumed".
*/
void ConsumeData(size_t nbytes) noexcept;
bool IsChunkConsumed(const MusicChunk &chunk) const { bool IsChunkConsumed(const MusicChunk &chunk) const {
assert(IsOpen()); assert(IsOpen());
@ -142,8 +189,6 @@ public:
pipe.ClearTail(chunk); pipe.ClearTail(chunk);
} }
ConstBuffer<void> FilterChunk(const MusicChunk &chunk);
private: private:
void OpenFilter(AudioFormat audio_format, void OpenFilter(AudioFormat audio_format,
PreparedFilter *prepared_replay_gain_filter, PreparedFilter *prepared_replay_gain_filter,
@ -155,6 +200,8 @@ private:
ConstBuffer<void> GetChunkData(const MusicChunk &chunk, ConstBuffer<void> GetChunkData(const MusicChunk &chunk,
Filter *replay_gain_filter, Filter *replay_gain_filter,
unsigned *replay_gain_serial_p); unsigned *replay_gain_serial_p);
ConstBuffer<void> FilterChunk(const MusicChunk &chunk);
}; };
#endif #endif