12#include <QMutexLocker>
22using namespace std::chrono_literals;
24#define LOC QString("AOG: ")
40 using Range = std::pair<std::chrono::milliseconds, std::chrono::milliseconds>;
48 if (Timecode == 0ms || Timecode == -1ms)
66 return {first, second};
79 return MS2Samples(Available.second - Available.first);
92 void Append(
const void *
Buffer,
unsigned long Length, std::chrono::milliseconds Timecode,
97 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Length:%1 Timecode:%2 Channels:%3 Bits:%4")
98 .arg(Length).arg(Timecode.count()).arg(
Channels).arg(Bits));
107 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
108 QString(
"m_tcFirst:%1 m_tcNext:%2 Timecode:%3 samples:%4 ms:%5")
112 if (qAbs((Timecode -
m_tcNext).count()) <= 1)
120 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Duplicate"));
125 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Discontinuity %1 -> %2")
126 .arg(
m_tcNext.count()).arg(Timecode.count()));
136 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Overflow:%1 size:%2 m_sizeMax:%3")
137 .arg(overflow).arg(size()).arg(
m_sizeMax));
145 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
146 QString(
"Overflow:%1 m_tcFirst old, new, delta:%2 %3 %4")
147 .arg(overflow).arg(old_m_tcFirst.count()).arg(
m_tcFirst.count())
148 .arg((
m_tcFirst - old_m_tcFirst).count()));
156 return reinterpret_cast<const int16_t*
>(constData() + (
static_cast<ptrdiff_t
>(start) *
BytesPerSampleNormalized()));
190 return Msecs > 0ms ?
static_cast<int>((Msecs.count() *
m_sampleRate) / 1000) : 0;
207 resize(n +
static_cast<int>(
sizeof(int16_t) * count));
208 const auto * src =
reinterpret_cast<const uchar*
>(
Buffer);
209 auto * dst =
reinterpret_cast<int16_t*
>(data() + n);
211 *dst++ =
static_cast<int16_t
>((
static_cast<int16_t
>(*src++) - CHAR_MAX) << (16 - CHAR_BIT));
216 append(
reinterpret_cast<const char*
>(
Buffer),
static_cast<int>(Length));
221 unsigned long count = Length /
sizeof(float);
223 resize(n +
static_cast<int>(
sizeof(int16_t) * count));
224 const float f((1 << 15) - 1);
225 const auto * src =
reinterpret_cast<const float*
>(
Buffer);
226 auto * dst =
reinterpret_cast<int16_t*
>(data() + n);
228 *dst++ =
static_cast<int16_t
>(f * (*src++));
232 append(
reinterpret_cast<const char*
>(
Buffer),
static_cast<int>(Length));
277 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Set sample rate %1").arg(
SampleRate));
284 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC + QString(
"Set sample count %1").arg(SampleCount));
294 std::chrono::milliseconds Timecode,
int Channnels,
int Bits)
302 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Reset");
310 auto EmptyImage = [
this]() {
311 QImage image(8, 8, QImage::Format_ARGB32);
322 bool data_present =
false;
323 for (
int i=0; i<10 && !data_present; i++)
327 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
328 QString(
"Audio data for timecode %1 arrived after %2 wait")
329 .arg(Timecode.count()).arg(i));
335 std::this_thread::sleep_for(20ms);
343 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
344 QString(
"No audio data for timecode %1")
345 .arg(Timecode.count()));
349 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
350 QString(
"GetImage for timecode %1 using [%2..%3] available [%4..%5]")
351 .arg(Timecode.count()).arg(avail.first.count()).arg(avail.second.count())
357 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
358 QString(
"GetImage for timecode %1 SKIPPED because width <= 0").arg(Timecode.count()));
363 const auto threshold = 20 * log10(1.0 / range);
364 auto height =
static_cast<int>(-ceil(threshold));
367 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
368 QString(
"GetImage for timecode %1 SKIPPED because height <= 0").arg(Timecode.count()));
376 const auto * max =
reinterpret_cast<const int16_t*
>(
m_buffer->constData() +
m_buffer->size());
379 LOG(VB_PLAYBACK, LOG_ERR,
LOC +
380 QString(
"GetImage for timecode %1 SKIPPED because data starts beyond end of buffer")
381 .arg(Timecode.count()));
386 if ((data + (channels *
static_cast<ptrdiff_t
>(width))) > max)
388 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
"Buffer overflow. Clipping samples.");
389 width =
static_cast<int>(max - data) / channels;
392 QImage image(width, height, QImage::Format_ARGB32);
395 for (
int x = 0; x < width; ++x)
398 auto right = channels > 1 ? data[1] : left;
401 auto avg = qAbs(left) + qAbs(right);
402 double db = 20 * log10(
static_cast<double>(
avg ?
avg : 1) / range);
403 auto idb =
static_cast<int>(ceil(db));
404 auto rgb { qRgb(255, 0, 0) };
406 rgb = qRgb(255, 255, 255);
408 rgb = qRgb( 0, 255, 255);
410 rgb = qRgb( 0, 255, 0);
412 rgb = qRgb(255, 255, 0);
414 int v = height -
static_cast<int>(height * (db / threshold));
417 for (
int y = 0; y <= v; ++y)
418 image.setPixel(x, height - 1 - y, rgb);
const int kBufferMilliSecs
void Append(const void *Buffer, unsigned long Length, std::chrono::milliseconds Timecode, int Channels, int Bits)
static int BitsPerChannel()
bool EnoughData(std::chrono::milliseconds Timecode)
std::chrono::milliseconds m_tcNext
std::chrono::milliseconds Samples2MS(unsigned Samples) const
const int16_t * Data16(Range Available) const
void Resize(int Channels, int Bits)
Range Avail(std::chrono::milliseconds Timecode) const
void Append(const void *Buffer, unsigned long Length, int Bits)
std::chrono::milliseconds First() const
int Samples(Range Available) const
uint BytesPerSampleNormalized() const
std::pair< std::chrono::milliseconds, std::chrono::milliseconds > Range
unsigned Bytes2SamplesNormalized(unsigned Bytes) const
void SetMaxSamples(uint16_t Samples)
std::chrono::milliseconds Next() const
int MS2Samples(std::chrono::milliseconds Msecs) const
std::chrono::milliseconds m_tcFirst
unsigned Bytes2Samples(unsigned Bytes) const
uint BytesPerSample() const
void SetSampleRate(uint16_t SampleRate)
~AudioOutputGraph() override
MythImage * GetImage(std::chrono::milliseconds Timecode) const
void SetSampleRate(uint16_t SampleRate)
void SetSampleCount(uint16_t SampleCount)
void SetPainter(MythPainter *Painter)
void add(const void *Buffer, unsigned long Length, std::chrono::milliseconds Timecode, int Channnels, int Bits) override
void Assign(const QImage &img)
int DecrRef(void) override
Decrements reference count and deletes on 0.
static const std::array< const uint64_t, 4 > samples
static uint32_t avg(uint32_t A, uint32_t B)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)