MythTV master
videovisualspectrum.cpp
Go to the documentation of this file.
1// QT
2#include <QPen>
3
4// MythTV
6
7// FFmpeg
8extern "C" {
9#include "libavutil/mem.h"
10#include "libavutil/tx.h"
11}
12static constexpr int k_FFT_sample_length { 512 };
13
14// Std
15#include <algorithm>
16
18
20 : VideoVisual(Audio, Render),
21 m_dftL(static_cast<AVComplexFloat*>(av_malloc(sizeof(AVComplexFloat) * k_FFT_sample_length))),
22 m_dftR(static_cast<AVComplexFloat*>(av_malloc(sizeof(AVComplexFloat) * k_FFT_sample_length)))
23{
24 // If av_tx_init() fails (returns < 0), the contexts will be nullptr and will crash later,
25 // but av_malloc() is not checked to succeed either.
26 av_tx_init(&m_fftContext , &m_fft , AV_TX_FLOAT_FFT, 0, k_FFT_sample_length, &kScale, AV_TX_INPLACE);
27}
28
30{
31 av_freep(reinterpret_cast<void*>(&m_dftL));
32 av_freep(reinterpret_cast<void*>(&m_dftR));
33 av_tx_uninit(&m_fftContext);
34}
35
36template<typename T> T sq(T a) { return a*a; };
37
38void VideoVisualSpectrum::Draw(const QRect Area, MythPainter* Painter, QPaintDevice* Device)
39{
40 if (m_disabled)
41 return;
42
43 uint i = 0;
44 {
45 QMutexLocker locker(mutex());
46 VisualNode* node = GetNode();
47 if (Area.isEmpty() || !Painter)
48 return;
49
50 if (!Initialise(Area))
51 return;
52
53 if (node)
54 {
55 i = static_cast<uint>(node->m_length);
56 for (auto k = 0; k < node->m_length; k++)
57 {
58 m_dftL[k] = (AVComplexFloat){ .re = static_cast<float>(node->m_left[k]), .im = 0 };
59 if (node->m_right)
60 m_dftR[k] = (AVComplexFloat){ .re = static_cast<float>(node->m_right[k]), .im = 0 };
61 }
62 }
63 }
64
65 for (auto k = i; k < k_FFT_sample_length; k++)
66 {
67 m_dftL[k] = (AVComplexFloat){ .re = 0, .im = 0 };
68 m_dftR[k] = (AVComplexFloat){ .re = 0, .im = 0 };
69 }
70 m_fft(m_fftContext, m_dftL, m_dftL, sizeof(AVComplexFloat));
71 m_fft(m_fftContext, m_dftR, m_dftR, sizeof(AVComplexFloat));
72
73 double falloff = std::clamp(((static_cast<double>(SetLastUpdate().count())) / 40.0) * m_falloff, 0.0, 2048.0);
74 for (int l = 0, r = m_scale.range(); l < m_scale.range(); l++, r++)
75 {
76 int index = m_scale[l];
77
78 // The 1D output is Hermitian symmetric (Yk = Yn-k) so Yn = Y0 etc.
79 // The dft_r2c_1d plan doesn't output these redundant values
80 // and furthermore they're not allocated in the ctor
81 double tmp = 2 * sq(m_dftL[index].re);
82 double magL = (tmp > 1.) ? (log(tmp) - 22.0) * m_scaleFactor : 0.;
83
84 tmp = 2 * sq(m_dftR[index].re);
85 double magR = (tmp > 1.) ? (log(tmp) - 22.0) * m_scaleFactor : 0.;
86 if (magL > m_range)
87 magL = 1.0;
88
89 if (magL < m_magnitudes[l])
90 {
91 tmp = m_magnitudes[l] - falloff;
92 tmp = std::max(tmp, magL);
93 magL = tmp;
94 }
95
96 magL = std::max(magL, 1.0);
97
98 if (magR > m_range)
99 magR = 1.0;
100
101 if (magR < m_magnitudes[r])
102 {
103 tmp = m_magnitudes[r] - falloff;
104 tmp = std::max(tmp, magR);
105 magR = tmp;
106 }
107
108 magR = std::max(magR, 1.0);
109
110 m_magnitudes[l] = magL;
111 m_magnitudes[r] = magR;
112 }
113
114 DrawPriv(Painter, Device);
115}
116
118{
119 std::fill(m_magnitudes.begin(), m_magnitudes.end(), 0.0);
121}
122
124{
125 static const QBrush kBrush(QColor(0, 0, 200, 180));
126 static const QPen kPen(QColor(255, 255, 255, 255));
127 double range = m_area.top() + (m_area.height() / 2.0);
128 int count = m_scale.range();
129 Painter->Begin(Device);
130 for (int i = 0; i < count; i++)
131 {
132 m_rects[i].setTop(static_cast<int>(range - static_cast<int>(m_magnitudes[i])));
133 m_rects[i].setBottom(static_cast<int>(range + static_cast<int>(m_magnitudes[i + count])));
134 if (m_rects[i].height() > 4)
135 Painter->DrawRect(m_rects[i], kBrush, kPen, 255);
136 }
137 Painter->End();
138}
139
141{
142 if (Area == m_area)
143 return true;
144
145 m_area = Area;
146 m_barWidth = m_area.width() / m_numSamples;
147 m_barWidth = std::max(m_barWidth, 6);
148 m_scale.setMax(192, m_area.width() / m_barWidth);
149
150 m_magnitudes.resize(m_scale.range() * 2);
151 std::fill(m_magnitudes.begin(), m_magnitudes.end(), 0.0);
153 return true;
154}
155
157{
158 m_range = m_area.height() / 2.0;
159 m_rects.resize(m_scale.range());
160 int y = m_area.top() + static_cast<int>(m_range);
161 for (int i = 0, x = m_area.left(); i < m_rects.size(); i++, x+= m_barWidth)
162 m_rects[i].setRect(x, y, m_barWidth - 1, 1);
163
164 m_scaleFactor = (static_cast<double>(m_area.height()) / 2.0) / log(static_cast<double>(k_FFT_sample_length));
165 m_falloff = static_cast<double>(m_area.height()) / 150.0;
166
167 LOG(VB_GENERAL, LOG_INFO, DESC + QString("Initialised Spectrum with %1 bars").arg(m_scale.range()));
168 return true;
169}
170
172{
173 public:
174 const QString &name() const override;
175 VideoVisual *Create(AudioPlayer* Audio, MythRender* Render) const override;
176 bool SupportedRenderer(RenderType /*Type*/) override { return true; }
178
180{
181 static QString s_name(SPECTRUM_NAME);
182 return s_name;
183}
184
186{
187 return new VideoVisualSpectrum(Audio, Render);
188}
A device containing images (ie. USB stick, CD, storage group etc)
int range() const
void setMax(int maxscale, int maxrange)
virtual void DrawRect(QRect area, const QBrush &fillBrush, const QPen &linePen, int alpha)
virtual void Begin(QPaintDevice *)
Definition: mythpainter.h:54
virtual void End()
Definition: mythpainter.h:55
QMutex * mutex()
Definition: visual.h:26
const QString & name() const override
VideoVisual * Create(AudioPlayer *Audio, MythRender *Render) const override
bool SupportedRenderer(RenderType) override
static constexpr float kScale
AVTXContext * m_fftContext
QVector< QRect > m_rects
void Draw(QRect Area, MythPainter *Painter, QPaintDevice *Device) override
AVComplexFloat * m_dftR
AVComplexFloat * m_dftL
VideoVisualSpectrum(AudioPlayer *Audio, MythRender *Render)
virtual bool Initialise(QRect Area)
QVector< double > m_magnitudes
virtual void DrawPriv(MythPainter *Painter, QPaintDevice *Device)
VisualNode * GetNode(void)
Definition: videovisual.cpp:85
void prepare() override
Definition: videovisual.cpp:79
bool m_disabled
Definition: videovisual.h:70
std::chrono::milliseconds SetLastUpdate(void)
Definition: videovisual.cpp:60
QRect m_area
Definition: videovisual.h:71
long m_length
Definition: videovisual.h:38
short * m_left
Definition: videovisual.h:36
short * m_right
Definition: videovisual.h:37
unsigned int uint
Definition: freesurround.h:24
static guint32 * tmp
Definition: goom_core.cpp:26
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
RenderType
None log(str msg, int level=LOGDEBUG)
Definition: xbmc.py:9
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206
#define DESC
Definition: videovisual.h:19
T sq(T a)
VideoVisualSpectrumFactory VideoVisualSpectrumFactory
static constexpr int k_FFT_sample_length
#define SPECTRUM_NAME