From 03193173f34e514fe6650c1447c231323c46d6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20DEL=20NERO?= Date: Wed, 22 Feb 2023 09:03:50 +0100 Subject: [PATCH] Working black & white TV transmitter (PAL-L / SECAM timings and polarity by default - No Audio). --- build/Makefile | 16 +- src/broadcast_tv/broadcast_tv.c | 255 ++++++++++++++++++++++++++++++++ src/broadcast_tv/composite.c | 109 ++++++++++++-- src/broadcast_tv/composite.h | 5 +- 4 files changed, 368 insertions(+), 17 deletions(-) create mode 100644 src/broadcast_tv/broadcast_tv.c diff --git a/build/Makefile b/build/Makefile index eb30869..181544a 100644 --- a/build/Makefile +++ b/build/Makefile @@ -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 diff --git a/src/broadcast_tv/broadcast_tv.c b/src/broadcast_tv/broadcast_tv.c new file mode 100644 index 0000000..d5b7449 --- /dev/null +++ b/src/broadcast_tv/broadcast_tv.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 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;ivideo_buffer[i] = 0xFFFFFF; + + for(i=0;ivideo_buffer[((y_res-1)*x_res) + i] = 0xFFFFFF; + + for(i=0;ivideo_buffer[((i)*x_res)] = 0xFFFFFF; + + for(i=0;ivideo_buffer[((i)*x_res) + (x_res - 1)] = 0xFFFFFF; + + for(i=0;ivideo_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; diff --git a/src/broadcast_tv/composite.h b/src/broadcast_tv/composite.h index 3a7dac1..5b9690b 100644 --- a/src/broadcast_tv/composite.h +++ b/src/broadcast_tv/composite.h @@ -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);