output/Source: add Fill(), ReadTag(), PeekData(), ConsumeData()
Don't expose MusicChunk instances, provide higher-level access to chunk contents.
This commit is contained in:
parent
8a407bfbb0
commit
86d3b25aec
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user