OsmoTV: Added more test patterns for TV repair

- improved FUBK
- red screen for DY adjustment
- crosses and dots for convergence adjustment
- EBU color bars
- natural color image
This commit is contained in:
Andreas Eversberg 2019-05-12 09:36:12 +02:00
parent e4314749fd
commit b9cc33c6ec
15 changed files with 82633 additions and 30 deletions

View File

@ -32,11 +32,19 @@
</tr>
<tr>
<td><img src="amps_small.jpg" title="Advanced Mobile Phone Service"/></td>
<td><img src="setup_small.jpg"/></td>
<td><img src="tv_small.jpg" title="TV signal generator and transmitter"/></td>
</tr>
<tr>
<th>AMPS (USA) / TACS (UK / Ireland / Italy)</th>
<th>Transmit TV test image signals</th>
</tr>
<tr>
<td><img src="setup_small.jpg"/></td>
<td></td>
</tr>
<tr>
<th>my early radio setup</th>
<th></th>
</tr>
</table>
</center>
@ -85,6 +93,16 @@ Implemented networks:
</ul>
</center>
<p>
Additional features:
</p>
<center>
<ul>
<li><a href="tv.html">Osmo TV</a></li>
</ul>
</center>
<br><br>
</td></tr></table></center>

33
docs/tv.html Normal file
View File

@ -0,0 +1,33 @@
<html>
<head>
<link href="style.css" rel="stylesheet" type="text/css" />
<title>osmocom-analog</title>
</head>
<body>
<center><table><tr><td>
<h2><center>Osmo TV</center></h2>
<center><img src="tv.jpg"/></center>
<p>
OsmoTV is capable of transmitting video signals via SDR. This video signal are transmitted on given channels. Color signal and audio carriere can be disabled by option.
</p>
<p>
The following test signals are supported:
<ul>
<li>FUBK test chart (known as the German 'Testbild')
<li>EBU color bars (8 bars from white to black)
<li>Red screen for deflection yoke adjustment
<li>Convergence pattern for convergence adjustment
<li>A real image of natural colors
<li>My VCR Test Chart
</ul>
</p>
<hr><center>[<a href="index.html">Back to main page</a>]</center><hr>
</td></tr></table></center>
</body>
</html>

View File

@ -1,4 +1,4 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) -fstack-check
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) -fstack-check $(IMAGEMAGICK_CFLAGS)
bin_PROGRAMS = \
osmotv
@ -6,9 +6,13 @@ bin_PROGRAMS = \
osmotv_SOURCES = \
bas.c \
fubk.c \
ebu.c \
convergence.c \
color.c \
font.c \
vcr.c \
image.c \
sample_image.c \
tv_modulate.c \
channels.c \
main.c
@ -22,7 +26,7 @@ osmotv_LDADD = \
$(top_builddir)/src/libwave/libwave.a \
$(top_builddir)/src/libsample/libsample.a \
$(ALSA_LIBS) \
$(GRAPHICSMAGICK_LIBS) $(IMAGEMAGICK_LIBS) \
$(IMAGEMAGICK_LIBS) \
-lm
if HAVE_SDR

View File

@ -25,6 +25,9 @@
#include "bas.h"
#include "vcr.h"
#include "fubk.h"
#include "convergence.h"
#include "color.h"
#include "ebu.h"
#include "image.h"
#define WHITE_LEVEL 1.0
@ -49,7 +52,7 @@
#define BURST_AMPLITUDE 0.15
#define COLOR_FILTER_ITER 1
void bas_init(bas_t *bas, double samplerate, enum bas_type type, int fbas, double circle_radius, int color_bar, int grid_only, const char *station_id, unsigned short *img, int width, int height)
void bas_init(bas_t *bas, double samplerate, enum bas_type type, int fbas, double circle_radius, int color_bar, int grid_only, const char *station_id, int grid_width, unsigned short *img, int width, int height)
{
memset(bas, 0, sizeof(*bas));
bas->samplerate = samplerate;
@ -60,6 +63,7 @@ void bas_init(bas_t *bas, double samplerate, enum bas_type type, int fbas, doubl
bas->color_bar = color_bar;
bas->grid_only = grid_only;
bas->station_id = station_id;
bas->grid_width = grid_width;
bas->img = img;
bas->img_width = width;
bas->img_height = height;
@ -111,6 +115,18 @@ int bas_generate(bas_t *bas, sample_t *sample)
/* render FUBK test image */
fubk_gen_line(sample, x, bas->samplerate, color_u, color_v, bas->v_polarity, H_LINE_START, H_LINE_END, middlefield_line, bas->circle_radius, bas->color_bar, bas->grid_only, bas->station_id);
break;
case BAS_CONVERGENCE:
/* render color convergence test image */
convergence_gen_line(sample, x, bas->samplerate, H_LINE_START, H_LINE_END, middlefield_line, (bas->grid_width) > 1 ? 1.0: 0.5);
break;
case BAS_RED:
/* render (thin) color convergence test image */
color_gen_line(sample, x, bas->samplerate, color_u, color_v, bas->v_polarity, H_LINE_START, H_LINE_END);
break;
case BAS_EBU:
/* render (thin) color convergence test image */
ebu_gen_line(sample, x, bas->samplerate, color_u, color_v, bas->v_polarity, H_LINE_START, H_LINE_END);
break;
case BAS_IMAGE: {
/* 574 lines of image are to be rendered */
int img_line = middlefield_line - (574 - bas->img_height) / 2;

View File

@ -1,6 +1,9 @@
enum bas_type {
BAS_FUBK,
BAS_CONVERGENCE,
BAS_RED,
BAS_EBU,
BAS_VCR,
BAS_IMAGE,
};
@ -13,6 +16,7 @@ typedef struct bas {
int color_bar; /* show only color bar on all lines */
int grid_only; /* show only the grid */
const char *station_id; /* text to display as station id */
int grid_width; /* width of the grid (convergence test) */
double color_phase; /* current phase of color carrier */
int v_polarity; /* polarity of V color vector */
unsigned short *img; /* image data, if it should be used */
@ -20,6 +24,6 @@ typedef struct bas {
iir_filter_t lp_y, lp_u, lp_v; /* low pass filters */
} bas_t;
void bas_init(bas_t *bas, double samplerate, enum bas_type type, int fbas, double circle_radius, int color_bar, int grid_only, const char *station_id, unsigned short *img, int width, int height);
void bas_init(bas_t *bas, double samplerate, enum bas_type type, int fbas, double circle_radius, int color_bar, int grid_only, const char *station_id, int grid_width, unsigned short *img, int width, int height);
int bas_generate(bas_t *bas, sample_t *sample);

73
src/tv/color.c Normal file
View File

@ -0,0 +1,73 @@
/* color test image generator
*
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include "../libsample/sample.h"
#include "color.h"
#define RAMP_WIDTH 0.0000002
static struct color_bar {
double amplitude, phase;
} color_bar[8] = {
{0.0, 0.0},
{0.336, 167.1},
{0.474, 283.5},
{0.443, 240.7},
{0.443, 60.7},
{0.474, 103.5},
{0.336, 347.1},
{0.0, 0.0},
};
int color_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double line_start, double line_end)
{
int b = 5;
double step = 1.0 / samplerate;
int i = 0;
double amplitude, Y, U, V, colorphase;
/* skip x to line_start */
while (x < line_start && x < line_end) {
i++;
x += step;
}
if (x >= line_end)
return i;
/* color */
Y = (1.0 - (double)b / 7.0) * 0.75;
amplitude = color_bar[b].amplitude;
if (v_polarity < 0)
colorphase = (360.0 - color_bar[b].phase) / 180.0 * M_PI;
else
colorphase = color_bar[b].phase / 180.0 * M_PI;
U = cos(colorphase) * amplitude / 2.0;
V = sin(colorphase) * amplitude / 2.0;
while (x < line_end) {
color_u[i] = U;
color_v[i] = V;
sample[i++] = Y;
x += step;
}
return i;
}

3
src/tv/color.h Normal file
View File

@ -0,0 +1,3 @@
int color_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double frame_start, double line_end);

109
src/tv/convergence.c Normal file
View File

@ -0,0 +1,109 @@
/* Color Convergence test image generator
*
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include "../libsample/sample.h"
#include "convergence.h"
#define GRID_LINES (4 * thick)
#define GRID_HEIGHT 40
#define GRID_HEIGHT2 20
#define CENTER_LINE (287 - 2)
#define GRID_WIDTH 0.0000027
#define GRID_WIDTH2 0.00000135
#define RAMP_WIDTH (0.0000002 * thick)
#define GRID_LEVEL 1.0
#define FIELD_LEVEL 0.00
/* create cosine ramp, so that the bandwidth is not higher than 2 * width of the ramp(0..1) */
static inline double ramp(double x)
{
return 0.5 - 0.5 * cos(x * M_PI);
}
int convergence_gen_line(sample_t *sample, double x, double samplerate, double line_start, double line_end, int line, double thick)
{
double step = 1.0 / samplerate;
int i = 0;
double render_start, render_end, center_x;
/* skip x to line_start */
while (x < line_start && x < line_end) {
i++;
x += step;
}
if (x >= line_end)
return i;
/* calculate phase for ramp start of center line */
center_x = (line_end - line_start) / 2.0 + line_start;
/* calculate position in grid:
* get the distance below the center line (line - CENTER_LINE)
* then be sure not to get negative value: add a multiple of the grid period
* then use modulo to get the distance below the grid line */
if (((line - CENTER_LINE + GRID_HEIGHT*40) % GRID_HEIGHT) < GRID_LINES) {
/* grid line after middle field */
while (x < line_end) {
sample[i++] = GRID_LEVEL;
x += step;
}
} else {
while (1) {
/* calculate position for next ramp:
* get the distance to center (center_x - x - RAMP_WIDTH)
* then be sure not to get negative value: add a multiple of the grid period
* then use fmod to get the next ramp start */
if (((line - CENTER_LINE + GRID_HEIGHT2*40) % GRID_HEIGHT2) < GRID_LINES)
render_start = fmod(center_x - x - RAMP_WIDTH + GRID_WIDTH2*40.0, GRID_WIDTH2) + x;
else
render_start = fmod(center_x - x - RAMP_WIDTH + GRID_WIDTH*40.0, GRID_WIDTH) + x;
/* draw background field up to grid start */
while (x < render_start && x < line_end) {
sample[i++] = FIELD_LEVEL;
x += step;
}
if (x >= line_end)
break;
/* ramp up to grid level */
render_end = render_start + RAMP_WIDTH;
while (x < render_end && x < line_end) {
sample[i++] = ramp((x - render_start) / RAMP_WIDTH) * (GRID_LEVEL - FIELD_LEVEL) + FIELD_LEVEL;
x += step;
}
if (x >= line_end)
break;
render_start = render_end;
/* ramp down to field level */
render_end = render_start + RAMP_WIDTH;
while (x < render_end && x < line_end) {
sample[i++] = ramp((x - render_start) / RAMP_WIDTH) * (FIELD_LEVEL - GRID_LEVEL) + GRID_LEVEL;
x += step;
}
if (x >= line_end)
break;
}
}
return i;
}

3
src/tv/convergence.h Normal file
View File

@ -0,0 +1,3 @@
int convergence_gen_line(sample_t *sample, double x, double samplerate, double line_start, double line_end, int line, double thick);

76
src/tv/ebu.c Normal file
View File

@ -0,0 +1,76 @@
/* color test image generator
*
* (C) 2019 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include <stdint.h>
#include "../libsample/sample.h"
#include "ebu.h"
#define RAMP_WIDTH 0.0000002
static struct color_bar {
double luminance, amplitude, phase;
} color_bar[8] = {
{1.0, 0.0, 0.0},
{0.664, 0.336, 167.1},
{0.526, 0.474, 283.5},
{0.440, 0.443, 240.7},
{0.310, 0.443, 60.7},
{0.224, 0.474, 103.5},
{0.086, 0.336, 347.1},
{0.0, 0.0, 0.0},
};
int ebu_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double line_start, double line_end)
{
int b = 5;
double step = 1.0 / samplerate, render_end;
int i = 0;
double amplitude, Y, U, V, colorphase;
/* skip x to line_start */
while (x < line_start && x < line_end) {
i++;
x += step;
}
if (x >= line_end)
return i;
/* color */
for (b = 0; b < 8; b++) {
Y = color_bar[b].luminance;
amplitude = color_bar[b].amplitude;
if (v_polarity < 0)
colorphase = (360.0 - color_bar[b].phase) / 180.0 * M_PI;
else
colorphase = color_bar[b].phase / 180.0 * M_PI;
U = cos(colorphase) * amplitude / 2.0;
V = sin(colorphase) * amplitude / 2.0;
render_end = (line_end - line_start) / 8 * (b + 1) + line_start;
while (x < render_end) {
color_u[i] = U;
color_v[i] = V;
sample[i++] = Y;
x += step;
}
}
return i;
}

3
src/tv/ebu.h Normal file
View File

@ -0,0 +1,3 @@
int ebu_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_u, sample_t *color_v, int v_polarity, double frame_start, double line_end);

View File

@ -29,7 +29,8 @@
#define CENTER_LINE (287 - 2)
#define GRID_WIDTH 0.0000027
#define RAMP_WIDTH 0.0000002
#define RAMP_WIDTH 0.00000015
#define TEXT_WIDTH 0.0000002
#define GRID_LEVEL 1.0
#define FIELD_LEVEL 0.25
@ -38,7 +39,7 @@
#define CIRCLE_CENTER (287 - 1)
#define CIRCLE_HEIGTH 3
struct color_bar {
static struct color_bar {
double amplitude, phase;
} color_bar[8] = {
{0.0, 0.0},
@ -51,7 +52,7 @@ struct color_bar {
{0.0, 0.0},
};
struct multi_burst {
static struct multi_burst {
double width; /* how whide is this portion */
double level; /* level of this portion */
double frequency; /* frequency of burst or zero */
@ -154,7 +155,7 @@ static double mittelfeld(sample_t *sample, double samplerate, int *_i, double *_
bit = ((bits << b) & 128);
if (!last_bit && !bit) {
/* keep black */
render_end = render_start + RAMP_WIDTH;
render_end = render_start + TEXT_WIDTH;
while (x < render_end) {
sample[i++] = 0.0;
x += step;
@ -163,7 +164,7 @@ static double mittelfeld(sample_t *sample, double samplerate, int *_i, double *_
}
if (last_bit && bit) {
/* keep white */
render_end = render_start + RAMP_WIDTH;
render_end = render_start + TEXT_WIDTH;
while (x < render_end) {
sample[i++] = 1.0;
x += step;
@ -172,18 +173,18 @@ static double mittelfeld(sample_t *sample, double samplerate, int *_i, double *_
}
if (!last_bit && bit) {
/* ramp to white */
render_end = render_start + RAMP_WIDTH;
render_end = render_start + TEXT_WIDTH;
while (x < render_end) {
sample[i++] = 1.0 - ramp((render_end - x) / RAMP_WIDTH);
sample[i++] = 1.0 - ramp((render_end - x) / TEXT_WIDTH);
x += step;
}
render_start = render_end;
}
if (last_bit && !bit) {
/* ramp to black */
render_end = render_start + RAMP_WIDTH;
render_end = render_start + TEXT_WIDTH;
while (x < render_end) {
sample[i++] = ramp((render_end - x) / RAMP_WIDTH);
sample[i++] = ramp((render_end - x) / TEXT_WIDTH);
x += step;
}
render_start = render_end;

View File

@ -60,6 +60,7 @@ static double circle_radius = 6.7;
static int color_bar = 0;
static int grid_only = 0;
static const char *station_id = "Jolly Roger";
static int grid_width = 0;
static int __attribute__((__unused__)) latency = 200;
static double samplerate = 10e6;
static const char *wave_file = NULL;
@ -87,8 +88,11 @@ void print_help(const char *arg0)
/* - - */
printf("\ncommand:\n");
printf(" tx-fubk Transmit FUBK test image (German PAL image)\n");
printf(" tx-ebu Transmit EBU test image (color bars)\n");
printf(" tx-convergence Transmit convergence grid for color adjustemnt\n");
printf(" tx-red Transmit single color image for DY adjustment\n");
printf(" tx-vcr Transmit VCR calibration pattern\n");
printf(" tx-img <image> Transmit given image file\n");
printf(" tx-img [<image>] Transmit natural image or given image file\n");
printf(" Use 4:3 image with 574 lines for best result.\n");
printf("\ngeneral options:\n");
printf(" -f --frequency <frequency>\n");
@ -118,6 +122,9 @@ void print_help(const char *arg0)
printf(" -I --sation-id \"<text>\"\n");
printf(" Give exactly 12 characters to display as Station ID.\n");
printf(" (default = \"%s\")\n", station_id);
printf("\nconvergence options:\n");
printf(" -W --grid-width 2 | 1\n");
printf(" Thickness of grid. (default = %d)\n", grid_width);
#ifdef HAVE_SDR
printf(" --limesdr\n");
printf(" Auto-select several required options for LimeSDR\n");
@ -144,6 +151,7 @@ static void add_options(void)
option_add('C', "color-bar", 1);
option_add('G', "grid-only", 1);
option_add('I', "station-id", 1);
option_add('W', "grid-width", 1);
#ifdef HAVE_SDR
option_add(OPT_LIMESDR, "limesdr", 0);
option_add(OPT_LIMESDR_MINI, "limesdr-mini", 0);
@ -195,6 +203,9 @@ static int handle_options(int short_option, int argi, char **argv)
case 'G':
grid_only = atoi(argv[argi]);
break;
case 'W':
grid_width = atoi(argv[argi]);
break;
case 'I':
station_id = strdup(argv[argi]);
if (strlen(station_id) != 12) {
@ -379,7 +390,7 @@ static int tx_test_picture(enum bas_type type)
fprintf(stderr, "No mem!\n");
goto error;
}
bas_init(&bas, samplerate, type, fbas, circle_radius, color_bar, grid_only, station_id, NULL, 0, 0);
bas_init(&bas, samplerate, type, fbas, circle_radius, color_bar, grid_only, station_id, grid_width, NULL, 0, 0);
count = bas_generate(&bas, test_bas);
count += bas_generate(&bas, test_bas + count);
count += bas_generate(&bas, test_bas + count);
@ -413,6 +424,8 @@ error:
return ret;
}
extern uint32_t sample_image[];
static int tx_img(const char *filename)
{
unsigned short *img = NULL;
@ -422,11 +435,22 @@ static int tx_img(const char *filename)
int ret = -1;
int count;
if (filename) {
img = load_img(&width, &height, filename, 0);
if (!img) {
fprintf(stderr, "Failed to load image '%s'\n", filename);
return -1;
}
} else {
int i;
width = 764;
height = 574;
img = (unsigned short *)sample_image;
for (i = 0; i < width * height * 6 / 4; i++)
sample_image[i] = htobe32(sample_image[i]);
for (i = 0; i < width * height * 3; i++)
img[i] = le16toh(img[i]);
}
/* test image, add some samples in case of overflow due to rounding errors */
img_bas = calloc(samplerate / 25.0 * 4.0 + 10.0, sizeof(sample_t));
@ -434,7 +458,7 @@ static int tx_img(const char *filename)
fprintf(stderr, "No mem!\n");
goto error;
}
bas_init(&bas, samplerate, BAS_IMAGE, fbas, circle_radius, color_bar, grid_only, NULL, img, width, height);
bas_init(&bas, samplerate, BAS_IMAGE, fbas, circle_radius, color_bar, grid_only, NULL, grid_width, img, width, height);
count = bas_generate(&bas, img_bas);
count += bas_generate(&bas, img_bas + count);
count += bas_generate(&bas, img_bas + count);
@ -445,6 +469,7 @@ static int tx_img(const char *filename)
ret = 0;
error:
free(img_bas);
if (filename)
free(img);
return ret;
}
@ -489,14 +514,16 @@ int main(int argc, char *argv[])
exit(0);
} else if (!strcmp(argv[argi], "tx-fubk")) {
tx_test_picture(BAS_FUBK);
} else if (!strcmp(argv[argi], "tx-ebu")) {
tx_test_picture(BAS_EBU);
} else if (!strcmp(argv[argi], "tx-convergence")) {
tx_test_picture(BAS_CONVERGENCE);
} else if (!strcmp(argv[argi], "tx-red")) {
tx_test_picture(BAS_RED);
} else if (!strcmp(argv[argi], "tx-vcr")) {
tx_test_picture(BAS_VCR);
} else if (!strcmp(argv[argi], "tx-img")) {
if (argi + 1 >= argc) {
fprintf(stderr, "Expecting image file, use '-h' for help!\n");
return -EINVAL;
}
tx_img(argv[argi + 1]);
tx_img((argi + 1 < argc) ? argv[argi + 1] : NULL);
} else {
fprintf(stderr, "Unknown command '%s', use '-h' for help!\n", argv[argi]);
return -EINVAL;

82232
src/tv/sample_image.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -177,6 +177,7 @@ int vcr_gen_line(sample_t *sample, double x, double samplerate, sample_t *color_
x += step;
}
}
break;
}
return i;