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