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