MythTV  master
audiooutputgraph.cpp
Go to the documentation of this file.
1 // Std
2 #include <climits>
3 #include <cmath>
4 #include <cstdint>
5 
6 // Qt
7 #include <QtGlobal>
8 #include <QImage>
9 #include <QByteArray>
10 #include <QPair>
11 #include <QMutexLocker>
12 
13 // MythTV
14 #include "libmythbase/compat.h"
16 #include "libmythui/mythimage.h"
17 #include "libmythui/mythpainter.h"
18 
19 #include "audiooutputgraph.h"
20 
21 using namespace std::chrono_literals;
22 
23 #define LOC QString("AOG: ")
24 
25 const int kBufferMilliSecs = 500;
26 
27 class AudioOutputGraph::AOBuffer : public QByteArray
28 {
29  public:
30  AOBuffer() = default;
31 
32  void SetMaxSamples(uint16_t Samples) { m_maxSamples = Samples; }
33  void SetSampleRate(uint16_t SampleRate) { m_sampleRate = SampleRate; }
34  static int BitsPerChannel() { return sizeof(short) * CHAR_BIT; }
35  int Channels() const { return m_channels; }
36  std::chrono::milliseconds Next() const { return m_tcNext; }
37  std::chrono::milliseconds First() const { return m_tcFirst; }
38 
39  using Range = std::pair<std::chrono::milliseconds, std::chrono::milliseconds>;
40  Range Avail(std::chrono::milliseconds Timecode) const
41  {
42  if (Timecode == 0ms || Timecode == -1ms)
43  Timecode = m_tcNext;
44 
45  std::chrono::milliseconds first = Timecode - Samples2MS(m_maxSamples / 2);
46  if (first < m_tcFirst)
47  first = m_tcFirst;
48 
49  std::chrono::milliseconds second = first + Samples2MS(m_maxSamples);
50  if (second > m_tcNext)
51  {
52  second = m_tcNext;
53  if (second < first + Samples2MS(m_maxSamples))
54  {
55  first = second - Samples2MS(m_maxSamples);
56  if (first < m_tcFirst)
57  first = m_tcFirst;
58  }
59  }
60  return {first, second};
61  }
62 
63  int Samples(Range Available) const
64  {
65  return MS2Samples(Available.second - Available.first);
66  }
67 
68  void Empty()
69  {
70  m_tcFirst = m_tcNext = 0ms;
71  m_bits = m_channels = 0;
72  resize(0);
73  }
74 
75  void Append(const void * Buffer, unsigned long Length, std::chrono::milliseconds Timecode,
76  int Channels, int Bits)
77  {
78  if (m_bits != Bits || m_channels != Channels)
79  {
80  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("%1, %2 channels, %3 bits")
81  .arg(Timecode.count()).arg(Channels).arg(Bits));
82  Resize(Channels, Bits);
83  m_tcNext = m_tcFirst = Timecode;
84  }
85 
86  auto samples = Bytes2Samples(static_cast<unsigned>(Length));
87  std::chrono::milliseconds tcNext = Timecode + Samples2MS(samples);
88 
89  if (qAbs((Timecode - m_tcNext).count()) <= 1)
90  {
91  Append(Buffer, Length, Bits);
92  m_tcNext = tcNext;
93  }
94  else if (Timecode >= m_tcFirst && tcNext <= m_tcNext)
95  {
96  // Duplicate
97  return;
98  }
99  else
100  {
101  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Discontinuity %1 -> %2")
102  .arg(m_tcNext.count()).arg(Timecode.count()));
103 
104  Resize(Channels, Bits);
105  Append(Buffer, Length, Bits);
106  m_tcFirst = Timecode;
107  m_tcNext = tcNext;
108  }
109 
110  auto overflow = size() - m_sizeMax;
111  if (overflow > 0)
112  {
113  remove(0, overflow);
114  m_tcFirst = m_tcNext - Samples2MS(Bytes2Samples(static_cast<unsigned>(m_sizeMax)));
115  }
116  }
117 
118  const int16_t* Data16(Range Available) const
119  {
120  auto start = MS2Samples(Available.first - m_tcFirst);
121  return reinterpret_cast<const int16_t*>(constData() + (static_cast<ptrdiff_t>(start) * BytesPerSample()));
122  }
123 
124  protected:
126  {
127  return static_cast<uint>(m_channels * ((m_bits + 7) / 8));
128  }
129 
130  unsigned Bytes2Samples(unsigned Bytes) const
131  {
132  return (m_channels && m_bits) ? Bytes / BytesPerSample() : 0;
133  }
134 
135  std::chrono::milliseconds Samples2MS(unsigned Samples) const
136  {
137  return m_sampleRate ? std::chrono::milliseconds((Samples * 1000UL + m_sampleRate - 1) / m_sampleRate) : 0ms; // round up
138  }
139 
140  int MS2Samples(std::chrono::milliseconds Msecs) const
141  {
142  return Msecs > 0ms ? static_cast<int>((Msecs.count() * m_sampleRate) / 1000) : 0; // NB round down
143  }
144 
145  void Append(const void * Buffer, unsigned long Length, int Bits)
146  {
147  switch (Bits)
148  {
149  case 8:
150  // 8bit unsigned to 16bit signed
151  {
152  auto count = Length;
153  auto n = size();
154  resize(n + static_cast<int>(sizeof(int16_t) * count));
155  const auto * src = reinterpret_cast<const uchar*>(Buffer);
156  auto * dst = reinterpret_cast<int16_t*>(data() + n);
157  while (count--)
158  *dst++ = static_cast<int16_t>((static_cast<int16_t>(*src++) - CHAR_MAX) << (16 - CHAR_BIT));
159  }
160  break;
161  case 16:
162  append(reinterpret_cast<const char*>(Buffer), static_cast<int>(Length));
163  break;
164  case 32:
165  // 32bit float to 16bit signed
166  {
167  unsigned long count = Length / sizeof(float);
168  auto n = size();
169  resize(n + static_cast<int>(sizeof(int16_t) * count));
170  const float f((1 << 15) - 1);
171  const auto * src = reinterpret_cast<const float*>(Buffer);
172  auto * dst = reinterpret_cast<int16_t*>(data() + n);
173  while (count--)
174  *dst++ = static_cast<int16_t>(f * (*src++));
175  }
176  break;
177  default:
178  append(reinterpret_cast<const char*>(Buffer), static_cast<int>(Length));
179  break;
180  }
181  }
182 
183  private:
184  void Resize(int Channels, int Bits)
185  {
186  m_bits = Bits;
187  m_channels = Channels;
188  m_sizeMax = static_cast<int>(((m_sampleRate * kBufferMilliSecs) / 1000) * BytesPerSample());
189  resize(0);
190  }
191 
192  private:
193  std::chrono::milliseconds m_tcFirst { 0ms };
194  std::chrono::milliseconds m_tcNext { 0ms };
195  uint16_t m_maxSamples { 0 };
196  uint16_t m_sampleRate { 44100 };
197  int m_bits { 0 };
198  int m_channels { 0 };
199  int m_sizeMax { 0 };
200 };
201 
203  m_buffer(new AudioOutputGraph::AOBuffer())
204 {
205 }
206 
208 {
209  delete m_buffer;
210 }
211 
213 {
214  QMutexLocker lock(&m_mutex);
215  m_painter = Painter;
216 }
217 
219 {
220  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Set sample rate %1)").arg(SampleRate));
221  QMutexLocker lock(&m_mutex);
223 }
224 
226 {
227  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Set sample count %1").arg(SampleCount));
228  QMutexLocker lock(&m_mutex);
229  m_buffer->SetMaxSamples(SampleCount);
230 }
231 
233 {
234 }
235 
236 void AudioOutputGraph::add(const void * Buffer, unsigned long Length,
237  std::chrono::milliseconds Timecode, int Channnels, int Bits)
238 {
239  QMutexLocker lock(&m_mutex);
240  m_buffer->Append(Buffer, Length, Timecode, Channnels, Bits);
241 }
242 
244 {
245  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Reset");
246  QMutexLocker lock(&m_mutex);
247  m_buffer->Empty();
248 }
249 
250 MythImage* AudioOutputGraph::GetImage(std::chrono::milliseconds Timecode) const
251 {
252  QMutexLocker lock(&m_mutex);
253  AOBuffer::Range avail = m_buffer->Avail(Timecode);
254 
255  LOG(VB_PLAYBACK, LOG_INFO, LOC +
256  QString("GetImage for timecode %1 using [%2..%3] available [%4..%5]")
257  .arg(Timecode.count()).arg(avail.first.count()).arg(avail.second.count())
258  .arg(m_buffer->First().count()).arg(m_buffer->Next().count()) );
259 
260  auto width = m_buffer->Samples(avail);
261  if (width <= 0)
262  return nullptr;
263 
264  const unsigned range = 1U << AudioOutputGraph::AOBuffer::BitsPerChannel();
265  const auto threshold = 20 * log10(1.0 / range); // 16bit=-96.3296dB => ~6dB/bit
266  auto height = static_cast<int>(-ceil(threshold)); // 96
267  if (height <= 0)
268  return nullptr;
269 
270  const int channels = m_buffer->Channels();
271 
272  // Assume signed 16 bit/sample
273  const auto * data = m_buffer->Data16(avail);
274  const auto * max = reinterpret_cast<const int16_t*>(m_buffer->constData() + m_buffer->size());
275  if (data >= max)
276  return nullptr;
277 
278  if ((data + (channels * static_cast<ptrdiff_t>(width))) >= max)
279  {
280  LOG(VB_GENERAL, LOG_WARNING, LOC + "Buffer overflow. Clipping samples.");
281  width = static_cast<int>(max - data) / channels;
282  }
283 
284  QImage image(width, height, QImage::Format_ARGB32);
285  image.fill(0);
286 
287  for (int x = 0; x < width; ++x)
288  {
289  auto left = data[0];
290  auto right = channels > 1 ? data[1] : left;
291  data += channels;
292 
293  auto avg = qAbs(left) + qAbs(right);
294  double db = 20 * log10(static_cast<double>(avg ? avg : 1) / range);
295  auto idb = static_cast<int>(ceil(db));
296  auto rgb { qRgb(255, 0, 0) };
297  if (idb <= m_dBsilence)
298  rgb = qRgb(255, 255, 255);
299  else if (idb <= m_dBquiet)
300  rgb = qRgb( 0, 255, 255);
301  else if (idb <= m_dBLoud)
302  rgb = qRgb( 0, 255, 0);
303  else if (idb <= m_dbMax)
304  rgb = qRgb(255, 255, 0);
305 
306  int v = height - static_cast<int>(height * (db / threshold));
307  if (v >= height)
308  v = height - 1;
309  for (int y = 0; y <= v; ++y)
310  image.setPixel(x, height - 1 - y, rgb);
311  }
312 
313  auto * result = new MythImage(m_painter);
314  result->Assign(image);
315  return result;
316 }
AudioOutputGraph::AOBuffer::SetSampleRate
void SetSampleRate(uint16_t SampleRate)
Definition: audiooutputgraph.cpp:33
AudioOutputGraph::add
void add(const void *Buffer, unsigned long Length, std::chrono::milliseconds Timecode, int Channnels, int Bits) override
Definition: audiooutputgraph.cpp:236
kBufferMilliSecs
const int kBufferMilliSecs
Definition: audiooutputgraph.cpp:25
AudioOutputGraph::AOBuffer::BytesPerSample
uint BytesPerSample() const
Definition: audiooutputgraph.cpp:125
AudioOutputGraph
Definition: audiooutputgraph.h:14
AudioOutputGraph::SetSampleCount
void SetSampleCount(uint16_t SampleCount)
Definition: audiooutputgraph.cpp:225
AudioOutputGraph::AOBuffer::Empty
void Empty()
Definition: audiooutputgraph.cpp:68
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
AudioOutputGraph::AOBuffer::MS2Samples
int MS2Samples(std::chrono::milliseconds Msecs) const
Definition: audiooutputgraph.cpp:140
SampleRate
Definition: recordingprofile.cpp:110
AudioOutputGraph::AOBuffer::First
std::chrono::milliseconds First() const
Definition: audiooutputgraph.cpp:37
AudioOutputGraph::prepare
void prepare() override
Definition: audiooutputgraph.cpp:232
AudioOutputGraph::AOBuffer::BitsPerChannel
static int BitsPerChannel()
Definition: audiooutputgraph.cpp:34
AudioOutputGraph::SetSampleRate
void SetSampleRate(uint16_t SampleRate)
Definition: audiooutputgraph.cpp:218
AudioOutputGraph::~AudioOutputGraph
~AudioOutputGraph() override
Definition: audiooutputgraph.cpp:207
AudioOutputGraph::AOBuffer::Samples2MS
std::chrono::milliseconds Samples2MS(unsigned Samples) const
Definition: audiooutputgraph.cpp:135
AudioOutputGraph::Reset
void Reset()
Definition: audiooutputgraph.cpp:243
LOC
#define LOC
Definition: audiooutputgraph.cpp:23
mythlogging.h
AudioOutputGraph::GetImage
MythImage * GetImage(std::chrono::milliseconds Timecode) const
Definition: audiooutputgraph.cpp:250
AudioOutputGraph::AOBuffer::Channels
int Channels() const
Definition: audiooutputgraph.cpp:35
compat.h
AudioOutputGraph::AOBuffer::Resize
void Resize(int Channels, int Bits)
Definition: audiooutputgraph.cpp:184
AudioOutputGraph::AudioOutputGraph
AudioOutputGraph()
Definition: audiooutputgraph.cpp:202
AudioOutputGraph::AOBuffer::Range
std::pair< std::chrono::milliseconds, std::chrono::milliseconds > Range
Definition: audiooutputgraph.cpp:39
AudioOutputGraph::m_dbMax
int m_dbMax
Definition: audiooutputgraph.h:40
AudioOutputGraph::AOBuffer
Definition: audiooutputgraph.cpp:27
AudioOutputGraph::AOBuffer::Append
void Append(const void *Buffer, unsigned long Length, int Bits)
Definition: audiooutputgraph.cpp:145
AudioOutputGraph::AOBuffer::Samples
int Samples(Range Available) const
Definition: audiooutputgraph.cpp:63
mythpainter.h
audiooutputgraph.h
uint
unsigned int uint
Definition: compat.h:81
AudioOutputGraph::AOBuffer::Next
std::chrono::milliseconds Next() const
Definition: audiooutputgraph.cpp:36
AudioOutputGraph::m_dBquiet
int m_dBquiet
Definition: audiooutputgraph.h:38
AudioOutputGraph::AOBuffer::Data16
const int16_t * Data16(Range Available) const
Definition: audiooutputgraph.cpp:118
AudioOutputGraph::AOBuffer::Avail
Range Avail(std::chrono::milliseconds Timecode) const
Definition: audiooutputgraph.cpp:40
AudioOutputGraph::m_dBLoud
int m_dBLoud
Definition: audiooutputgraph.h:39
mythimage.h
Buffer
Definition: MythExternControl.h:36
MythPainter
Definition: mythpainter.h:34
MythImage
Definition: mythimage.h:36
AudioOutputGraph::m_painter
MythPainter * m_painter
Definition: audiooutputgraph.h:36
AudioOutputGraph::SetPainter
void SetPainter(MythPainter *Painter)
Definition: audiooutputgraph.cpp:212
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
AudioOutputGraph::AOBuffer::Append
void Append(const void *Buffer, unsigned long Length, std::chrono::milliseconds Timecode, int Channels, int Bits)
Definition: audiooutputgraph.cpp:75
AudioOutputGraph::m_buffer
AOBuffer *const m_buffer
Definition: audiooutputgraph.h:42
AudioOutputGraph::AOBuffer::Bytes2Samples
unsigned Bytes2Samples(unsigned Bytes) const
Definition: audiooutputgraph.cpp:130
avg
static uint32_t avg(uint32_t A, uint32_t B)
Definition: mythdeinterlacer.cpp:469
AudioOutputGraph::m_dBsilence
int m_dBsilence
Definition: audiooutputgraph.h:37
samples
static const std::array< const uint64_t, 4 > samples
Definition: element.cpp:46
AudioOutputGraph::AOBuffer::SetMaxSamples
void SetMaxSamples(uint16_t Samples)
Definition: audiooutputgraph.cpp:32
AudioOutputGraph::m_mutex
QMutex m_mutex
Definition: audiooutputgraph.h:43