mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-18 15:51:11 +00:00
I'm too lazy to comment this
Original commit message from CVS: *** empty log message ***
This commit is contained in:
parent
b968efd71b
commit
ff816a1475
9 changed files with 915 additions and 1 deletions
|
@ -165,7 +165,7 @@ GST_PLUGINS_ALL="\
|
||||||
cutter deinterlace flx goom intfloat law level\
|
cutter deinterlace flx goom intfloat law level\
|
||||||
median mpeg1enc mpeg1sys mpeg1videoparse mpeg2enc mpeg2sub\
|
median mpeg1enc mpeg1sys mpeg1videoparse mpeg2enc mpeg2sub\
|
||||||
mpegaudio mpegaudioparse mpegstream mpegtypes modplug\
|
mpegaudio mpegaudioparse mpegstream mpegtypes modplug\
|
||||||
passthrough playondemand rtjpeg silence sine\
|
monoscope passthrough playondemand rtjpeg silence sine\
|
||||||
smooth spectrum speed stereo stereomono\
|
smooth spectrum speed stereo stereomono\
|
||||||
synaesthesia udp videoscale volenv volume vumeter wavparse y4m"
|
synaesthesia udp videoscale volenv volume vumeter wavparse y4m"
|
||||||
|
|
||||||
|
@ -734,6 +734,7 @@ gst/mpegstream/Makefile
|
||||||
gst/mpegtypes/Makefile
|
gst/mpegtypes/Makefile
|
||||||
gst/modplug/Makefile
|
gst/modplug/Makefile
|
||||||
gst/modplug/libmodplug/Makefile
|
gst/modplug/libmodplug/Makefile
|
||||||
|
gst/monoscope/Makefile
|
||||||
gst/passthrough/Makefile
|
gst/passthrough/Makefile
|
||||||
gst/playondemand/Makefile
|
gst/playondemand/Makefile
|
||||||
gst/rtjpeg/Makefile
|
gst/rtjpeg/Makefile
|
||||||
|
|
7
gst/monoscope/.gitignore
vendored
Normal file
7
gst/monoscope/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Makefile
|
||||||
|
Makefile.in
|
||||||
|
*.o
|
||||||
|
*.lo
|
||||||
|
*.la
|
||||||
|
.deps
|
||||||
|
.libs
|
12
gst/monoscope/Makefile.am
Normal file
12
gst/monoscope/Makefile.am
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
plugindir = $(libdir)/gst
|
||||||
|
|
||||||
|
plugin_LTLIBRARIES = libgstmonoscope.la
|
||||||
|
|
||||||
|
libgstmonoscope_la_SOURCES = gstmonoscope.c monoscope.c convolve.c
|
||||||
|
|
||||||
|
noinst_HEADERS = monoscope.h convolve.h
|
||||||
|
|
||||||
|
libgstmonoscope_la_CFLAGS = -O2 -ffast-math $(GST_CFLAGS)
|
||||||
|
libgstmonoscope_la_LIBADD = $(GST_LIBS)
|
||||||
|
libgstmonoscope_la_LDFLAGS = @GST_PLUGIN_LDFLAGS@
|
||||||
|
|
11
gst/monoscope/README
Normal file
11
gst/monoscope/README
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
This is a visualization based on on the monoscope output plugin from
|
||||||
|
alsaplayer.
|
||||||
|
|
||||||
|
The monoscope output plugin was written primarily by Ralph Loader.
|
||||||
|
|
||||||
|
This implementation is taken from alsaplayer version 0.99.54, at
|
||||||
|
http://www.alsaplayer.org/
|
||||||
|
|
||||||
|
Note: only one instance of this plugin may be created at a time: it has a
|
||||||
|
lot of static data. This should be fixed (and it shouldn't be hard to do
|
||||||
|
so, either).
|
316
gst/monoscope/convolve.c
Normal file
316
gst/monoscope/convolve.c
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
/* Karatsuba convolution
|
||||||
|
*
|
||||||
|
* Copyright (C) 1999 Ralph Loader <suckfish@ihug.co.nz>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* The algorithm is based on the following. For the convolution of a pair
|
||||||
|
* of pairs, (a,b) * (c,d) = (0, a.c, a.d+b.c, b.d), we can reduce the four
|
||||||
|
* multiplications to three, by the formulae a.d+b.c = (a+b).(c+d) - a.c -
|
||||||
|
* b.d. A similar relation enables us to compute a 2n by 2n convolution
|
||||||
|
* using 3 n by n convolutions, and thus a 2^n by 2^n convolution using 3^n
|
||||||
|
* multiplications (as opposed to the 4^n that the quadratic algorithm
|
||||||
|
* takes. */
|
||||||
|
|
||||||
|
/* For large n, this is slower than the O(n log n) that the FFT method
|
||||||
|
* takes, but we avoid using complex numbers, and we only have to compute
|
||||||
|
* one convolution, as opposed to 3 FFTs. We have good locality-of-
|
||||||
|
* reference as well, which will help on CPUs with tiny caches. */
|
||||||
|
|
||||||
|
/* E.g., for a 512 x 512 convolution, the FFT method takes 55 * 512 = 28160
|
||||||
|
* (real) multiplications, as opposed to 3^9 = 19683 for the Karatsuba
|
||||||
|
* algorithm. We actually want 257 outputs of a 256 x 512 convolution;
|
||||||
|
* that doesn't appear to give an easy advantage for the FFT algorithm, but
|
||||||
|
* for the Karatsuba algorithm, it's easy to use two 256 x 256
|
||||||
|
* convolutions, taking 2 x 3^8 = 12312 multiplications. [This difference
|
||||||
|
* is that the FFT method "wraps" the arrays, doing a 2^n x 2^n -> 2^n,
|
||||||
|
* while the Karatsuba algorithm pads with zeros, doing 2^n x 2^n -> 2.2^n
|
||||||
|
* - 1]. */
|
||||||
|
|
||||||
|
/* There's a big lie above, actually... for a 4x4 convolution, it's quicker
|
||||||
|
* to do it using 16 multiplications than the more complex Karatsuba
|
||||||
|
* algorithm... So the recursion bottoms out at 4x4s. This increases the
|
||||||
|
* number of multiplications by a factor of 16/9, but reduces the overheads
|
||||||
|
* dramatically. */
|
||||||
|
|
||||||
|
/* The convolution algorithm is implemented as a stack machine. We have a
|
||||||
|
* stack of commands, each in one of the forms "do a 2^n x 2^n
|
||||||
|
* convolution", or "combine these three length 2^n outputs into one
|
||||||
|
* 2^{n+1} output." */
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "convolve.h"
|
||||||
|
|
||||||
|
typedef union stack_entry_s {
|
||||||
|
struct {const double * left, * right; double * out;} v;
|
||||||
|
struct {double * main, * null;} b;
|
||||||
|
|
||||||
|
} stack_entry;
|
||||||
|
|
||||||
|
#define STACK_SIZE (CONVOLVE_DEPTH * 3)
|
||||||
|
|
||||||
|
struct _struct_convolve_state {
|
||||||
|
double left [CONVOLVE_BIG];
|
||||||
|
double right [CONVOLVE_SMALL * 3];
|
||||||
|
double scratch [CONVOLVE_SMALL * 3];
|
||||||
|
stack_entry stack[STACK_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialisation routine - sets up tables and space to work in.
|
||||||
|
* Returns a pointer to internal state, to be used when performing calls.
|
||||||
|
* On error, returns NULL.
|
||||||
|
* The pointer should be freed when it is finished with, by convolve_close().
|
||||||
|
*/
|
||||||
|
convolve_state *convolve_init(void)
|
||||||
|
{
|
||||||
|
return (convolve_state *) malloc (sizeof(convolve_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free the state allocated with convolve_init().
|
||||||
|
*/
|
||||||
|
void convolve_close(convolve_state *state)
|
||||||
|
{
|
||||||
|
if (state)
|
||||||
|
free(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convolve_4 (double * out, const double * left, const double * right)
|
||||||
|
/* This does a 4x4 -> 7 convolution. For what it's worth, the slightly odd
|
||||||
|
* ordering gives about a 1% speed up on my Pentium II. */
|
||||||
|
{
|
||||||
|
double l0, l1, l2, l3, r0, r1, r2, r3;
|
||||||
|
double a;
|
||||||
|
l0 = left[0];
|
||||||
|
r0 = right[0];
|
||||||
|
a = l0 * r0;
|
||||||
|
l1 = left[1];
|
||||||
|
r1 = right[1];
|
||||||
|
out[0] = a;
|
||||||
|
a = (l0 * r1) + (l1 * r0);
|
||||||
|
l2 = left[2];
|
||||||
|
r2 = right[2];
|
||||||
|
out[1] = a;
|
||||||
|
a = (l0 * r2) + (l1 * r1) + (l2 * r0);
|
||||||
|
l3 = left[3];
|
||||||
|
r3 = right[3];
|
||||||
|
out[2] = a;
|
||||||
|
|
||||||
|
out[3] = (l0 * r3) + (l1 * r2) + (l2 * r1) + (l3 * r0);
|
||||||
|
out[4] = (l1 * r3) + (l2 * r2) + (l3 * r1);
|
||||||
|
out[5] = (l2 * r3) + (l3 * r2);
|
||||||
|
out[6] = l3 * r3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convolve_run (stack_entry * top, unsigned size, double * scratch)
|
||||||
|
/* Interpret a stack of commands. The stack starts with two entries; the
|
||||||
|
* convolution to do, and an illegal entry used to mark the stack top. The
|
||||||
|
* size is the number of entries in each input, and must be a power of 2,
|
||||||
|
* and at least 8. It is OK to have out equal to left and/or right.
|
||||||
|
* scratch must have length 3*size. The number of stack entries needed is
|
||||||
|
* 3n-4 where size=2^n. */
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
const double * left;
|
||||||
|
const double * right;
|
||||||
|
double * out;
|
||||||
|
|
||||||
|
/* When we get here, the stack top is always a convolve,
|
||||||
|
* with size > 4. So we will split it. We repeatedly split
|
||||||
|
* the top entry until we get to size = 4. */
|
||||||
|
|
||||||
|
left = top->v.left;
|
||||||
|
right = top->v.right;
|
||||||
|
out = top->v.out;
|
||||||
|
top++;
|
||||||
|
|
||||||
|
do {
|
||||||
|
double * s_left, * s_right;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Halve the size. */
|
||||||
|
size >>= 1;
|
||||||
|
|
||||||
|
/* Allocate the scratch areas. */
|
||||||
|
s_left = scratch + size * 3;
|
||||||
|
/* s_right is a length 2*size buffer also used for
|
||||||
|
* intermediate output. */
|
||||||
|
s_right = scratch + size * 4;
|
||||||
|
|
||||||
|
/* Create the intermediate factors. */
|
||||||
|
for (i = 0; i < size; i++) {
|
||||||
|
double l = left[i] + left[i + size];
|
||||||
|
double r = right[i] + right[i + size];
|
||||||
|
s_left[i + size] = r;
|
||||||
|
s_left[i] = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Push the combine entry onto the stack. */
|
||||||
|
top -= 3;
|
||||||
|
top[2].b.main = out;
|
||||||
|
top[2].b.null = NULL;
|
||||||
|
|
||||||
|
/* Push the low entry onto the stack. This must be
|
||||||
|
* the last of the three sub-convolutions, because
|
||||||
|
* it may overwrite the arguments. */
|
||||||
|
top[1].v.left = left;
|
||||||
|
top[1].v.right = right;
|
||||||
|
top[1].v.out = out;
|
||||||
|
|
||||||
|
/* Push the mid entry onto the stack. */
|
||||||
|
top[0].v.left = s_left;
|
||||||
|
top[0].v.right = s_right;
|
||||||
|
top[0].v.out = s_right;
|
||||||
|
|
||||||
|
/* Leave the high entry in variables. */
|
||||||
|
left += size;
|
||||||
|
right += size;
|
||||||
|
out += size * 2;
|
||||||
|
|
||||||
|
} while (size > 4);
|
||||||
|
|
||||||
|
/* When we get here, the stack top is a group of 3
|
||||||
|
* convolves, with size = 4, followed by some combines. */
|
||||||
|
convolve_4 (out, left, right);
|
||||||
|
convolve_4 (top[0].v.out, top[0].v.left, top[0].v.right);
|
||||||
|
convolve_4 (top[1].v.out, top[1].v.left, top[1].v.right);
|
||||||
|
top += 2;
|
||||||
|
|
||||||
|
/* Now process combines. */
|
||||||
|
do {
|
||||||
|
/* b.main is the output buffer, mid is the middle
|
||||||
|
* part which needs to be adjusted in place, and
|
||||||
|
* then folded back into the output. We do this in
|
||||||
|
* a slightly strange way, so as to avoid having
|
||||||
|
* two loops. */
|
||||||
|
double * out = top->b.main;
|
||||||
|
double * mid = scratch + size * 4;
|
||||||
|
unsigned int i;
|
||||||
|
top++;
|
||||||
|
out[size * 2 - 1] = 0;
|
||||||
|
for (i = 0; i < size-1; i++) {
|
||||||
|
double lo;
|
||||||
|
double hi;
|
||||||
|
lo = mid[0] - (out[0] + out[2 * size]) + out[size];
|
||||||
|
hi = mid[size] - (out[size] + out[3 * size]) + out[2 * size];
|
||||||
|
out[size] = lo;
|
||||||
|
out[2 * size] = hi;
|
||||||
|
out++;
|
||||||
|
mid++;
|
||||||
|
}
|
||||||
|
size <<= 1;
|
||||||
|
} while (top->b.null == NULL);
|
||||||
|
} while (top->b.main != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int convolve_match (const int * lastchoice,
|
||||||
|
const short * input,
|
||||||
|
convolve_state * state)
|
||||||
|
/* lastchoice is a 256 sized array. input is a 512 array. We find the
|
||||||
|
* contiguous length 256 sub-array of input that best matches lastchoice.
|
||||||
|
* A measure of how good a sub-array is compared with the lastchoice is
|
||||||
|
* given by the sum of the products of each pair of entries. We maximise
|
||||||
|
* that, by taking an appropriate convolution, and then finding the maximum
|
||||||
|
* entry in the convolutions. state is a (non-NULL) pointer returned by
|
||||||
|
* convolve_init. */
|
||||||
|
{
|
||||||
|
double avg;
|
||||||
|
double best;
|
||||||
|
int p = 0;
|
||||||
|
int i;
|
||||||
|
double * left = state->left;
|
||||||
|
double * right = state->right;
|
||||||
|
double * scratch = state->scratch;
|
||||||
|
stack_entry * top = state->stack + STACK_SIZE - 1;
|
||||||
|
#if 1
|
||||||
|
for (i = 0; i < 512; i++)
|
||||||
|
left[i] = input[i];
|
||||||
|
|
||||||
|
avg = 0;
|
||||||
|
for (i = 0; i < 256; i++) {
|
||||||
|
double a = lastchoice[255 - i];
|
||||||
|
right[i] = a;
|
||||||
|
avg += a;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/* We adjust the smaller of the two input arrays to have average
|
||||||
|
* value 0. This makes the eventual result insensitive to both
|
||||||
|
* constant offsets and positive multipliers of the inputs. */
|
||||||
|
avg /= 256;
|
||||||
|
for (i = 0; i < 256; i++)
|
||||||
|
right[i] -= avg;
|
||||||
|
/* End-of-stack marker. */
|
||||||
|
#if 0 /* The following line produces a CRASH, need to figure out why?!! */
|
||||||
|
top[1].b.null = scratch;
|
||||||
|
#endif
|
||||||
|
top[1].b.main = NULL;
|
||||||
|
/* The low 256x256, of which we want the high 256 outputs. */
|
||||||
|
top->v.left = left;
|
||||||
|
top->v.right = right;
|
||||||
|
top->v.out = right + 256;
|
||||||
|
convolve_run (top, 256, scratch);
|
||||||
|
|
||||||
|
/* The high 256x256, of which we want the low 256 outputs. */
|
||||||
|
top->v.left = left + 256;
|
||||||
|
top->v.right = right;
|
||||||
|
top->v.out = right;
|
||||||
|
convolve_run (top, 256, scratch);
|
||||||
|
|
||||||
|
/* Now find the best position amoungs this. Apart from the first
|
||||||
|
* and last, the required convolution outputs are formed by adding
|
||||||
|
* outputs from the two convolutions above. */
|
||||||
|
best = right[511];
|
||||||
|
right[767] = 0;
|
||||||
|
p = -1;
|
||||||
|
for (i = 0; i < 256; i++) {
|
||||||
|
double a = right[i] + right[i + 512];
|
||||||
|
if (a > best) {
|
||||||
|
best = a;
|
||||||
|
p = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
{
|
||||||
|
/* This is some debugging code... */
|
||||||
|
int bad = 0;
|
||||||
|
best = 0;
|
||||||
|
for (i = 0; i < 256; i++)
|
||||||
|
best += ((double) input[i+p]) * ((double) lastchoice[i] - avg);
|
||||||
|
|
||||||
|
for (i = 0; i < 257; i++) {
|
||||||
|
double tot = 0;
|
||||||
|
unsigned int j;
|
||||||
|
for (j = 0; j < 256; j++)
|
||||||
|
tot += ((double) input[i+j]) * ((double) lastchoice[j] - avg);
|
||||||
|
if (tot > best)
|
||||||
|
printf ("(%i)", i);
|
||||||
|
if (tot != left[i + 255])
|
||||||
|
printf ("!");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("%i\n", p);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
47
gst/monoscope/convolve.h
Normal file
47
gst/monoscope/convolve.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/* convolve.h: Header for convolutions.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1999 Ralph Loader <suckfish@ihug.co.nz>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CONVOLVE_H
|
||||||
|
#define CONVOLVE_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* convolve_match takes two blocks, one twice the size of the other. The
|
||||||
|
* sizes of these are CONVOLVE_BIG and CONVOLVE_SMALL respectively. */
|
||||||
|
#define CONVOLVE_DEPTH 8
|
||||||
|
#define CONVOLVE_SMALL (1 << CONVOLVE_DEPTH)
|
||||||
|
#define CONVOLVE_BIG (CONVOLVE_SMALL * 2)
|
||||||
|
|
||||||
|
/* Convolution stuff */
|
||||||
|
typedef struct _struct_convolve_state convolve_state;
|
||||||
|
|
||||||
|
convolve_state *convolve_init (void);
|
||||||
|
void convolve_close (convolve_state * state);
|
||||||
|
|
||||||
|
int convolve_match (const int * lastchoice,
|
||||||
|
const short int * input,
|
||||||
|
convolve_state * state);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
367
gst/monoscope/gstmonoscope.c
Normal file
367
gst/monoscope/gstmonoscope.c
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
/* gstmonoscope.c: implementation of monoscope drawing element
|
||||||
|
* Copyright (C) <2002> Richard Boulton <richard@tartarus.org>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Library General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Library General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <config.h>
|
||||||
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
#include "monoscope.h"
|
||||||
|
|
||||||
|
#define GST_TYPE_MONOSCOPE (gst_monoscope_get_type())
|
||||||
|
#define GST_MONOSCOPE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MONOSCOPE,GstMonoscope))
|
||||||
|
#define GST_MONOSCOPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MONOSCOPE,GstMonoscope))
|
||||||
|
#define GST_IS_MONOSCOPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MONOSCOPE))
|
||||||
|
#define GST_IS_MONOSCOPE_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MONOSCOPE))
|
||||||
|
|
||||||
|
typedef struct _GstMonoscope GstMonoscope;
|
||||||
|
typedef struct _GstMonoscopeClass GstMonoscopeClass;
|
||||||
|
|
||||||
|
struct _GstMonoscope {
|
||||||
|
GstElement element;
|
||||||
|
|
||||||
|
/* pads */
|
||||||
|
GstPad *sinkpad,*srcpad;
|
||||||
|
GstBufferPool *peerpool;
|
||||||
|
|
||||||
|
// the timestamp of the next frame
|
||||||
|
guint64 next_time;
|
||||||
|
gint16 datain[2][512];
|
||||||
|
|
||||||
|
// video state
|
||||||
|
gint fps;
|
||||||
|
gint width;
|
||||||
|
gint height;
|
||||||
|
gboolean first_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GstMonoscopeClass {
|
||||||
|
GstElementClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
GType gst_monoscope_get_type(void);
|
||||||
|
|
||||||
|
|
||||||
|
/* elementfactory information */
|
||||||
|
static GstElementDetails gst_monoscope_details = {
|
||||||
|
"Monoscope",
|
||||||
|
"Filter/Visualization",
|
||||||
|
"Displays a highly stabilised waveform of audio input",
|
||||||
|
VERSION,
|
||||||
|
"Richard Boulton <richard@tartarus.org>",
|
||||||
|
"(C) 2002",
|
||||||
|
};
|
||||||
|
|
||||||
|
/* signals and args */
|
||||||
|
enum {
|
||||||
|
/* FILL ME */
|
||||||
|
LAST_SIGNAL
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ARG_0,
|
||||||
|
ARG_WIDTH,
|
||||||
|
ARG_HEIGHT,
|
||||||
|
ARG_FPS,
|
||||||
|
/* FILL ME */
|
||||||
|
};
|
||||||
|
|
||||||
|
GST_PADTEMPLATE_FACTORY (src_template,
|
||||||
|
"src",
|
||||||
|
GST_PAD_SRC,
|
||||||
|
GST_PAD_ALWAYS,
|
||||||
|
GST_CAPS_NEW (
|
||||||
|
"monoscopesrc",
|
||||||
|
"video/raw",
|
||||||
|
"format", GST_PROPS_FOURCC (GST_STR_FOURCC ("RGB ")),
|
||||||
|
"bpp", GST_PROPS_INT (32),
|
||||||
|
"depth", GST_PROPS_INT (32),
|
||||||
|
"endianness", GST_PROPS_INT (G_BYTE_ORDER),
|
||||||
|
"red_mask", GST_PROPS_INT (0xff0000),
|
||||||
|
"green_mask", GST_PROPS_INT (0xff00),
|
||||||
|
"blue_mask", GST_PROPS_INT (0xff),
|
||||||
|
"width", GST_PROPS_INT_RANGE (16, 4096),
|
||||||
|
"height", GST_PROPS_INT_RANGE (16, 4096)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
GST_PADTEMPLATE_FACTORY (sink_template,
|
||||||
|
"sink", /* the name of the pads */
|
||||||
|
GST_PAD_SINK, /* type of the pad */
|
||||||
|
GST_PAD_ALWAYS, /* ALWAYS/SOMETIMES */
|
||||||
|
GST_CAPS_NEW (
|
||||||
|
"monoscopesink", /* the name of the caps */
|
||||||
|
"audio/raw", /* the mime type of the caps */
|
||||||
|
/* Properties follow: */
|
||||||
|
"format", GST_PROPS_STRING ("int"),
|
||||||
|
"law", GST_PROPS_INT (0),
|
||||||
|
"endianness", GST_PROPS_INT (G_BYTE_ORDER),
|
||||||
|
"signed", GST_PROPS_BOOLEAN (TRUE),
|
||||||
|
"width", GST_PROPS_INT (16),
|
||||||
|
"depth", GST_PROPS_INT (16),
|
||||||
|
"rate", GST_PROPS_INT_RANGE (8000, 96000),
|
||||||
|
"channels", GST_PROPS_INT (1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static void gst_monoscope_class_init (GstMonoscopeClass *klass);
|
||||||
|
static void gst_monoscope_init (GstMonoscope *monoscope);
|
||||||
|
|
||||||
|
static void gst_monoscope_set_property (GObject *object, guint prop_id,
|
||||||
|
const GValue *value, GParamSpec *pspec);
|
||||||
|
static void gst_monoscope_get_property (GObject *object, guint prop_id,
|
||||||
|
GValue *value, GParamSpec *pspec);
|
||||||
|
|
||||||
|
static void gst_monoscope_chain (GstPad *pad, GstBuffer *buf);
|
||||||
|
|
||||||
|
static GstPadConnectReturn
|
||||||
|
gst_monoscope_sinkconnect (GstPad *pad, GstCaps *caps);
|
||||||
|
|
||||||
|
static GstElementClass *parent_class = NULL;
|
||||||
|
|
||||||
|
GType
|
||||||
|
gst_monoscope_get_type (void)
|
||||||
|
{
|
||||||
|
static GType type = 0;
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
static const GTypeInfo info = {
|
||||||
|
sizeof (GstMonoscopeClass),
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
(GClassInitFunc) gst_monoscope_class_init,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
sizeof (GstMonoscope),
|
||||||
|
0,
|
||||||
|
(GInstanceInitFunc) gst_monoscope_init,
|
||||||
|
};
|
||||||
|
type = g_type_register_static (GST_TYPE_ELEMENT, "GstMonoscope", &info, 0);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_monoscope_class_init(GstMonoscopeClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *gobject_class;
|
||||||
|
GstElementClass *gstelement_class;
|
||||||
|
|
||||||
|
gobject_class = (GObjectClass*) klass;
|
||||||
|
gstelement_class = (GstElementClass*) klass;
|
||||||
|
|
||||||
|
parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
|
||||||
|
|
||||||
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_WIDTH,
|
||||||
|
g_param_spec_int ("width","Width","The Width",
|
||||||
|
1, 2048, 256, G_PARAM_READWRITE));
|
||||||
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HEIGHT,
|
||||||
|
g_param_spec_int ("height","Height","The height",
|
||||||
|
1, 2048, 128, G_PARAM_READWRITE));
|
||||||
|
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FPS,
|
||||||
|
g_param_spec_int ("fps","FPS","Frames per second",
|
||||||
|
1, 100, 25, G_PARAM_READWRITE));
|
||||||
|
|
||||||
|
gobject_class->set_property = gst_monoscope_set_property;
|
||||||
|
gobject_class->get_property = gst_monoscope_get_property;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_monoscope_init (GstMonoscope *monoscope)
|
||||||
|
{
|
||||||
|
/* create the sink and src pads */
|
||||||
|
monoscope->sinkpad = gst_pad_new_from_template (
|
||||||
|
GST_PADTEMPLATE_GET (sink_template ), "sink");
|
||||||
|
monoscope->srcpad = gst_pad_new_from_template (
|
||||||
|
GST_PADTEMPLATE_GET (src_template ), "src");
|
||||||
|
gst_element_add_pad (GST_ELEMENT (monoscope), monoscope->sinkpad);
|
||||||
|
gst_element_add_pad (GST_ELEMENT (monoscope), monoscope->srcpad);
|
||||||
|
|
||||||
|
gst_pad_set_chain_function (monoscope->sinkpad, gst_monoscope_chain);
|
||||||
|
gst_pad_set_connect_function (monoscope->sinkpad, gst_monoscope_sinkconnect);
|
||||||
|
|
||||||
|
monoscope->next_time = 0;
|
||||||
|
monoscope->peerpool = NULL;
|
||||||
|
|
||||||
|
// reset the initial video state
|
||||||
|
monoscope->first_buffer = TRUE;
|
||||||
|
monoscope->width = 256;
|
||||||
|
monoscope->height = 128;
|
||||||
|
monoscope->fps = 25; // desired frame rate
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstPadConnectReturn
|
||||||
|
gst_monoscope_sinkconnect (GstPad *pad, GstCaps *caps)
|
||||||
|
{
|
||||||
|
GstMonoscope *monoscope;
|
||||||
|
monoscope = GST_MONOSCOPE (gst_pad_get_parent (pad));
|
||||||
|
|
||||||
|
if (!GST_CAPS_IS_FIXED (caps)) {
|
||||||
|
return GST_PAD_CONNECT_DELAYED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GST_PAD_CONNECT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_monoscope_chain (GstPad *pad, GstBuffer *bufin)
|
||||||
|
{
|
||||||
|
GstMonoscope *monoscope;
|
||||||
|
GstBuffer *bufout;
|
||||||
|
guint32 samples_in;
|
||||||
|
gint16 *data;
|
||||||
|
gint i;
|
||||||
|
|
||||||
|
monoscope = GST_MONOSCOPE (gst_pad_get_parent (pad));
|
||||||
|
|
||||||
|
GST_DEBUG (0, "Monoscope: chainfunc called\n");
|
||||||
|
|
||||||
|
samples_in = GST_BUFFER_SIZE (bufin) / sizeof (gint16);
|
||||||
|
|
||||||
|
GST_DEBUG (0, "input buffer has %d samples\n", samples_in);
|
||||||
|
|
||||||
|
/* FIXME: should really select the first 1024 samples after the timestamp. */
|
||||||
|
if (GST_BUFFER_TIMESTAMP (bufin) < monoscope->next_time || samples_in < 1024) {
|
||||||
|
GST_DEBUG (0, "timestamp is %llu: want >= %llu\n", GST_BUFFER_TIMESTAMP (bufin), monoscope->next_time);
|
||||||
|
gst_buffer_unref (bufin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = (gint16 *) GST_BUFFER_DATA (bufin);
|
||||||
|
for (i=0; i < 512; i++) {
|
||||||
|
monoscope->datain[0][i] = *data++;
|
||||||
|
monoscope->datain[1][i] = *data++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monoscope->first_buffer) {
|
||||||
|
GstCaps *caps;
|
||||||
|
|
||||||
|
monoscope_init (monoscope->width, monoscope->height);
|
||||||
|
|
||||||
|
GST_DEBUG (0, "making new pad\n");
|
||||||
|
|
||||||
|
caps = GST_CAPS_NEW (
|
||||||
|
"monoscopesrc",
|
||||||
|
"video/raw",
|
||||||
|
"format", GST_PROPS_FOURCC (GST_STR_FOURCC ("RGB ")),
|
||||||
|
"bpp", GST_PROPS_INT (32),
|
||||||
|
"depth", GST_PROPS_INT (32),
|
||||||
|
"endianness", GST_PROPS_INT (G_BYTE_ORDER),
|
||||||
|
"red_mask", GST_PROPS_INT (0xff0000),
|
||||||
|
"green_mask", GST_PROPS_INT (0x00ff00),
|
||||||
|
"blue_mask", GST_PROPS_INT (0x0000ff),
|
||||||
|
"width", GST_PROPS_INT (monoscope->width),
|
||||||
|
"height", GST_PROPS_INT (monoscope->height)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!gst_pad_try_set_caps (monoscope->srcpad, caps)) {
|
||||||
|
gst_element_error (GST_ELEMENT (monoscope), "could not set caps");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
monoscope->first_buffer = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufout = gst_buffer_new ();
|
||||||
|
GST_BUFFER_SIZE (bufout) = monoscope->width * monoscope->height * 4;
|
||||||
|
GST_BUFFER_DATA (bufout) = (guchar *) monoscope_update (monoscope->datain);
|
||||||
|
GST_BUFFER_TIMESTAMP (bufout) = monoscope->next_time;
|
||||||
|
GST_BUFFER_FLAG_SET (bufout, GST_BUFFER_DONTFREE);
|
||||||
|
|
||||||
|
monoscope->next_time += 1000000LL / monoscope->fps;
|
||||||
|
|
||||||
|
gst_pad_push (monoscope->srcpad, bufout);
|
||||||
|
|
||||||
|
gst_buffer_unref (bufin);
|
||||||
|
|
||||||
|
GST_DEBUG (0, "Monoscope: exiting chainfunc\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_monoscope_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
GstMonoscope *monoscope;
|
||||||
|
|
||||||
|
/* it's not null if we got it, but it might not be ours */
|
||||||
|
g_return_if_fail (GST_IS_MONOSCOPE (object));
|
||||||
|
monoscope = GST_MONOSCOPE (object);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case ARG_WIDTH:
|
||||||
|
monoscope->width = g_value_get_int (value);
|
||||||
|
break;
|
||||||
|
case ARG_HEIGHT:
|
||||||
|
monoscope->height = g_value_get_int (value);
|
||||||
|
break;
|
||||||
|
case ARG_FPS:
|
||||||
|
monoscope->fps = g_value_get_int (value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_monoscope_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
GstMonoscope *monoscope;
|
||||||
|
|
||||||
|
/* it's not null if we got it, but it might not be ours */
|
||||||
|
g_return_if_fail (GST_IS_MONOSCOPE (object));
|
||||||
|
monoscope = GST_MONOSCOPE (object);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case ARG_WIDTH:
|
||||||
|
g_value_set_int (value, monoscope->width);
|
||||||
|
break;
|
||||||
|
case ARG_HEIGHT:
|
||||||
|
g_value_set_int (value, monoscope->height);
|
||||||
|
break;
|
||||||
|
case ARG_FPS:
|
||||||
|
g_value_set_int (value, monoscope->fps);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
plugin_init (GModule *module, GstPlugin *plugin)
|
||||||
|
{
|
||||||
|
GstElementFactory *factory;
|
||||||
|
|
||||||
|
/* create an elementfactory for the monoscope element */
|
||||||
|
factory = gst_elementfactory_new("monoscope",GST_TYPE_MONOSCOPE,
|
||||||
|
&gst_monoscope_details);
|
||||||
|
g_return_val_if_fail(factory != NULL, FALSE);
|
||||||
|
|
||||||
|
gst_elementfactory_add_padtemplate (factory, GST_PADTEMPLATE_GET (src_template));
|
||||||
|
gst_elementfactory_add_padtemplate (factory, GST_PADTEMPLATE_GET (sink_template));
|
||||||
|
|
||||||
|
gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GstPluginDesc plugin_desc = {
|
||||||
|
GST_VERSION_MAJOR,
|
||||||
|
GST_VERSION_MINOR,
|
||||||
|
"monoscope",
|
||||||
|
plugin_init
|
||||||
|
};
|
143
gst/monoscope/monoscope.c
Normal file
143
gst/monoscope/monoscope.c
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/* monoscope.cpp
|
||||||
|
* Copyright (C) 2002 Richard Boulton <richard@tartarus.org>
|
||||||
|
* Copyright (C) 1998-2001 Andy Lo A Foe <andy@alsaplayer.org>
|
||||||
|
* Original code by Tinic Uro
|
||||||
|
*
|
||||||
|
* This code is copied from Alsaplayer.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
#include "monoscope.h"
|
||||||
|
#include "convolve.h"
|
||||||
|
|
||||||
|
#define scope_width 256
|
||||||
|
#define scope_height 128
|
||||||
|
|
||||||
|
static gint16 newEq[CONVOLVE_BIG]; // latest block of 512 samples.
|
||||||
|
static gint16 copyEq[CONVOLVE_BIG];
|
||||||
|
static int avgEq[CONVOLVE_SMALL]; // a running average of the last few.
|
||||||
|
static int avgMax; // running average of max sample.
|
||||||
|
static guint32 display[(scope_width + 1) * (scope_height + 1)];
|
||||||
|
|
||||||
|
static convolve_state *state = NULL;
|
||||||
|
static guint32 colors[64];
|
||||||
|
|
||||||
|
static void colors_init(guint32 * colors)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 32; i++) {
|
||||||
|
colors[i] = (i*8 << 16) + (255 << 8);
|
||||||
|
colors[i+31] = (255 << 16) + (((31 - i) * 8) << 8);
|
||||||
|
}
|
||||||
|
colors[63] = (40 << 16) + (75 << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void monoscope_init (guint32 resx, guint32 resy)
|
||||||
|
{
|
||||||
|
state = convolve_init();
|
||||||
|
colors_init(colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
guint32 * monoscope_update (gint16 data [2][512])
|
||||||
|
{
|
||||||
|
/* Note that CONVOLVE_BIG must == data size here, ie 512. */
|
||||||
|
/* Really, we want samples evenly spread over the available data.
|
||||||
|
* Just taking a continuous chunk will do for now, though. */
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < CONVOLVE_BIG; i++) {
|
||||||
|
/* Average the two channels. */
|
||||||
|
newEq[i] = (((int) data[0][i]) + (int) data[1][i]) >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int foo;
|
||||||
|
int bar;
|
||||||
|
int h;
|
||||||
|
guchar bits[ 257 * 129];
|
||||||
|
guint32 *loc;
|
||||||
|
|
||||||
|
int factor;
|
||||||
|
int val;
|
||||||
|
int max = 1;
|
||||||
|
short * thisEq;
|
||||||
|
memcpy (copyEq, newEq, sizeof (short) * CONVOLVE_BIG);
|
||||||
|
thisEq = copyEq;
|
||||||
|
#if 1
|
||||||
|
val = convolve_match (avgEq, copyEq, state);
|
||||||
|
thisEq += val;
|
||||||
|
#endif
|
||||||
|
memset(display, 0, 256 * 128 * sizeof(guint32));
|
||||||
|
for (i=0; i < 256; i++) {
|
||||||
|
foo = thisEq[i] + (avgEq[i] >> 1);
|
||||||
|
avgEq[i] = foo;
|
||||||
|
if (foo < 0)
|
||||||
|
foo = -foo;
|
||||||
|
if (foo > max)
|
||||||
|
max = foo;
|
||||||
|
}
|
||||||
|
avgMax += max - (avgMax >> 8);
|
||||||
|
if (avgMax < max)
|
||||||
|
avgMax = max; /* Avoid overflow */
|
||||||
|
factor = 0x7fffffff / avgMax;
|
||||||
|
/* Keep the scaling sensible. */
|
||||||
|
if (factor > (1 << 18))
|
||||||
|
factor = 1 << 18;
|
||||||
|
if (factor < (1 << 8))
|
||||||
|
factor = 1 << 8;
|
||||||
|
for (i=0; i < 256; i++) {
|
||||||
|
foo = avgEq[i] * factor;
|
||||||
|
foo >>= 18;
|
||||||
|
if (foo > 63)
|
||||||
|
foo = 63;
|
||||||
|
if (foo < -64)
|
||||||
|
foo = -64;
|
||||||
|
val = (i + ((foo+64) << 8));
|
||||||
|
bar = val;
|
||||||
|
if ((bar > 0) && (bar < (256 * 128))) {
|
||||||
|
loc = display + bar;
|
||||||
|
if (foo < 0) {
|
||||||
|
for (h = 0; h <= (-foo); h++) {
|
||||||
|
*loc = colors[h];
|
||||||
|
loc+=256;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (h = 0; h <= foo; h++) {
|
||||||
|
*loc = colors[h];
|
||||||
|
loc-=256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw grid. */
|
||||||
|
for (i=16;i < 128; i+=16) {
|
||||||
|
for (h = 0; h < 256; h+=2) {
|
||||||
|
display[(i << 8) + h] = colors[63];
|
||||||
|
if (i == 64)
|
||||||
|
display[(i << 8) + h + 1] = colors[63];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 16; i < 256; i+=16) {
|
||||||
|
for (h = 0; h < 128; h+=2) {
|
||||||
|
display[i + (h << 8)] = colors[63];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
|
||||||
|
void monoscope_close ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
10
gst/monoscope/monoscope.h
Normal file
10
gst/monoscope/monoscope.h
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#ifndef _MONOSCOPE_H
|
||||||
|
#define _MONOSCOPE_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
void monoscope_init (guint32 resx, guint32 resy);
|
||||||
|
guint32 * monoscope_update (gint16 data [2][512]);
|
||||||
|
void monoscope_close ();
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue