Working black & white TV transmitter (PAL-L / SECAM timings and polarity by default - No Audio).

This commit is contained in:
Jean-François DEL NERO 2023-02-22 09:03:50 +01:00
parent fafd7fa578
commit 03193173f3
4 changed files with 368 additions and 17 deletions

View File

@ -24,14 +24,17 @@ endif
ifeq ($(findstring CYGWIN,$(shell uname)),CYGWIN)
endif
EXEC=broadcast_fm rf_jammer
EXEC=broadcast_tv broadcast_fm rf_jammer
all: $(EXEC)
broadcast_fm: broadcast_fm.o rds.o rds_stream_dump.o wave.o modulator.o FIR_Audio_Filter_Filter.o FM_Baseband_Filter.o AudioPreemphasis_Filter.o FIR_RDS_Passband_Filter.o hxcmod.o rand_gen.o
$(CC) -o $@ $^ $(LDFLAGS)
rf_jammer: rf_jammer.o wave.o modulator.o rand_gen.o
broadcast_tv: broadcast_tv.o composite.o wave.o modulator.o bmp_file.o
$(CC) -o $@ $^ $(LDFLAGS)
rf_jammer: rf_jammer.o wave.o modulator.o rand_gen.o composite.o
$(CC) -o $@ $^ $(LDFLAGS)
broadcast_fm.o: ../src/broadcast_fm/broadcast_fm.c
@ -70,9 +73,18 @@ AudioPreemphasis_Filter.o: ../src/broadcast_fm/fir_filters/AudioPreemphasis_Filt
hxcmod.o: ../src/common/hxcmod/hxcmod.c
$(CC) -o $@ -c $< $(CFLAGS)
bmp_file.o: ../src/common/bmp_file.c
$(CC) -o $@ -c $< $(CFLAGS)
rand_gen.o: ../src/common/rand_gen.c
$(CC) -o $@ -c $< $(CFLAGS)
composite.o: ../src/broadcast_tv/composite.c
$(CC) -o $@ -c $< $(CFLAGS)
broadcast_tv.o: ../src/broadcast_tv/broadcast_tv.c
$(CC) -o $@ -c $< $(CFLAGS)
clean:
rm -rf *.o
rm -rf *.so

View File

@ -0,0 +1,255 @@
///////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------//
//-------------------------------------------------------------------------------//
//-----------H----H--X----X-----CCCCC----22222----0000-----0000------11----------//
//----------H----H----X-X-----C--------------2---0----0---0----0--1--1-----------//
//---------HHHHHH-----X------C----------22222---0----0---0----0-----1------------//
//--------H----H----X--X----C----------2-------0----0---0----0-----1-------------//
//-------H----H---X-----X---CCCCC-----222222----0000-----0000----1111------------//
//-------------------------------------------------------------------------------//
//----------------------------------------------------- http://hxc2001.free.fr --//
///////////////////////////////////////////////////////////////////////////////////
// File : broadcast_tv.c
// Contains: a broadcast TV modulator
//
// This file is part of rf-tools.
//
// Written by: Jean-François DEL NERO
//
// Copyright (C) 2023 Jean-François DEL NERO
//
// You are free to do what you want with this code.
// A credit is always appreciated if you use it into your product :)
//
// Change History (most recent first):
///////////////////////////////////////////////////////////////////////////////////
//
// Disclaimer / Legal warning : Radio spectrum and the law
//
// In most countries the use of any radio transmitting device is required to be
// either licensed or specifically exempted from licensing under the local regulator.
// Other than as used in accordance with a licence (or exemption),
// the use of radio equipment is illegal.
//
// So take care to limit the emitting range and power when testing this software !
//
// --------------------------------------------------------------------------------
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdint.h>
#include "wave.h"
#include "modulator.h"
#include "utils.h"
#include "composite.h"
#include "bmp_file.h"
#define IQ_SAMPLE_RATE 16000000
#define IF_FREQ 0
#define BUFFER_SAMPLES_SIZE (1024*8)
#define printf(fmt...) do { \
if(!stdoutmode) \
fprintf(stdout, fmt); \
} while (0)
int stdoutmode;
int isOption(int argc, char* argv[],char * paramtosearch,char * argtoparam)
{
int param=1;
int i,j;
char option[512];
memset(option,0,512);
while(param<=argc)
{
if(argv[param])
{
if(argv[param][0]=='-')
{
memset(option,0,512);
j=0;
i=1;
while( argv[param][i] && argv[param][i]!=':')
{
option[j]=argv[param][i];
i++;
j++;
}
if( !strcmp(option,paramtosearch) )
{
if(argtoparam)
{
if(argv[param][i]==':')
{
i++;
j=0;
while( argv[param][i] )
{
argtoparam[j]=argv[param][i];
i++;
j++;
}
argtoparam[j]=0;
return 1;
}
else
{
return -1;
}
}
else
{
return 1;
}
}
}
}
param++;
}
return 0;
}
void printhelp(char* argv[])
{
printf("Options:\n");
printf(" -stdout \t\t\t: IQ stream send to stdout\n");
printf(" -bmp_file:[BMPFILE.BMP]\t: BMP image file to display\n");
printf(" -generate \t\t\t: Generate the video IQ stream\n");
printf(" -help \t\t\t: This help\n\n");
printf("Example : 525MHz TV broadcasting with an hackrf (currently B&W with PAL-L/SECAM timings and polarity)\n./broadcast_tv -generate -stdout -bmp_file:Philips_PM5544.bmp | hackrf_transfer -f 525000000 -t - -x 47 -a 1 -s 16000000\n");
printf("\n");
}
int main(int argc, char* argv[])
{
unsigned int i,j;
int ret;
char filename[512];
wave_io * wave1,*wave2;
double video_buf[BUFFER_SAMPLES_SIZE];
composite_state vid_stat;
int16_t subcarriers_dbg_wave_buf[BUFFER_SAMPLES_SIZE];
uint16_t iq_wave_buf[ BUFFER_SAMPLES_SIZE];
iq_wave_gen iqgen;
bitmap_data bmp_data;
stdoutmode = 0;
if(isOption(argc,argv,"stdout",NULL)>0)
{
stdoutmode = 1;
}
if(!stdoutmode)
{
printf("broadcast_tv v0.0.1.1\n");
printf("Copyright (C) 2023 Jean-Francois DEL NERO\n");
printf("This program comes with ABSOLUTELY NO WARRANTY\n");
printf("This is free software, and you are welcome to redistribute it\n");
printf("under certain conditions;\n\n");
}
// help option...
if(isOption(argc,argv,"help",0)>0)
{
printhelp(argv);
}
memset(filename,0,sizeof(filename));
if(isOption(argc,argv,"bmp_file",(char*)&filename)>0)
{
printf("Input file : %s\n",filename);
}
memset(&bmp_data,0,sizeof(bmp_data));
if(strlen(filename))
{
ret = bmp_load(filename,&bmp_data);
if( ret >= 0)
{
printf("BMP file loaded successfully\n");
}
else
{
printf("BMP load error %d\n",ret);
}
}
if(isOption(argc,argv,"generate",0)>0)
{
// IQ Modulator
iqgen.phase = 0;
iqgen.Frequency = IF_FREQ;
iqgen.Amplitude = 127;
iqgen.sample_rate = IQ_SAMPLE_RATE;
init_composite(&vid_stat, 16000000, 768, 576,bmp_data.data);
if(stdoutmode)
{
// stdout / stream mode : IQ are outputed to the stdout -> use a pipe to hackrf_transfer
wave1 = create_wave(NULL,iqgen.sample_rate,WAVE_FILE_FORMAT_RAW_8BITS_IQ);
wave2 = NULL;
}
else
{
// file mode : create iq + wav files
wave1 = create_wave("broadcast_tv.iq",iqgen.sample_rate,WAVE_FILE_FORMAT_RAW_8BITS_IQ);
wave2 = create_wave("broadcast_tv.wav",16000000,WAVE_FILE_FORMAT_WAV_16BITS_MONO);
}
for(i=0;i<1024*16*2 || stdoutmode;i++)
{
gen_video_signal(&vid_stat, (double*)&video_buf, BUFFER_SAMPLES_SIZE);
for(j=0;j<BUFFER_SAMPLES_SIZE;j++)
{
subcarriers_dbg_wave_buf[j] = (int)((video_buf[j]/(double)100) * (double)32767);
iqgen.Amplitude = ((double)video_buf[j]);
iq_wave_buf[j] = get_next_iq(&iqgen);
}
write_wave(wave1, &iq_wave_buf,BUFFER_SAMPLES_SIZE);
write_wave(wave2, &subcarriers_dbg_wave_buf,BUFFER_SAMPLES_SIZE);
}
close_wave(wave1);
close_wave(wave2);
}
if( (isOption(argc,argv,"help",0)<=0) &&
(isOption(argc,argv,"generate",0)<=0)
)
{
printhelp(argv);
}
return 0;
}

View File

@ -32,10 +32,14 @@
#define EQUALIZING_PULSE_LEN 2.35
#define LINE_PULSE_LEN 4.7
#define ASSERTED_LEVEL 10
#define DEASSERTED_LEVEL 100
#define ASSERTED_LEVEL 0
#define DEASSERTED_LEVEL 40
#define COMPOSITE_LINE_PERIOD_US 64
// PAL timings
// PAL / Secam : 768 x 576
// NTSC : 720 x 480 (Old : 640 x 480)
// PAL-L timings
pulses_state vertical_blanking[]=
{
// First sequence of equalizing pulses
@ -45,8 +49,11 @@ pulses_state vertical_blanking[]=
// Duration of second sequence of equalizing pulses
{ 5, ASSERTED_LEVEL, DEASSERTED_LEVEL, EQUALIZING_PULSE_LEN/(double)1E6 ,((COMPOSITE_LINE_PERIOD_US/2)/(double)1E6),0},
// Lines (6 > 310)
{305, ASSERTED_LEVEL, DEASSERTED_LEVEL, LINE_PULSE_LEN/(double)1E6 ,((COMPOSITE_LINE_PERIOD_US)/(double)1E6),0},
// Blank Lines (6 > 23)
{18, ASSERTED_LEVEL, DEASSERTED_LEVEL, LINE_PULSE_LEN/(double)1E6 ,((COMPOSITE_LINE_PERIOD_US)/(double)1E6),0},
// Lines (24 > 310)
{287, ASSERTED_LEVEL, DEASSERTED_LEVEL, LINE_PULSE_LEN/(double)1E6 ,((COMPOSITE_LINE_PERIOD_US)/(double)1E6),1},
// First sequence of equalizing pulses
{ 5, ASSERTED_LEVEL, DEASSERTED_LEVEL, EQUALIZING_PULSE_LEN/(double)1E6 ,((COMPOSITE_LINE_PERIOD_US/2)/(double)1E6),0},
@ -58,8 +65,11 @@ pulses_state vertical_blanking[]=
// -- end of line 318
{ 1, DEASSERTED_LEVEL, DEASSERTED_LEVEL, 0 ,(COMPOSITE_LINE_PERIOD_US/2)/(double)1E6,0},
// Lines (319 > 622)
{304, ASSERTED_LEVEL, DEASSERTED_LEVEL, LINE_PULSE_LEN/(double)1E6 ,(COMPOSITE_LINE_PERIOD_US)/(double)1E6,0},
// Blank Lines (319 > 335)
{17, ASSERTED_LEVEL, DEASSERTED_LEVEL, LINE_PULSE_LEN/(double)1E6 ,(COMPOSITE_LINE_PERIOD_US)/(double)1E6,0},
// Lines (336 > 622)
{287, ASSERTED_LEVEL, DEASSERTED_LEVEL, LINE_PULSE_LEN/(double)1E6 ,(COMPOSITE_LINE_PERIOD_US)/(double)1E6,3},
// Line 623 - (half)
{ 1, ASSERTED_LEVEL, DEASSERTED_LEVEL, LINE_PULSE_LEN/(double)1E6 ,((COMPOSITE_LINE_PERIOD_US)/2)/(double)1E6,0},
@ -69,16 +79,57 @@ pulses_state vertical_blanking[]=
};
void init_composite(composite_state * state, int sample_rate, int x_res, int y_res)
void init_composite(composite_state * state, int sample_rate, int x_res, int y_res, uint32_t * bmp)
{
int i;
state->sample_period = (double)1 / (double)sample_rate;
state->buf_x_res = x_res;
state->buf_y_res = y_res;
state->video_buffer = malloc( x_res * y_res * sizeof(uint32_t) );
if(state->video_buffer)
memset(state->video_buffer,0,x_res * y_res * sizeof(uint32_t) );
state->video_buffer = bmp;
if(!state->video_buffer)
{
state->video_buffer = malloc( x_res * y_res * sizeof(uint32_t) );
if(state->video_buffer)
{
memset(state->video_buffer,0,x_res * y_res * sizeof(uint32_t) );
// Basic Mire test init
for(i=0;i<x_res;i++)
state->video_buffer[i] = 0xFFFFFF;
for(i=0;i<x_res;i++)
state->video_buffer[((y_res-1)*x_res) + i] = 0xFFFFFF;
for(i=0;i<y_res;i++)
state->video_buffer[((i)*x_res)] = 0xFFFFFF;
for(i=0;i<y_res;i++)
state->video_buffer[((i)*x_res) + (x_res - 1)] = 0xFFFFFF;
for(i=0;i<x_res/2;i++)
{
state->video_buffer[(x_res/4) + (((y_res/4)-1)*x_res) + i] = 0xFFFFFF;
state->video_buffer[(x_res/4) + (((y_res/4)-1)*x_res) + i + x_res] = 0xFFFFFF;
state->video_buffer[(x_res/4) + (((y_res - (y_res/4))-1)*x_res) + i] = 0xFFFFFF;
state->video_buffer[(x_res/4) + (((y_res - (y_res/4))-1)*x_res) + i + x_res] = 0xFFFFFF;
state->video_buffer[(x_res/4) + (((y_res - (y_res/2))-1)*x_res) + i] = 0x6F6F6F;
state->video_buffer[(x_res/4) + (((y_res - (y_res/2))-1)*x_res) + i + x_res] = 0x6F6F6F;
}
for(i=y_res/4;i<(y_res - (y_res/4));i++)
{
state->video_buffer[(x_res/4) + ((i)*x_res)] = 0xFFFFFF;
state->video_buffer[(x_res/4) + ((i)*x_res) + (x_res/4)] = 0x6F6F6F;
state->video_buffer[(x_res/2) + ((i)*x_res) + (x_res/4)] = 0xFFFFFF;
}
}
}
state->step_index = 0;
state->cur_state_time = 0;
@ -86,7 +137,7 @@ void init_composite(composite_state * state, int sample_rate, int x_res, int y_r
void gen_video_signal(composite_state * state, double * vid_signal, int buf_size)
{
int i;
int i,xpos;
double value;
value = 0;
@ -98,6 +149,11 @@ void gen_video_signal(composite_state * state, double * vid_signal, int buf_size
state->cur_state_time = 0;
state->repeat_cnt++;
if(vertical_blanking[state->step_index].type & 1)
state->cur_line_index++;
else
state->cur_line_index = 0;
if( state->repeat_cnt >= vertical_blanking[state->step_index].repeat )
{
state->repeat_cnt = 0;
@ -119,6 +175,35 @@ void gen_video_signal(composite_state * state, double * vid_signal, int buf_size
value = vertical_blanking[state->step_index].start_val;
}
if(vertical_blanking[state->step_index].type & 1)
{
if(state->cur_state_time >= vertical_blanking[state->step_index].first_duration)
{
if(state->cur_state_time >= vertical_blanking[state->step_index].first_duration + ((3.5+4.7)/(double)1E6))
{
// Stream pixels line
if(state->cur_state_time <= vertical_blanking[state->step_index].first_duration + ((3.5+4.7)/(double)1E6) + (45.6/(double)1E6) )
{
xpos = ((state->cur_state_time - ( vertical_blanking[state->step_index].first_duration + ((3.5+4.7)/(double)1E6) )) / (45.6/(double)1E6)) * state->buf_x_res;
value = vertical_blanking[state->step_index].end_val;
value += ((double)((state->video_buffer[(state->cur_line_index * state->buf_x_res * 2) + ( state->buf_x_res * (((vertical_blanking[state->step_index].type>>1)^1)&1)) + xpos] & 0xFF)) * ((double)((double)100.0 - vertical_blanking[state->step_index].end_val)/(double)256));
}
else
{
value = vertical_blanking[state->step_index].end_val;
}
}
else
{
// TODO : Color Burst
value = vertical_blanking[state->step_index].end_val;
}
}
}
else
state->cur_line_index = 0;
vid_signal[i] = value;
state->cur_state_time += state->sample_period;

View File

@ -24,8 +24,6 @@
// Change History (most recent first):
///////////////////////////////////////////////////////////////////////////////////
#define COMPOSITE_LINE_PERIOD_US 64.0
typedef struct composite_state_
{
double sample_period;
@ -35,6 +33,7 @@ typedef struct composite_state_
uint32_t * video_buffer;
int cur_line_index;
int step_index;
int repeat_cnt;
@ -53,5 +52,5 @@ typedef struct pulses_state_
}pulses_state;
void init_composite(composite_state * state, int sample_rate, int x_res, int y_res);
void init_composite(composite_state * state, int sample_rate, int x_res, int y_res, uint32_t * bmp);
void gen_video_signal(composite_state * state, double * vid_signal, int buf_size);