diff --git a/mythtv/libs/libmythtv/visualisations/videovisual.cpp b/mythtv/libs/libmythtv/visualisations/videovisual.cpp new file mode 100644 index 00000000000..471b4b5defe --- /dev/null +++ b/mythtv/libs/libmythtv/visualisations/videovisual.cpp @@ -0,0 +1,147 @@ +#include "mythrender_base.h" +#include "mythplayer.h" +#include "videovisual.h" + +#ifdef FFTW3_SUPPORT +#include "videovisualspectrum.h" +#include "videovisualcircles.h" +#endif + +#ifdef USING_OPENGL +#include "mythrender_opengl2.h" +#endif + +bool VideoVisual::CanVisualise(AudioPlayer *audio, MythRender *render) +{ +#ifdef FFTW3_SUPPORT + if (render && audio->GetNumChannels() == 2) + return true; +#endif + return false; +} + +VideoVisual* VideoVisual::Create(AudioPlayer *audio, MythRender *render) +{ +#ifdef FFTW3_SUPPORT + if (render) + { +#ifdef USING_OPENGL + if (dynamic_cast(render)) + return new VideoVisualCircles(audio, render); +#endif + return new VideoVisualSpectrum(audio, render); + } +#endif + return NULL; +} + +VideoVisual::VideoVisual(AudioPlayer *audio, MythRender *render) + : m_audio(audio), m_disabled(false), m_area(QRect()), m_render(render) +{ + m_lastUpdate = QDateTime::currentDateTime(); + mutex()->lock(); + if (m_audio) + m_audio->addVisual(this); + mutex()->unlock(); +} + +VideoVisual::~VideoVisual() +{ + mutex()->lock(); + if (m_audio) + m_audio->removeVisual(this); + DeleteNodes(); + mutex()->unlock(); +} + +int64_t VideoVisual::SetLastUpdate(void) +{ + QDateTime now = QDateTime::currentDateTime(); + int64_t result = m_lastUpdate.msecsTo(now); + m_lastUpdate = now; + return result; +} + +// caller holds lock +void VideoVisual::DeleteNodes(void) +{ + while (!m_nodes.empty()) + { + delete m_nodes.back(); + m_nodes.pop_back(); + } +} + +// caller holds lock +void VideoVisual::prepare() +{ + DeleteNodes(); +} + +// caller holds lock +VisualNode* VideoVisual::GetNode(void) +{ + int64_t timestamp = m_audio->GetAudioTime(); + while (m_nodes.size() > 1) + { + if (m_nodes.front()->offset > timestamp) + break; + delete m_nodes.front(); + m_nodes.pop_front(); + } + + if (m_nodes.isEmpty()) + return NULL; + + return m_nodes.first(); +} + +// caller holds lock +void VideoVisual::add(uchar *b, unsigned long b_len, unsigned long w, int c, int p) +{ + if (!m_disabled && m_nodes.size() > 500) + { + VERBOSE(VB_GENERAL, DESC + + QString("Over 500 nodes buffered - disabling visualiser.")); + DeleteNodes(); + m_disabled = true; + } + + if (m_disabled) + return; + + long len = b_len, cnt; + short *l = 0, *r = 0; + + len /= c; + len /= (p / 8); + + if (len > 512) + len = 512; + + cnt = len; + + if (c == 2) + { + l = new short[len]; + r = new short[len]; + + if (p == 8) + stereo16_from_stereopcm8(l, r, b, cnt); + else if (p == 16) + stereo16_from_stereopcm16(l, r, (short *) b, cnt); + } + else if (c == 1) + { + l = new short[len]; + + if (p == 8) + mono16_from_monopcm8(l, b, cnt); + else if (p == 16) + mono16_from_monopcm16(l, (short *) b, cnt); + } + else + len = 0; + + m_nodes.append(new VisualNode(l, r, len, w)); +} diff --git a/mythtv/libs/libmythtv/visualisations/videovisual.h b/mythtv/libs/libmythtv/visualisations/videovisual.h new file mode 100644 index 00000000000..441e4bff0c1 --- /dev/null +++ b/mythtv/libs/libmythtv/visualisations/videovisual.h @@ -0,0 +1,62 @@ +#ifndef VIDEOVISUAL_H +#define VIDEOVISUAL_H + +#include +#include +#include "stdint.h" + +#include "mythverbose.h" +#include "visual.h" +#include "mythpainter.h" +#include "videovisualdefs.h" + +#define DESC QString("Visualiser: ") + +class MythRender; +class AudioPlayer; + +class VisualNode +{ + public: + VisualNode(short *l, short *r, unsigned long n, unsigned long o) + : left(l), right(r), length(n), offset(o) { } + + ~VisualNode() + { + delete [] left; + delete [] right; + } + + short *left, *right; + long length, offset; +}; + +class VideoVisual : public MythTV::Visual +{ + public: + static bool CanVisualise(AudioPlayer *audio, MythRender *render); + static VideoVisual* Create(AudioPlayer *audio, MythRender *render); + + VideoVisual(AudioPlayer *audio, MythRender *render); + ~VideoVisual(); + + virtual void Draw(const QRect &area, MythPainter *painter, + QPaintDevice* device) = 0; + + virtual void add(uchar *b, unsigned long b_len, unsigned long w, int c, int p); + virtual void prepare(); + + protected: + VisualNode* GetNode(void); + void DeleteNodes(void); + int64_t SetLastUpdate(void); + + AudioPlayer *m_audio; + bool m_disabled; + QRect m_area; + MythRender *m_render; + QList m_nodes; + QDateTime m_lastUpdate; +}; + +#endif // VIDEOVISUAL_H diff --git a/mythtv/libs/libmythtv/visualisations/videovisualcircles.cpp b/mythtv/libs/libmythtv/visualisations/videovisualcircles.cpp new file mode 100644 index 00000000000..b891575e6af --- /dev/null +++ b/mythtv/libs/libmythtv/visualisations/videovisualcircles.cpp @@ -0,0 +1,47 @@ +#include +#include "videovisualcircles.h" + +VideoVisualCircles::VideoVisualCircles(AudioPlayer *audio, MythRender *render) + : VideoVisualSpectrum(audio, render) +{ + m_numSamples = 32; +} + +void VideoVisualCircles::DrawPriv(MythPainter *painter, QPaintDevice* device) +{ + if (!painter) + return; + + static const QBrush nobrush(Qt::NoBrush); + int red = 0, green = 200; + QPen pen(QColor(red, green, 0, 255)); + int count = m_scale.range(); + int incr = 200 / count; + int rad = m_range; + QRect circ(m_area.x() + m_area.width() / 2, m_area.y() + m_area.height() / 2, + rad, rad); + painter->Begin(device); + for (int i = 0; i < count; i++, rad += m_range, red += incr, green -= incr) + { + double mag = abs((m_magnitudes[i] + m_magnitudes[i + count]) / 2.0); + if (mag > 1.0) + { + pen.setWidth((int)mag); + painter->DrawRoundRect(circ, rad, nobrush, pen, 200); + } + circ.adjust(-m_range, -m_range, m_range, m_range); + pen.setColor(QColor(red, green, 0, 255)); + } + painter->End(); +} + +bool VideoVisualCircles::InitialisePriv(void) +{ + m_range = (m_area.height() / 2) / (m_scale.range() -10); + m_scaleFactor = 10.0; + m_falloff = 1.0; + + VERBOSE(VB_GENERAL, DESC + QString("Initialised Circles with %1 circles.") + .arg(m_scale.range())); + return true; +} diff --git a/mythtv/libs/libmythtv/visualisations/videovisualcircles.h b/mythtv/libs/libmythtv/visualisations/videovisualcircles.h new file mode 100644 index 00000000000..fd75ddcfcd2 --- /dev/null +++ b/mythtv/libs/libmythtv/visualisations/videovisualcircles.h @@ -0,0 +1,16 @@ +#ifndef VIDEOVISUALCIRCLES_H +#define VIDEOVISUALCIRCLES_H + +#include "videovisualspectrum.h" + +class VideoVisualCircles : public VideoVisualSpectrum +{ + public: + VideoVisualCircles(AudioPlayer *audio, MythRender *render); + + protected: + virtual bool InitialisePriv(void); + virtual void DrawPriv(MythPainter *painter, QPaintDevice* device); +}; + +#endif // VIDEOVISUALCIRCLES_H diff --git a/mythtv/libs/libmythtv/visualisations/videovisualdefs.h b/mythtv/libs/libmythtv/visualisations/videovisualdefs.h new file mode 100644 index 00000000000..35a86ad0809 --- /dev/null +++ b/mythtv/libs/libmythtv/visualisations/videovisualdefs.h @@ -0,0 +1,314 @@ +#ifndef VIDEOVISUAL_DEFS_H +#define VIDEOVISUAL_DEFS_H + +#include + +class LogScale +{ + public: + LogScale(int maxscale = 0, int maxrange = 0) + : indices(0), s(0), r(0) + { + setMax(maxscale, maxrange); + } + + ~LogScale() + { + if (indices) + delete [] indices; + } + + int scale() const { return s; } + int range() const { return r; } + + void setMax(int maxscale, int maxrange) + { + if (maxscale == 0 || maxrange == 0) + return; + + s = maxscale; + r = maxrange; + + if (indices) + delete [] indices; + + double alpha; + int i, scaled; + long double domain = (long double) maxscale; + long double range = (long double) maxrange; + long double x = 1.0; + long double dx = 1.0; + long double y = 0.0; + long double yy = 0.0; + long double t = 0.0; + long double e4 = 1.0E-8; + + indices = new int[maxrange]; + for (i = 0; i < maxrange; i++) + indices[i] = 0; + + // initialize log scale + for (uint i = 0; i < 10000 && (std::abs(dx) > e4); i++) + { + t = std::log((domain + x) / x); + y = (x * t) - range; + yy = t - (domain / (x + domain)); + dx = y / yy; + x -= dx; + } + + alpha = x; + for (i = 1; i < (int) domain; i++) + { + scaled = (int) floor(0.5 + (alpha * log((double(i) + alpha) / alpha))); + if (scaled < 1) + scaled = 1; + if (indices[scaled - 1] < i) + indices[scaled - 1] = i; + } + } + + int operator[](int index) + { + return indices[index]; + } + + + private: + int *indices; + int s, r; +}; + +static inline void stereo16_from_stereopcm8(register short *l, + register short *r, + register uchar *c, + long cnt) +{ + while (cnt >= 4l) + { + l[0] = c[0]; + r[0] = c[1]; + l[1] = c[2]; + r[1] = c[3]; + l[2] = c[4]; + r[2] = c[5]; + l[3] = c[6]; + r[3] = c[7]; + l += 4; + r += 4; + c += 8; + cnt -= 4l; + } + + if (cnt > 0l) + { + l[0] = c[0]; + r[0] = c[1]; + if (cnt > 1l) + { + l[1] = c[2]; + r[1] = c[3]; + if (cnt > 2l) + { + l[2] = c[4]; + r[2] = c[5]; + } + } + } +} + +static inline void stereo16_from_stereopcm16(register short *l, + register short *r, + register short *s, + long cnt) +{ + while (cnt >= 4l) + { + l[0] = s[0]; + r[0] = s[1]; + l[1] = s[2]; + r[1] = s[3]; + l[2] = s[4]; + r[2] = s[5]; + l[3] = s[6]; + r[3] = s[7]; + l += 4; + r += 4; + s += 8; + cnt -= 4l; + } + + if (cnt > 0l) + { + l[0] = s[0]; + r[0] = s[1]; + if (cnt > 1l) + { + l[1] = s[2]; + r[1] = s[3]; + if (cnt > 2l) + { + l[2] = s[4]; + r[2] = s[5]; + } + } + } +} + +static inline void mono16_from_monopcm8(register short *l, + register uchar *c, + long cnt) +{ + while (cnt >= 4l) + { + l[0] = c[0]; + l[1] = c[1]; + l[2] = c[2]; + l[3] = c[3]; + l += 4; + c += 4; + cnt -= 4l; + } + + if (cnt > 0l) + { + l[0] = c[0]; + if (cnt > 1l) + { + l[1] = c[1]; + if (cnt > 2l) + { + l[2] = c[2]; + } + } + } +} + +static inline void mono16_from_monopcm16(register short *l, + register short *s, + long cnt) +{ + while (cnt >= 4l) + { + l[0] = s[0]; + l[1] = s[1]; + l[2] = s[2]; + l[3] = s[3]; + l += 4; + s += 4; + cnt -= 4l; + } + + if (cnt > 0l) + { + l[0] = s[0]; + if (cnt > 1l) + { + l[1] = s[1]; + if (cnt > 2l) + { + l[2] = s[2]; + } + } + } +} + +#ifdef FFTW3_SUPPORT + +#include +extern "C" { +#include +#define myth_fftw_float double /* need to use different plan function to change */ +#define fftw_real myth_fftw_float +#define myth_fftw_complex std::complex +#if (myth_fftw_float == double) +#define myth_fftw_complex_cast fftw_complex +#elif (myth_fftw_float == float) +#define myth_fftw_complex_cast fftwf_complex +#endif +} + +static inline void fast_short_set(register short *p, + short v, + long c) +{ + while (c >= 4l) { + p[0] = v; + p[1] = v; + p[2] = v; + p[3] = v; + p += 4; + c -= 4l; + } + + if (c > 0l) { + p[0] = v; + if (c > 1l) { + p[1] = v; + if (c > 2l) { + p[2] = v; + } + } + } +} + +static inline void fast_real_set_from_short(register fftw_real *d, + register short *s, + long c) +{ + while (c >= 4l) { + d[0] = fftw_real(s[0]); + d[1] = fftw_real(s[1]); + d[2] = fftw_real(s[2]); + d[3] = fftw_real(s[3]); + d += 4; + s += 4; + c -= 4l; + } + + if (c > 0l) { + d[0] = fftw_real(s[0]); + if (c > 1l) { + d[1] = fftw_real(s[1]); + if (c > 2l) { + d[2] = fftw_real(s[2]); + } + } + } +} + +static inline void fast_reals_set(register fftw_real *p1, + register fftw_real *p2, + fftw_real v, + long c) +{ + while (c >= 4l) { + p1[0] = v; + p1[1] = v; + p1[2] = v; + p1[3] = v; + p2[0] = v; + p2[1] = v; + p2[2] = v; + p2[3] = v; + p1 += 4; + p2 += 4; + c -= 4l; + } + + if (c > 0l) { + p1[0] = v; + p2[0] = v; + if (c > 1l) { + p1[1] = v; + p2[1] = v; + if (c > 2l) { + p1[2] = v; + p2[2] = v; + } + } + } +} + +#endif // FFTW3_SUPPORT +#endif // VIDEOVISUAL_DEFS_H diff --git a/mythtv/libs/libmythtv/visualisations/videovisualspectrum.cpp b/mythtv/libs/libmythtv/visualisations/videovisualspectrum.cpp new file mode 100644 index 00000000000..a9fb9b8337c --- /dev/null +++ b/mythtv/libs/libmythtv/visualisations/videovisualspectrum.cpp @@ -0,0 +1,172 @@ +#include + +#include "videovisualspectrum.h" + +#define FFTW_N 512 +extern "C" { +void *av_malloc(unsigned int size); +void av_free(void *ptr); +} + +VideoVisualSpectrum::VideoVisualSpectrum(AudioPlayer *audio, MythRender *render) + : VideoVisual(audio, render), m_range(1.0), m_scaleFactor(2.0), + m_falloff(3.0), m_barWidth(1) +{ + m_numSamples = 64; + lin = (myth_fftw_float*) av_malloc(sizeof(myth_fftw_float)*FFTW_N); + rin = (myth_fftw_float*) av_malloc(sizeof(myth_fftw_float)*FFTW_N); + lout = (myth_fftw_complex*) + av_malloc(sizeof(myth_fftw_complex)*(FFTW_N/2+1)); + rout = (myth_fftw_complex*) + av_malloc(sizeof(myth_fftw_complex)*(FFTW_N/2+1)); + + lplan = fftw_plan_dft_r2c_1d(FFTW_N, lin, (myth_fftw_complex_cast*)lout, FFTW_MEASURE); + rplan = fftw_plan_dft_r2c_1d(FFTW_N, rin, (myth_fftw_complex_cast*)rout, FFTW_MEASURE); +} + +VideoVisualSpectrum::~VideoVisualSpectrum() +{ + if (lin) + av_free(lin); + if (rin) + av_free(rin); + if (lout) + av_free(lout); + if (rout) + av_free(rout); + fftw_destroy_plan(lplan); + fftw_destroy_plan(rplan); +} + +template T sq(T a) { return a*a; }; + +void VideoVisualSpectrum::Draw(const QRect &area, MythPainter *painter, + QPaintDevice* device) +{ + if (m_disabled) + return; + + mutex()->lock(); + VisualNode *node = GetNode(); + + if (area.isEmpty() || !painter) + { + mutex()->unlock(); + return; + } + + if (!Initialise(area)) + { + mutex()->unlock(); + return; + } + + uint i = 0; + if (node) + { + i = node->length; + fast_real_set_from_short(lin, node->left, node->length); + if (node->right) + fast_real_set_from_short(rin, node->right, node->length); + } + mutex()->unlock(); + + fast_reals_set(lin + i, rin + i, 0, FFTW_N - i); + fftw_execute(lplan); + fftw_execute(rplan); + + double magL, magR, tmp; + double falloff = (((double)SetLastUpdate()) / 40.0) * m_falloff; + for (int l = 0, r = m_scale.range(); l < m_scale.range(); l++, r++) + { + int index = m_scale[l]; + magL = (log(sq(real(lout[index])) + sq(real(lout[FFTW_N - index]))) - 22.0) * + m_scaleFactor; + magR = (log(sq(real(rout[index])) + sq(real(rout[FFTW_N - index]))) - 22.0) * + m_scaleFactor; + + if (magL > m_range) + magL = 1.0; + + if (magL < m_magnitudes[l]) + { + tmp = m_magnitudes[l] - falloff; + if (tmp < magL) + tmp = magL; + magL = tmp; + } + + if (magL < 1.0) + magL = 1.0; + + if (magR > m_range) + magR = 1.0; + + if (magR < m_magnitudes[r]) + { + tmp = m_magnitudes[r] - falloff; + if (tmp < magR) + tmp = magR; + magR = tmp; + } + + if (magR < 1.0) + magR = 1.0; + + m_magnitudes[l] = magL; + m_magnitudes[r] = magR; + } + + DrawPriv(painter, device); +} + +void VideoVisualSpectrum::DrawPriv(MythPainter *painter, QPaintDevice* device) +{ + static const QBrush brush(QColor(0, 0, 200, 180)); + static const QPen pen(QColor(255, 255, 255, 255)); + double range = m_area.height() / 2.0; + int count = m_scale.range(); + painter->Begin(device); + for (int i = 0; i < count; i++) + { + m_rects[i].setTop(range - int(m_magnitudes[i])); + m_rects[i].setBottom(range + int(m_magnitudes[i + count])); + if (m_rects[i].height() > 4) + painter->DrawRect(m_rects[i], brush, pen, 255); + } + painter->End(); +} + +bool VideoVisualSpectrum::Initialise(const QRect &area) +{ + if (area == m_area) + return true; + + m_area = area; + m_barWidth = m_area.width() / m_numSamples; + if (m_barWidth < 6) + m_barWidth = 6; + m_scale.setMax(192, m_area.width() / m_barWidth); + + m_magnitudes.resize(m_scale.range() * 2); + for (int i = 0; i < m_magnitudes.size(); i++) + m_magnitudes[i] = 0.0; + + InitialisePriv(); + return true; +} + +bool VideoVisualSpectrum::InitialisePriv(void) +{ + m_range = m_area.height() / 2.0; + m_rects.resize(m_scale.range()); + for (int i = 0, x = 0; i < m_rects.size(); i++, x+= m_barWidth) + m_rects[i].setRect(x, m_area.height() / 2, m_barWidth - 1, 1); + + m_scaleFactor = double(m_area.height() / 2) / log((double)(FFTW_N)); + m_falloff = (double)m_area.height() / 150.0; + + VERBOSE(VB_GENERAL, DESC + QString("Initialised Spectrum with %1 bars") + .arg(m_scale.range())); + return true; +} diff --git a/mythtv/libs/libmythtv/visualisations/videovisualspectrum.h b/mythtv/libs/libmythtv/visualisations/videovisualspectrum.h new file mode 100644 index 00000000000..87356025a5d --- /dev/null +++ b/mythtv/libs/libmythtv/visualisations/videovisualspectrum.h @@ -0,0 +1,40 @@ +#ifndef VIDEOVISUALSPECTRUM_H +#define VIDEOVISUALSPECTRUM_H + +#include +#include "videovisual.h" + +class VideoVisualSpectrum : public VideoVisual +{ + public: + VideoVisualSpectrum(AudioPlayer *audio, MythRender *render); + virtual ~VideoVisualSpectrum(); + + virtual void Draw(const QRect &area, MythPainter *painter, + QPaintDevice* device); + + protected: + virtual bool Initialise(const QRect &area); + virtual bool InitialisePriv(void); + virtual void DrawPriv(MythPainter *painter, QPaintDevice* device); + + int m_numSamples; + QVector m_magnitudes; + double m_range; + LogScale m_scale; + double m_scaleFactor; + double m_falloff; + + fftw_plan lplan; + fftw_plan rplan; + myth_fftw_float *lin; + myth_fftw_float *rin; + myth_fftw_complex *lout; + myth_fftw_complex *rout; + + private: + QVector m_rects; + int m_barWidth; +}; + +#endif // VIDEOVISUALSPECTRUM_H