├── .gitignore ├── COPYING ├── README └── src ├── Makefile ├── acp.c ├── acp.h ├── av.c ├── av.h ├── av_ffmpeg.c ├── av_ffmpeg.h ├── av_test.c ├── av_test.h ├── build_win64.sh ├── cc608.c ├── cc608.h ├── common.c ├── common.h ├── dance.c ├── dance.h ├── demo.tti ├── eurocrypt.c ├── eurocrypt.h ├── fifo.c ├── fifo.h ├── fir.c ├── fir.h ├── hacktv.1 ├── hacktv.c ├── hacktv.h ├── mac.c ├── mac.h ├── nicam728.c ├── nicam728.h ├── rf.c ├── rf.h ├── rf_file.c ├── rf_file.h ├── rf_fl2k.c ├── rf_fl2k.h ├── rf_hackrf.c ├── rf_hackrf.h ├── rf_soapysdr.c ├── rf_soapysdr.h ├── sis.c ├── sis.h ├── spdif.c ├── spdif.h ├── syster.c ├── syster.h ├── teletext.c ├── teletext.h ├── vbidata.c ├── vbidata.h ├── video.c ├── video.h ├── videocrypt.c ├── videocrypt.h ├── videocrypts-sequence.h ├── videocrypts.c ├── videocrypts.h ├── vitc.c ├── vitc.h ├── vits.c ├── vits.h ├── wss.c └── wss.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | *.exe 4 | build_win64 5 | hacktv 6 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | -[ HackTV - Analogue TV transmitter for the HackRF ]- 3 | 4 | https://sanslogic.co.uk/hacktv 5 | 6 | WHAT'S IT DO 7 | 8 | Generates a PAL, NTSC, SECAM, D/D2-MAC video signal from a video file, stream 9 | or test pattern. Also supports older 819, 405, 240 and 30 line standards, as 10 | well as the NASA Apollo video standards, both colour and mono. 11 | 12 | Input is any file type or URL supported by ffmpeg. 13 | 14 | Output can be to a file, HackRF, fl2k-supported VGA adaptors or any SDR 15 | supported by SoapySDR. 16 | 17 | It also supports: 18 | 19 | + Teletext (625-line only) 20 | + NICAM stereo audio 21 | + Videocrypt I/II/S hardware support 22 | + Partial Nagravision Syster hardware support 23 | + Analogue Copy Protection system, similar to Macrovision 24 | 25 | 26 | WHAT'S IT NOT DO (yet) 27 | 28 | + An optional notch filter for the colour subcarrier would be nice 29 | 30 | 31 | WHAT IT WON'T DO 32 | 33 | + DVB or other pure digital signals 34 | + Bring back Firefly :( 35 | 36 | 37 | REQUIREMENTS 38 | 39 | Depends on libhackrf and various ffmpeg libraries. 40 | 41 | * For Fedora (with rpmfusion) 42 | yum install hackrf-devel osmo-fl2k-devel SoapySDR-devel ffmpeg-devel 43 | 44 | * For Debian and related 45 | apt-get update 46 | apt-get install libhackrf-dev libavutil-dev libavdevice-dev libswresample-dev libswscale-dev libavformat-dev libavcodec-dev 47 | 48 | * On Debian (sid) 49 | apt-get install hacktv 50 | 51 | 52 | WARNING 53 | 54 | The hackrf is not designed to be connected directly to AV equipment and could 55 | be damaged by, or cause damage to, your receiver. Please ensure no DC voltages 56 | or control signals are sent back into the hackrf, and that the RF power levels 57 | out of the hackrf are not too high for your receiver. 58 | 59 | 60 | INSTALL 61 | 62 | cd src 63 | make 64 | make install 65 | 66 | 67 | EXAMPLES 68 | 69 | # Generate a file containing a PAL baseband signal from a video 70 | $ hacktv -o baseband.bin -m pal example.mkv 71 | 72 | # Transmit a test pattern on UHF channel 31 (PAL System I), 47dB TX gain 73 | $ hacktv -f 551250000 -m i -g 47 test 74 | 75 | # Transmit a test pattern with teletext 76 | $ hacktv -f 551250000 -m i -g 47 --teletext demo.tti test 77 | 78 | # Download and transmit teletext pages from the Teefax service 79 | $ svn checkout http://teastop.plus.com/svn/teletext/ teefax 80 | $ hacktv -f 551250000 -m i -g 47 --teletext teefax test 81 | 82 | # Transmit two channels simultaneously on UHF channel 68 and 69 (PAL I) 83 | $ hacktv -s 20000000 --offset -6.75e6 --level 0.5 --filter -o - test | hacktv -s 20000000 -f 854e6 --offset 1.25e6 --level 0.5 --passthru /dev/stdin -g 47 --filter test 84 | 85 | # Grab and transmit the local display (X11) 86 | $ hacktv -f 551.25e6 -m i -g 47 -ffmt x11grab --fopts framerate=25 ffmpeg::0 87 | 88 | LINKS 89 | 90 | https://github.com/captainjack64/hacktv - Fork of hacktv with support for additional scrambling systems 91 | https://github.com/steeviebops/jhacktv-gui - A cross platform GUI for hacktv written in Java 92 | 93 | 94 | -Philip Heron 95 | 96 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | 2 | VERSION := $(shell git log -1 --pretty='%cd' --date=format:'%Y%m%d')-$(shell git describe --dirty --always) 3 | CC := $(CROSS_HOST)gcc 4 | PKGCONF := pkg-config 5 | CFLAGS := -g -Wall -pthread -O3 $(EXTRA_CFLAGS) -DVERSION=\"$(VERSION)\" 6 | LDFLAGS := -g -lm -pthread $(EXTRA_LDFLAGS) 7 | OBJS := hacktv.o common.o fir.o vbidata.o teletext.o wss.o video.o fifo.o mac.o dance.o eurocrypt.o videocrypt.o videocrypts.o syster.o acp.o vits.o vitc.o nicam728.o sis.o av.o av_test.o av_ffmpeg.o rf.o rf_file.o spdif.o cc608.o 8 | PKGS := libavcodec libavformat libavdevice libswscale libswresample libavutil $(EXTRA_PKGS) 9 | 10 | HACKRF := $(shell $(PKGCONF) --exists libhackrf && echo hackrf) 11 | ifeq ($(HACKRF),hackrf) 12 | OBJS += rf_hackrf.o 13 | PKGS += libhackrf 14 | CFLAGS += -DHAVE_HACKRF 15 | endif 16 | 17 | SOAPYSDR := $(shell $(PKGCONF) --exists SoapySDR && echo SoapySDR) 18 | ifeq ($(SOAPYSDR),SoapySDR) 19 | OBJS += rf_soapysdr.o 20 | PKGS += SoapySDR 21 | CFLAGS += -DHAVE_SOAPYSDR 22 | endif 23 | 24 | FL2K := $(shell $(PKGCONF) --exists libosmo-fl2k && echo fl2k) 25 | ifeq ($(FL2K),fl2k) 26 | OBJS += rf_fl2k.o 27 | PKGS += libosmo-fl2k 28 | CFLAGS += -DHAVE_FL2K 29 | endif 30 | 31 | CFLAGS += $(shell $(PKGCONF) --cflags $(PKGS)) 32 | LDFLAGS += $(shell $(PKGCONF) --libs $(PKGS)) 33 | 34 | all: hacktv 35 | 36 | hacktv: $(OBJS) 37 | $(CC) -o hacktv $(OBJS) $(LDFLAGS) 38 | 39 | %.o: %.c Makefile 40 | $(CC) $(CFLAGS) -c $< -o $@ 41 | @$(CC) $(CFLAGS) -MM $< -o $(@:.o=.d) 42 | 43 | install: 44 | cp -f hacktv $(PREFIX)/usr/local/bin/ 45 | 46 | clean: 47 | rm -f *.o *.d hacktv hacktv.exe 48 | 49 | -include $(OBJS:.o=.d) 50 | 51 | -------------------------------------------------------------------------------- /src/acp.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2019 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | /* -=== ACP / Macrovision encoder ===- */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "video.h" 25 | 26 | int acp_init(acp_t *s, vid_t *vid) 27 | { 28 | double left; 29 | double spacing; 30 | double psync_width; 31 | int i; 32 | 33 | memset(s, 0, sizeof(acp_t)); 34 | 35 | if(vid->conf.lines == 625) 36 | { 37 | left = 8.88e-6; 38 | spacing = 5.92e-6; 39 | psync_width = 2.368e-6; 40 | } 41 | else 42 | { 43 | left = 8.288e-6; 44 | spacing = 8.288e-6; 45 | psync_width = 2.222e-6; 46 | } 47 | 48 | /* Calculate the levels */ 49 | s->psync_level = vid->sync_level + round((vid->white_level - vid->sync_level) * 0.06); 50 | s->pagc_level = vid->sync_level + round((vid->white_level - vid->sync_level) * 1.10); 51 | 52 | /* Calculate the width of each pulse */ 53 | s->psync_width = round(vid->pixel_rate * psync_width); 54 | s->pagc_width = round(vid->pixel_rate * 2.7e-6); 55 | 56 | /* Left position of each pulse */ 57 | for(i = 0; i < 6; i++) 58 | { 59 | s->left[i] = round(vid->pixel_rate * (left + spacing * i)); 60 | } 61 | 62 | return(VID_OK); 63 | } 64 | 65 | void acp_free(acp_t *s) 66 | { 67 | if(s == NULL) return; 68 | 69 | memset(s, 0, sizeof(acp_t)); 70 | } 71 | 72 | int acp_render_line(vid_t *s, void *arg, int nlines, vid_line_t **lines) 73 | { 74 | acp_t *a = arg; 75 | int i, x; 76 | vid_line_t *l = lines[0]; 77 | 78 | i = 0; 79 | 80 | if(l->line == 1) 81 | { 82 | /* Vary the AGC pulse level, clipped sawtooth waveform */ 83 | i = abs(l->frame * 4 % 1712 - 856) - 150; 84 | 85 | if(i < 0) i = 0; 86 | else if(i > 255) i = 255; 87 | 88 | i = s->yuv_level_lookup[i << 16 | i << 8 | i].y; 89 | 90 | a->pagc_level = s->sync_level + round((i - s->sync_level) * 1.10); 91 | } 92 | 93 | i = 0; 94 | 95 | if(s->conf.lines == 625) 96 | { 97 | /* For 625-line modes, ACP is rendered on lines 9-18 and 321-330 */ 98 | if(l->line >= 9 && l->line <= 18) i = 1; 99 | if(l->line >= 321 && l->line <= 330) i = 1; 100 | } 101 | else 102 | { 103 | /* For 525-line modes, ACP is rendered on lines 12-19 and 275-282 */ 104 | if(l->line >= 12 && l->line <= 19) i = 1; 105 | if(l->line >= 275 && l->line <= 282) i = 1; 106 | } 107 | 108 | if(i == 0 || l->vbialloc) return(1); 109 | 110 | /* Render the P-Sync / AGC pulse pairs */ 111 | for(i = 0; i < 6; i++) 112 | { 113 | /* Render the P-Sync pulse */ 114 | for(x = a->left[i]; x < a->left[i] + a->psync_width; x++) 115 | { 116 | l->output[x * 2] = a->psync_level; 117 | } 118 | 119 | /* Render the AGC pulse */ 120 | for(; x < a->left[i] + a->psync_width + a->pagc_width; x++) 121 | { 122 | l->output[x * 2] = a->pagc_level; 123 | } 124 | } 125 | 126 | l->vbialloc = 1; 127 | 128 | return(1); 129 | } 130 | 131 | -------------------------------------------------------------------------------- /src/acp.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2019 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _ACP_H 19 | #define _ACP_H 20 | 21 | #include 22 | #include "video.h" 23 | 24 | typedef struct { 25 | 26 | int left[6]; 27 | 28 | int16_t psync_level; 29 | int16_t pagc_level; 30 | 31 | int psync_width; 32 | int pagc_width; 33 | 34 | } acp_t; 35 | 36 | extern int acp_init(acp_t *s, vid_t *vid); 37 | extern void acp_free(acp_t *s); 38 | extern int acp_render_line(vid_t *s, void *arg, int nlines, vid_line_t **lines); 39 | 40 | #endif 41 | 42 | -------------------------------------------------------------------------------- /src/av.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2023 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include "av.h" 20 | 21 | void av_frame_init(av_frame_t *frame, int width, int height, uint32_t *framebuffer, int pstride, int lstride) 22 | { 23 | *frame = (av_frame_t) { 24 | .width = width, 25 | .height = height, 26 | .framebuffer = framebuffer, 27 | .pixel_stride = pstride, 28 | .line_stride = lstride, 29 | .pixel_aspect_ratio = { 1, 1 }, 30 | .interlaced = 0, 31 | .cc608 = { 0, 0 }, 32 | }; 33 | } 34 | 35 | int av_read_video(av_t *s, av_frame_t *frame) 36 | { 37 | int r = AV_EOF; 38 | 39 | if(s->read_video) 40 | { 41 | r = s->read_video(s->av_source_ctx, frame); 42 | 43 | if(r != AV_OK) 44 | { 45 | /* EOF or error */ 46 | s->read_video = NULL; 47 | } 48 | } 49 | else 50 | { 51 | /* Send a blank frame */ 52 | av_frame_init(frame, 0, 0, NULL, 0, 0); 53 | r = AV_OK; 54 | } 55 | 56 | if(r == AV_OK) s->frames++; 57 | 58 | return(r); 59 | } 60 | 61 | int av_read_audio(av_t *s, int16_t **samples, size_t *nsamples) 62 | { 63 | int r = AV_EOF; 64 | 65 | *samples = NULL; 66 | *nsamples = 0; 67 | 68 | if(s->read_audio) 69 | { 70 | r = s->read_audio(s->av_source_ctx, samples, nsamples); 71 | 72 | if(r != AV_OK) 73 | { 74 | /* EOF or error */ 75 | s->read_audio = NULL; 76 | } 77 | } 78 | 79 | if(r == AV_OK) s->samples += *nsamples; 80 | 81 | return(r); 82 | } 83 | 84 | int av_eof(av_t *s) 85 | { 86 | return(s->read_video == NULL && s->read_audio == NULL ? 1 : 0); 87 | } 88 | 89 | int av_close(av_t *s) 90 | { 91 | int r; 92 | 93 | r = s->close ? s->close(s->av_source_ctx) : AV_ERROR; 94 | 95 | s->av_source_ctx = NULL; 96 | s->read_video = NULL; 97 | s->read_audio = NULL; 98 | s->close = NULL; 99 | 100 | return(r); 101 | } 102 | 103 | r64_t av_calculate_frame_size(av_t *av, r64_t resolution, r64_t aspect) 104 | { 105 | r64_t r = { av->width, av->height }; 106 | const r64_t fadj[][2] = { 107 | /* Horizontal resolution adjustment factors based on the list at: 108 | * https://xpt.sourceforge.net/techdocs/media/video/dvd/dvd04-DVDAuthoringSpecwise/ar01s02.html 109 | */ 110 | { { 720, 576 }, { 720, 702 } }, /* D1/DV/DVB/DVD/SVCD */ 111 | { { 704, 576 }, { 704, 702 } }, /* DVB/DVD/VCD */ 112 | { { 544, 576 }, { 1088, 1053 } }, /* DVB */ 113 | { { 480, 576 }, { 480, 468 } }, /* SVCD */ 114 | { { 384, 288 }, { 768, 767 } }, 115 | { { 352, 576 }, { 352, 351 } }, /* DVD */ 116 | { { 352, 288 }, { 352, 351 } }, /* VCD/DVD */ 117 | { { 176, 144 }, { 352, 351 } }, /* H.261/H.263 */ 118 | 119 | { { 720, 480 }, { 1600, 1587 } }, /* DVD */ 120 | { { 704, 480 }, { 14080, 14283 } }, /* ATSC/DVD/VCD */ 121 | 122 | { } 123 | }; 124 | 125 | if(av->fit_mode == AV_FIT_STRETCH) 126 | { 127 | /* Mode "stretch" ignores the source aspect, 128 | * always returns the active resolution */ 129 | } 130 | else if(av->fit_mode == AV_FIT_NONE) 131 | { 132 | /* Mode "none" keeps the source resolution */ 133 | return(resolution); 134 | } 135 | else 136 | { 137 | r64_t b, c; 138 | 139 | /* Use frame size if no aspect set, assume 1:1 pixel ratio */ 140 | if(aspect.num <= 0 || aspect.den <= 0) 141 | { 142 | aspect = resolution; 143 | } 144 | 145 | if(av->fit_mode == AV_FIT_FILL) 146 | { 147 | /* Mode "fill" scales the source video to fill the active frame */ 148 | 149 | /* Find the nearest display aspect ratio if there is more than one */ 150 | c = av->display_aspect_ratios[0]; 151 | 152 | if(av->display_aspect_ratios[1].den > 0) 153 | { 154 | c = r64_nearest(aspect, c, av->display_aspect_ratios[1]); 155 | } 156 | } 157 | else 158 | { 159 | c = aspect; 160 | } 161 | 162 | /* Enforce active ratio limits if set */ 163 | if(av->min_display_aspect_ratio.den > 0 && 164 | r64_cmp(c, av->min_display_aspect_ratio) < 0) 165 | { 166 | c = av->min_display_aspect_ratio; 167 | } 168 | 169 | if(av->max_display_aspect_ratio.den > 0 && 170 | r64_cmp(c, av->max_display_aspect_ratio) > 0) 171 | { 172 | c = av->max_display_aspect_ratio; 173 | } 174 | 175 | /* b = display ratio */ 176 | b = av->display_aspect_ratios[0]; 177 | 178 | if(av->display_aspect_ratios[1].den > 0) 179 | { 180 | b = r64_nearest(c, b, av->display_aspect_ratios[1]); 181 | } 182 | 183 | /* Calculate visible resolution */ 184 | if(r64_cmp(c, b) > 0) 185 | { 186 | /* Vertical padding (Letterbox) */ 187 | r.den = (int64_t) r.den * ((int64_t) b.num * c.den) / ((int64_t) b.den * c.num); 188 | } 189 | else if(r64_cmp(c, b) < 0) 190 | { 191 | /* Horizontal padding (Pillarbox) */ 192 | r.num = (int64_t) r.num * ((int64_t) c.num * b.den) / ((int64_t) c.den * b.num); 193 | } 194 | 195 | /* Calculate source resolution */ 196 | if(r64_cmp(c, aspect) > 0) 197 | { 198 | /* Vertical cropping */ 199 | r.den = (int64_t) r.den * ((int64_t) c.num * aspect.den) / ((int64_t) c.den * aspect.num); 200 | } 201 | else if(r64_cmp(c, aspect) < 0) 202 | { 203 | /* Horizontal cropping */ 204 | r.num = (int64_t) r.num * ((int64_t) aspect.num * c.den) / ((int64_t) aspect.den * c.num); 205 | } 206 | } 207 | 208 | /* Experiment: Adjust final resolution to compensate for padding */ 209 | for(int i = 0; fadj[i][0].num != 0; i++) 210 | { 211 | if(resolution.num == fadj[i][0].num && 212 | resolution.den == fadj[i][0].den) 213 | { 214 | r.num = (int64_t) r.num * fadj[i][1].num / fadj[i][1].den; 215 | break; 216 | } 217 | } 218 | 219 | return(r); 220 | } 221 | 222 | r64_t av_display_aspect_ratio(av_frame_t *frame) 223 | { 224 | /* Helper function to return a frames display aspect ratio */ 225 | /* DAR = SAR * PAR */ 226 | return(r64_mul( 227 | (r64_t) { frame->width, frame->height }, 228 | frame->pixel_aspect_ratio) 229 | ); 230 | } 231 | 232 | void av_set_display_aspect_ratio(av_frame_t *frame, r64_t display_aspect_ratio) 233 | { 234 | /* Helper function to set a frames display aspect ratio */ 235 | /* PAR = DAR / SAR */ 236 | frame->pixel_aspect_ratio = r64_div( 237 | display_aspect_ratio, 238 | (r64_t) { frame->width, frame->height } 239 | ); 240 | } 241 | 242 | void av_hflip_frame(av_frame_t *frame) 243 | { 244 | frame->framebuffer += (frame->width - 1) * frame->pixel_stride; 245 | frame->pixel_stride = -frame->pixel_stride; 246 | } 247 | 248 | void av_vflip_frame(av_frame_t *frame) 249 | { 250 | frame->framebuffer += (frame->height - 1) * frame->line_stride; 251 | frame->line_stride = -frame->line_stride; 252 | } 253 | 254 | void av_rotate_frame(av_frame_t *frame, int a) 255 | { 256 | int i; 257 | 258 | /* a == degrees / 90 */ 259 | a = a % 4; 260 | 261 | if(a == 1 || a == 3) 262 | { 263 | /* Rotate the frame 90 degrees clockwise */ 264 | 265 | /* Move the origin to the bottom left of the image */ 266 | frame->framebuffer += (frame->height - 1) * frame->line_stride; 267 | 268 | /* Reverse the image dimensions */ 269 | i = frame->width; 270 | frame->width = frame->height; 271 | frame->height = i; 272 | 273 | /* Reverse the line and pixel strides */ 274 | i = frame->pixel_stride; 275 | frame->pixel_stride = -frame->line_stride; 276 | frame->line_stride = i; 277 | 278 | /* Reverse the pixel aspect ratio (r = 1 / r) */ 279 | frame->pixel_aspect_ratio = (r64_t) { 280 | frame->pixel_aspect_ratio.den, 281 | frame->pixel_aspect_ratio.num 282 | }; 283 | } 284 | 285 | if(a == 2 || a == 3) 286 | { 287 | /* Rotate the frame 180 degrees */ 288 | av_hflip_frame(frame); 289 | av_vflip_frame(frame); 290 | } 291 | } 292 | 293 | void av_crop_frame(av_frame_t *frame, int x, int y, int width, int height) 294 | { 295 | if(x < 0) { width += x; x = 0; } 296 | if(y < 0) { height += y; y = 0; } 297 | if(x + width > frame->width) width = frame->width - x; 298 | if(y + height > frame->height) height = frame->height - y; 299 | 300 | frame->framebuffer += y * frame->line_stride + x * frame->pixel_stride; 301 | frame->width = width; 302 | frame->height = height; 303 | } 304 | 305 | -------------------------------------------------------------------------------- /src/av.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2023 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _AV_H 19 | #define _AV_H 20 | 21 | #include 22 | #include 23 | #include "common.h" 24 | 25 | /* Return codes */ 26 | #define AV_OK 0 27 | #define AV_ERROR -1 28 | #define AV_OUT_OF_MEMORY -2 29 | #define AV_EOF -3 30 | 31 | typedef struct { 32 | 33 | /* Dimensions */ 34 | int width; 35 | int height; 36 | 37 | /* 32-bit RGBx framebuffer */ 38 | uint32_t *framebuffer; 39 | int pixel_stride; 40 | int line_stride; 41 | 42 | /* The pixel aspect ratio */ 43 | r64_t pixel_aspect_ratio; 44 | 45 | /* Interlace flag: 46 | * 0 = Non-interlaced 47 | * 1 = Top field first 48 | * 2 = Bottom field first */ 49 | int interlaced; 50 | 51 | /* CC608 subtitle data */ 52 | uint8_t cc608[2]; 53 | 54 | } av_frame_t; 55 | 56 | 57 | 58 | /* AV module callbacks: 59 | * 60 | * av_read_video(): Returns AV_OK when a frame is available, or AV_EOF if 61 | * the source has no further video frames. 62 | * Any return code that is not AV_OK is treated as AV_EOF */ 63 | 64 | typedef int (*av_read_video_t)(void *ctx, av_frame_t *frame); 65 | 66 | /* av_read_audio(): Returns AV_OK when audio samples are available, or AV_EOF if 67 | * the source has no further audio samples. 68 | * Any return code that is not AV_OK is treated as AV_EOF */ 69 | 70 | typedef int (*av_read_audio_t)(void *ctx, int16_t **samples, size_t *nsamples); 71 | 72 | /* av_close(): The source is being closed. The return code is ignored */ 73 | 74 | typedef int (*av_close_t)(void *ctx); 75 | 76 | 77 | 78 | /* Frame fit/crop modes */ 79 | typedef enum { 80 | AV_FIT_STRETCH, 81 | AV_FIT_FILL, 82 | AV_FIT_FIT, 83 | AV_FIT_NONE, 84 | } av_fit_mode_t; 85 | 86 | typedef struct { 87 | 88 | pthread_mutex_t mutex; 89 | pthread_cond_t cond; 90 | 91 | /* Video settings */ 92 | int width; 93 | int height; 94 | r64_t frame_rate; 95 | r64_t display_aspect_ratios[2]; 96 | av_fit_mode_t fit_mode; 97 | r64_t min_display_aspect_ratio; 98 | r64_t max_display_aspect_ratio; 99 | av_frame_t default_frame; 100 | 101 | /* Video state */ 102 | unsigned int frames; 103 | 104 | /* Audio settings */ 105 | r64_t sample_rate; 106 | 107 | /* Audio state */ 108 | unsigned int samples; 109 | 110 | /* AV source data and callbacks */ 111 | void *av_source_ctx; 112 | av_read_video_t read_video; 113 | av_read_audio_t read_audio; 114 | av_close_t close; 115 | 116 | } av_t; 117 | 118 | extern void av_frame_init(av_frame_t *frame, int width, int height, uint32_t *framebuffer, int pstride, int lstride); 119 | 120 | extern int av_read_video(av_t *s, av_frame_t *frame); 121 | extern int av_read_audio(av_t *s, int16_t **samples, size_t *nsamples); 122 | extern int av_eof(av_t *s); 123 | extern int av_close(av_t *s); 124 | 125 | extern r64_t av_display_aspect_ratio(av_frame_t *frame); 126 | extern void av_set_display_aspect_ratio(av_frame_t *frame, r64_t display_aspect_ratio); 127 | 128 | extern r64_t av_calculate_frame_size(av_t *s, r64_t resolution, r64_t pixel_aspect_ratio); 129 | 130 | extern void av_hflip_frame(av_frame_t *frame); 131 | extern void av_vflip_frame(av_frame_t *frame); 132 | extern void av_rotate_frame(av_frame_t *frame, int a); 133 | extern void av_crop_frame(av_frame_t *frame, int x, int y, int width, int height); 134 | 135 | #include "av_test.h" 136 | #include "av_ffmpeg.h" 137 | 138 | #endif 139 | 140 | -------------------------------------------------------------------------------- /src/av_ffmpeg.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _FFMPEG_H 19 | #define _FFMPEG_H 20 | 21 | extern int av_ffmpeg_open(av_t *av, char *input_url, char *format, char *options); 22 | extern void av_ffmpeg_init(void); 23 | extern void av_ffmpeg_deinit(void); 24 | 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /src/av_test.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include "hacktv.h" 21 | 22 | /* A small 2-bit hacktv logo */ 23 | #define LOGO_WIDTH 48 24 | #define LOGO_HEIGHT 9 25 | #define LOGO_SCALE 4 26 | static const char *_logo = 27 | " " 28 | " ## ## ## #### ## ## ###### ## ## " 29 | " ## ## #### ## ## ## ## ## ## ## " 30 | " ## ## ## ## ## #### ## ## ## " 31 | " ###### ###### ## ### ## ## ## " 32 | " ## ## ## ## ## #### ## ## ## " 33 | " ## ## ## ## ## ## ## ## ## #### " 34 | " ## ## ## ## #### ## ## ## ## " 35 | " "; 36 | 37 | /* AV test pattern source */ 38 | typedef struct { 39 | int width; 40 | int height; 41 | uint32_t *video; 42 | int16_t *audio; 43 | size_t audio_samples; 44 | } av_test_t; 45 | 46 | static int _test_read_video(void *ctx, av_frame_t *frame) 47 | { 48 | av_test_t *s = ctx; 49 | av_frame_init(frame, s->width, s->height, s->video, 1, s->width); 50 | av_set_display_aspect_ratio(frame, (r64_t) { 4, 3 }); 51 | return(AV_OK); 52 | } 53 | 54 | static int _test_read_audio(void *ctx, int16_t **samples, size_t *nsamples) 55 | { 56 | av_test_t *s = ctx; 57 | *samples = s->audio; 58 | *nsamples = s->audio_samples; 59 | return(AV_OK); 60 | } 61 | 62 | static int _test_close(void *ctx) 63 | { 64 | av_test_t *s = ctx; 65 | if(s->video) free(s->video); 66 | if(s->audio) free(s->audio); 67 | free(s); 68 | return(AV_OK); 69 | } 70 | 71 | int av_test_open(av_t *av) 72 | { 73 | uint32_t const bars[8] = { 74 | 0x000000, 75 | 0x0000BF, 76 | 0xBF0000, 77 | 0xBF00BF, 78 | 0x00BF00, 79 | 0x00BFBF, 80 | 0xBFBF00, 81 | 0xFFFFFF, 82 | }; 83 | av_test_t *s; 84 | int c, x, y; 85 | double d; 86 | int16_t l; 87 | 88 | s = calloc(1, sizeof(av_test_t)); 89 | if(!s) 90 | { 91 | return(AV_OUT_OF_MEMORY); 92 | } 93 | 94 | /* Generate a basic test pattern */ 95 | s->width = av->width; 96 | s->height = av->height; 97 | s->video = malloc(av->width * av->height * sizeof(uint32_t)); 98 | if(!s->video) 99 | { 100 | free(s); 101 | return(AV_OUT_OF_MEMORY); 102 | } 103 | 104 | for(y = 0; y < s->height; y++) 105 | { 106 | for(x = 0; x < s->width; x++) 107 | { 108 | if(y < s->height - 140) 109 | { 110 | /* 75% colour bars */ 111 | c = 7 - x * 8 / s->width; 112 | c = bars[c]; 113 | } 114 | else if(y < s->height - 120) 115 | { 116 | /* 75% red */ 117 | c = 0xBF0000; 118 | } 119 | else if(y < s->height - 100) 120 | { 121 | /* Gradient black to white */ 122 | c = x * 0xFF / (s->width - 1); 123 | c = c << 16 | c << 8 | c; 124 | } 125 | else 126 | { 127 | /* 8 level grey bars */ 128 | c = x * 0xFF / (s->width - 1); 129 | c &= 0xE0; 130 | c = c | (c >> 3) | (c >> 6); 131 | c = c << 16 | c << 8 | c; 132 | } 133 | 134 | s->video[y * s->width + x] = c; 135 | } 136 | } 137 | 138 | /* Overlay the logo */ 139 | if(s->width >= LOGO_WIDTH * LOGO_SCALE && 140 | s->height >= LOGO_HEIGHT * LOGO_SCALE) 141 | { 142 | x = s->width / 2; 143 | y = s->height / 10; 144 | 145 | for(x = 0; x < LOGO_WIDTH * LOGO_SCALE; x++) 146 | { 147 | for(y = 0; y < LOGO_HEIGHT * LOGO_SCALE; y++) 148 | { 149 | c = _logo[y / LOGO_SCALE * LOGO_WIDTH + x / LOGO_SCALE] == ' ' ? 0x000000 : 0xFFFFFF; 150 | 151 | s->video[(s->height / 10 + y) * s->width + ((s->width - LOGO_WIDTH * LOGO_SCALE) / 2) + x] = c; 152 | } 153 | } 154 | } 155 | 156 | /* Generate the 1khz test tones (BBC 1 style) */ 157 | d = 1000.0 * 2 * M_PI * av->sample_rate.den / av->sample_rate.num; 158 | y = av->sample_rate.num / av->sample_rate.den * 64 / 100; /* 640ms */ 159 | s->audio_samples = y * 10; /* 6.4 seconds */ 160 | s->audio = malloc(s->audio_samples * 2 * sizeof(int16_t)); 161 | if(!s->audio) 162 | { 163 | free(s->video); 164 | free(s); 165 | return(AV_OUT_OF_MEMORY); 166 | } 167 | 168 | for(x = 0; x < s->audio_samples; x++) 169 | { 170 | l = sin(x * d) * INT16_MAX * 0.1; 171 | 172 | if(x < y) 173 | { 174 | /* 0 - 640ms, interrupt left channel */ 175 | s->audio[x * 2 + 0] = 0; 176 | s->audio[x * 2 + 1] = l; 177 | } 178 | else if(x >= y * 2 && x < y * 3) 179 | { 180 | /* 1280ms - 1920ms, interrupt right channel */ 181 | s->audio[x * 2 + 0] = l; 182 | s->audio[x * 2 + 1] = 0; 183 | } 184 | else if(x >= y * 4 && x < y * 5) 185 | { 186 | /* 2560ms - 3200ms, interrupt right channel again */ 187 | s->audio[x * 2 + 0] = l; 188 | s->audio[x * 2 + 1] = 0; 189 | } 190 | else 191 | { 192 | /* Use both channels for all other times */ 193 | s->audio[x * 2 + 0] = l; /* Left */ 194 | s->audio[x * 2 + 1] = l; /* Right */ 195 | } 196 | } 197 | 198 | /* Register the callback functions */ 199 | av->av_source_ctx = s; 200 | av->read_video = _test_read_video; 201 | av->read_audio = _test_read_audio; 202 | av->close = _test_close; 203 | 204 | return(AV_OK); 205 | } 206 | 207 | -------------------------------------------------------------------------------- /src/av_test.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _TEST_H 19 | #define _TEST_H 20 | 21 | extern int av_test_open(av_t *av); 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /src/build_win64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | HOST=x86_64-w64-mingw32 7 | PREFIX=$(pwd)/build_win64/install_root 8 | export PKG_CONFIG_LIBDIR=$PREFIX/lib/pkgconfig 9 | 10 | mkdir -p $PREFIX 11 | cd build_win64 12 | 13 | if [[ ! -f $PREFIX/lib/libusb-1.0.a ]]; then 14 | 15 | if [[ ! -f libusb-1.0.26.tar.bz2 ]]; then 16 | wget https://github.com/libusb/libusb/releases/download/v1.0.26/libusb-1.0.26.tar.bz2 17 | tar -xvjf libusb-1.0.26.tar.bz2 18 | fi 19 | 20 | cd libusb-1.0.26 21 | ./configure --host=$HOST --prefix=$PREFIX --enable-static --disable-shared 22 | make -j4 install 23 | cd .. 24 | fi 25 | 26 | if [[ ! -f $PREFIX/lib/libhackrf.a ]]; then 27 | 28 | if [[ ! -f hackrf-2023.01.1.tar.xz ]]; then 29 | wget https://github.com/greatscottgadgets/hackrf/releases/download/v2023.01.1/hackrf-2023.01.1.tar.xz 30 | tar -xvJf hackrf-2023.01.1.tar.xz 31 | fi 32 | 33 | rm -rf hackrf-2023.01.1/host/libhackrf/build 34 | mkdir -p hackrf-2023.01.1/host/libhackrf/build 35 | cd hackrf-2023.01.1/host/libhackrf/build 36 | cmake .. \ 37 | -DCMAKE_SYSTEM_NAME=Windows \ 38 | -DCMAKE_C_COMPILER=$HOST-gcc \ 39 | -DCMAKE_INSTALL_PREFIX=$PREFIX \ 40 | -DCMAKE_INSTALL_LIBPREFIX=$PREFIX/lib \ 41 | -DLIBUSB_INCLUDE_DIR=$PREFIX/include/libusb-1.0 \ 42 | -DLIBUSB_LIBRARIES=$PREFIX/lib/libusb-1.0.a 43 | make -j4 install 44 | cd ../../../.. 45 | mv $PREFIX/bin/*.a $PREFIX/lib/ 46 | find $PREFIX -name libhackrf\*.dll\* -delete 47 | fi 48 | 49 | if [[ ! -f $PREFIX/lib/libosmo-fl2k.a ]]; then 50 | 51 | if [[ ! -d osmo-fl2k ]]; then 52 | git clone --depth 1 https://gitea.osmocom.org/sdr/osmo-fl2k 53 | fi 54 | 55 | rm -rf osmo-fl2k/build 56 | mkdir -p osmo-fl2k/build 57 | cd osmo-fl2k/build 58 | cmake .. \ 59 | -DCMAKE_SYSTEM_NAME=Windows \ 60 | -DCMAKE_C_COMPILER=$HOST-gcc \ 61 | -DCMAKE_INSTALL_PREFIX=$PREFIX \ 62 | -DCMAKE_INSTALL_LIBPREFIX=$PREFIX \ 63 | -DCMAKE_INSTALL_LIBDIR=$PREFIX/lib \ 64 | -DLIBUSB_INCLUDE_DIR=$PREFIX/include/libusb-1.0 \ 65 | -DLIBUSB_LIBRARIES=$PREFIX/lib/libusb-1.0.a 66 | make -j4 install 67 | cd ../.. 68 | mv $PREFIX/lib/liblibosmo-fl2k_static.a $PREFIX/lib/libosmo-fl2k.a 69 | fi 70 | 71 | if [[ ! -f $PREFIX/lib/libfdk-aac.a ]]; then 72 | 73 | if [[ ! -d fdk-aac ]]; then 74 | git clone https://github.com/mstorsjo/fdk-aac.git 75 | fi 76 | 77 | cd fdk-aac 78 | ./autogen.sh 79 | ./configure --host=$HOST --prefix=$PREFIX --enable-static --disable-shared 80 | make -j4 install 81 | cd .. 82 | fi 83 | 84 | if [[ ! -f $PREFIX/lib/libopus.a ]]; then 85 | 86 | if [[ ! -f opus-1.4.tar.gz ]]; then 87 | wget https://downloads.xiph.org/releases/opus/opus-1.4.tar.gz 88 | tar -xvzf opus-1.4.tar.gz 89 | fi 90 | 91 | cd opus-1.4 92 | ./configure --host=$HOST --prefix=$PREFIX --enable-static --disable-shared --disable-doc --disable-extra-programs 93 | make -j4 install 94 | cd .. 95 | fi 96 | 97 | if [[ ! -f $PREFIX/lib/libavformat.a ]]; then 98 | 99 | if [[ ! -d ffmpeg ]]; then 100 | git clone --depth 1 --branch n6.1.1 https://github.com/FFmpeg/FFmpeg.git ffmpeg 101 | fi 102 | 103 | cd ffmpeg 104 | ./configure \ 105 | --enable-gpl --enable-nonfree --enable-libfdk-aac --enable-libopus \ 106 | --enable-static --disable-shared --disable-programs \ 107 | --disable-outdevs --disable-encoders \ 108 | --arch=x86_64 --target-os=mingw64 --cross-prefix=$HOST- \ 109 | --pkg-config=pkg-config --prefix=$PREFIX 110 | make -j4 install 111 | cd .. 112 | fi 113 | 114 | cd .. 115 | CROSS_HOST=$HOST- make -j4 EXTRA_LDFLAGS="-static" EXTRA_PKGS="libusb-1.0" 116 | $HOST-strip hacktv.exe 117 | 118 | echo "Done" 119 | 120 | -------------------------------------------------------------------------------- /src/cc608.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2025 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "video.h" 24 | #include "vbidata.h" 25 | 26 | int cc608_fifo_init(cc608_fifo_t *fifo) 27 | { 28 | fifo->ptr_out = fifo->ptr_in = fifo->len = 0; 29 | fifo->size = 128 * 2; 30 | fifo->fifo = malloc(fifo->size); 31 | if(fifo->fifo == NULL) 32 | { 33 | return(-1); 34 | } 35 | 36 | pthread_mutex_init(&fifo->mutex, NULL); 37 | 38 | return(0); 39 | } 40 | 41 | void cc608_fifo_free(cc608_fifo_t *fifo) 42 | { 43 | pthread_mutex_destroy(&fifo->mutex); 44 | free(fifo->fifo); 45 | } 46 | 47 | int cc608_fifo_write(cc608_fifo_t *fifo, uint8_t *data, int len) 48 | { 49 | int i; 50 | 51 | /* Always copy codes in pairs */ 52 | len -= len & 1; 53 | 54 | pthread_mutex_lock(&fifo->mutex); 55 | 56 | for(i = 0; i < len && fifo->len < fifo->size; i += 2, data += 2) 57 | { 58 | if(((data[0] | data[1]) & 0x7F) == 0x00) 59 | { 60 | /* Don't write empty pairs into the FIFO */ 61 | continue; 62 | } 63 | 64 | fifo->fifo[fifo->ptr_in++] = data[0]; 65 | fifo->fifo[fifo->ptr_in++] = data[1]; 66 | 67 | if(fifo->ptr_in >= fifo->size) fifo->ptr_in = 0; 68 | 69 | fifo->len += 2; 70 | } 71 | 72 | pthread_mutex_unlock(&fifo->mutex); 73 | 74 | return(i); 75 | } 76 | 77 | int cc608_fifo_read(cc608_fifo_t *fifo, uint8_t *data, int len) 78 | { 79 | int i; 80 | 81 | /* Always copy codes in pairs */ 82 | len -= len & 1; 83 | 84 | pthread_mutex_lock(&fifo->mutex); 85 | 86 | for(i = 0; i < len && fifo->len > 0; i++, fifo->len--) 87 | { 88 | *(data++) = fifo->fifo[fifo->ptr_out++]; 89 | if(fifo->ptr_out >= fifo->size) fifo->ptr_out = 0; 90 | } 91 | 92 | pthread_mutex_unlock(&fifo->mutex); 93 | 94 | return(i); 95 | } 96 | 97 | int cc608_init(cc608_t *s, vid_t *vid) 98 | { 99 | double offset; 100 | double x, w; 101 | double level; 102 | int i; 103 | 104 | memset(s, 0, sizeof(cc608_t)); 105 | 106 | if(vid->conf.type == VID_RASTER_525) 107 | { 108 | s->lines[0] = 21; 109 | s->lines[1] = -1; // 284; 110 | offset = 27.382e-6; 111 | } 112 | else if(vid->conf.type == VID_RASTER_625) 113 | { 114 | s->lines[0] = 22; 115 | s->lines[1] = -1; //335; 116 | offset = 27.5e-6; 117 | } 118 | else 119 | { 120 | fprintf(stderr, "cc608: CEA/EIA-608 is not supported in this TV mode.\n"); 121 | return(VID_ERROR); 122 | } 123 | 124 | /* Calculate the high level for the VBI data, 50% of the white level */ 125 | s->lut = vbidata_init_step( 126 | 32, 127 | vid->width, 128 | level = round((vid->white_level - vid->black_level) * 0.5), 129 | (double) vid->width / 32, 130 | vid->pixel_rate * 240e-9 * IRT1090, 131 | vid->pixel_rate * offset 132 | ); 133 | if(!s->lut) 134 | { 135 | return(VID_OUT_OF_MEMORY); 136 | } 137 | 138 | /* Render the clock run-in */ 139 | w = (double) vid->width * 7 / 32; 140 | x = (double) vid->pixel_rate * offset - (vid->width * 8.75 / 32); 141 | 142 | s->cri_x = x; 143 | s->cri_len = ceil(w); 144 | s->cri = malloc(sizeof(int16_t) * s->cri_len); 145 | if(!s->cri) 146 | { 147 | free(s->lut); 148 | return(VID_OUT_OF_MEMORY); 149 | } 150 | 151 | for(i = 0; i < s->cri_len; i++) 152 | { 153 | s->cri[i] = (0.5 - cos(((double) i - (x - s->cri_x)) * (2 * M_PI / w * 7)) * 0.5) * level; 154 | } 155 | 156 | if(cc608_fifo_init(&s->ccfifo) != 0) 157 | { 158 | free(s->lut); 159 | return(VID_OUT_OF_MEMORY); 160 | } 161 | 162 | return(VID_OK); 163 | } 164 | 165 | void cc608_free(cc608_t *s) 166 | { 167 | cc608_fifo_free(&s->ccfifo); 168 | free(s->lut); 169 | memset(s, 0, sizeof(cc608_t)); 170 | } 171 | 172 | void _encode_chars(uint8_t *data, uint8_t c1, uint8_t c2) 173 | { 174 | int i; 175 | 176 | c1 = (c1 & 0x7F) | 0x80; 177 | c2 = (c2 & 0x7F) | 0x80; 178 | 179 | for(i = 1; i < 8; i++) 180 | { 181 | c1 ^= (c1 << i) & 0x80; 182 | c2 ^= (c2 << i) & 0x80; 183 | } 184 | 185 | data[0] = (c1 << 1) | 0x01; 186 | data[1] = (c2 << 1) | (c1 >> 7); 187 | data[2] = (c2 >> 7); 188 | } 189 | 190 | int cc608_render(vid_t *s, void *arg, int nlines, vid_line_t **lines) 191 | { 192 | cc608_t *v = arg; 193 | vid_line_t *l = lines[0]; 194 | uint8_t data[3]; 195 | int i; 196 | 197 | if(l->line != v->lines[0] && 198 | l->line != v->lines[1]) 199 | { 200 | return(1); 201 | } 202 | 203 | if(cc608_fifo_read(&v->ccfifo, data, 2) != 2) 204 | { 205 | /* Transmit zeros if no pending codes */ 206 | data[0] = data[1] = 0; 207 | } 208 | 209 | _encode_chars(data, data[0], data[1]); 210 | 211 | /* Render the line */ 212 | for(i = 0; i < v->cri_len; i++) 213 | { 214 | l->output[(v->cri_x + i) * 2] += v->cri[i]; 215 | } 216 | 217 | vbidata_render(v->lut, data, 0, 17, VBIDATA_LSB_FIRST, l); 218 | l->vbialloc = 1; 219 | 220 | return(1); 221 | } 222 | 223 | -------------------------------------------------------------------------------- /src/cc608.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2025 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _CC608_H 19 | #define _CC608_H 20 | 21 | #include 22 | #include "video.h" 23 | 24 | 25 | 26 | /* CC608 FIFO functions */ 27 | 28 | typedef struct { 29 | 30 | uint8_t *fifo; 31 | int size; 32 | int len; 33 | int ptr_in; 34 | int ptr_out; 35 | 36 | pthread_mutex_t mutex; 37 | 38 | } cc608_fifo_t; 39 | 40 | /* cc608_fifo_init(): Returns 0 if successful, -1 if out of memory */ 41 | 42 | extern int cc608_fifo_init(cc608_fifo_t *fifo); 43 | 44 | /* cc608_fifo_free(): Frees FIFO memory */ 45 | 46 | extern void cc608_fifo_free(cc608_fifo_t *fifo); 47 | 48 | /* cc608_fifo_write(): Write up to "len" bytes from "data" into the FIFO. 49 | * Returns the number of bytes written, always an even 50 | * number */ 51 | 52 | extern int cc608_fifo_write(cc608_fifo_t *fifo, uint8_t *data, int len); 53 | 54 | /* cc608_fifo_read(): Read up to "len" bytes from the FIFO into "data". 55 | * Returns the number of bytes read, always an even number */ 56 | 57 | extern int cc608_fifo_read(cc608_fifo_t *fifo, uint8_t *data, int len); 58 | 59 | 60 | 61 | /* CC608 render functions */ 62 | 63 | typedef struct { 64 | 65 | /* Config */ 66 | int lines[2]; 67 | 68 | /* Clock run-in signal */ 69 | int cri_x; 70 | int cri_len; 71 | int16_t *cri; 72 | 73 | /* VBI renderer lookup */ 74 | vbidata_lut_t *lut; 75 | 76 | /* FIFO */ 77 | cc608_fifo_t ccfifo; 78 | 79 | } cc608_t; 80 | 81 | extern int cc608_init(cc608_t *s, vid_t *vid); 82 | extern void cc608_free(cc608_t *s); 83 | 84 | extern int cc608_render(vid_t *s, void *arg, int nlines, vid_line_t **lines); 85 | 86 | 87 | 88 | #endif 89 | 90 | -------------------------------------------------------------------------------- /src/common.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "common.h" 23 | 24 | int64_t gcd(int64_t a, int64_t b) 25 | { 26 | int64_t c; 27 | 28 | while((c = a % b)) 29 | { 30 | a = b; 31 | b = c; 32 | } 33 | 34 | return(b); 35 | } 36 | 37 | static r64_t _normalise(int64_t *num, int64_t *den) 38 | { 39 | int64_t e; 40 | 41 | if(*den == 0) 42 | { 43 | *num = 0; 44 | return((r64_t) { }); 45 | } 46 | 47 | if(*den < 0) 48 | { 49 | *num = -*num; 50 | *den = -*den; 51 | } 52 | 53 | e = gcd(*num, *den); 54 | 55 | return((r64_t) { *num /= e, *den /= e }); 56 | } 57 | 58 | r64_t r64_mul(r64_t a, r64_t b) 59 | { 60 | int64_t c, d; 61 | c = (int64_t) a.num * b.num; 62 | d = (int64_t) a.den * b.den; 63 | return(_normalise(&c, &d)); 64 | } 65 | 66 | r64_t r64_div(r64_t a, r64_t b) 67 | { 68 | int64_t c, d; 69 | c = (int64_t) a.num * b.den; 70 | d = (int64_t) a.den * b.num; 71 | return(_normalise(&c, &d)); 72 | } 73 | 74 | int r64_cmp(r64_t a, r64_t b) 75 | { 76 | int64_t c = (int64_t) a.num * b.den - (int64_t) a.den * b.num; 77 | return(c < 0 ? -1 : (c > 0 ? 1 : 0)); 78 | } 79 | 80 | r64_t r64_nearest(r64_t ref, r64_t a, r64_t b) 81 | { 82 | /* Return "a" or "b" depending on which is nearest "ref", or "a" if equal */ 83 | r64_t h = { a.num * b.den + a.den * b.num, a.den * b.den * 2 }; 84 | return(r64_cmp(ref, h) <= 0 ? a : b); 85 | } 86 | 87 | r64_t r64_parse_decimal(const char *str, const char **endptr) 88 | { 89 | /* Parse decimal number with exponent */ 90 | const char *s = str; 91 | int64_t e, num, den = 1; 92 | 93 | if(endptr != NULL) *endptr = str; 94 | 95 | /* Skip any leading spaces */ 96 | while(isspace(*s)) s++; 97 | 98 | /* Test for the sign */ 99 | if(*s == '+' || *s == '-') 100 | { 101 | if(*s == '-') den = -1; 102 | s++; 103 | } 104 | 105 | /* Test for no number */ 106 | if((s[0] != '.' && !isdigit(s[0])) || 107 | (s[0] == '.' && !isdigit(s[1]))) 108 | { 109 | return((r64_t) { }); 110 | } 111 | 112 | /* Read first number/integer part */ 113 | for(num = 0; isdigit(*s); s++) 114 | { 115 | num = num * 10 + *s - '0'; 116 | } 117 | 118 | /* Read the fractional part */ 119 | if(*s == '.') 120 | { 121 | for(s++; isdigit(*s); s++) 122 | { 123 | num = num * 10 + *s - '0'; 124 | den *= 10; 125 | } 126 | 127 | (void) _normalise(&num, &den); 128 | } 129 | 130 | /* Read the exponent part */ 131 | if(*s == 'e' || *s == 'E') 132 | { 133 | int neg = 0; 134 | 135 | s++; 136 | 137 | /* Test for the sign */ 138 | if(*s == '+' || *s == '-') 139 | { 140 | if(*s == '-') neg = 1; 141 | s++; 142 | } 143 | 144 | if(!isdigit(*s)) return((r64_t) { }); 145 | 146 | for(e = 0; isdigit(*s); s++) 147 | { 148 | e = e * 10 + *s - '0'; 149 | } 150 | 151 | if(neg) { for(; e > 0; e--) den *= 10; } 152 | else { for(; e > 0; e--) num *= 10; } 153 | } 154 | 155 | /* Test for empty string or invalid trailing characters */ 156 | if(*s == '.' || *s == '+' || *s == '-' || 157 | *s == 'e' || *s == 'E' || s == str) 158 | { 159 | return((r64_t) { }); 160 | } 161 | 162 | /* Looks good, return the result */ 163 | if(endptr != NULL) *endptr = s; 164 | return(_normalise(&num, &den)); 165 | } 166 | 167 | r64_t r64_parse(const char *str, const char **endptr) 168 | { 169 | /* Parse decimal number with exponent, 170 | * individually or as a ratio pair x:y or x/y */ 171 | const char *s; 172 | r64_t a, b; 173 | 174 | if(endptr != NULL) *endptr = str; 175 | 176 | /* Parse the first part */ 177 | a = r64_parse_decimal(str, &s); 178 | if(a.den == 0) return(a); 179 | 180 | if(*s == ':' || *s == '/') 181 | { 182 | /* Don't allow spaces after the divider */ 183 | s++; 184 | if(*s == ' ') return((r64_t) { }); 185 | 186 | /* Parse the second part */ 187 | b = r64_parse_decimal(s, &str); 188 | if(b.num == 0 || b.den == 0) 189 | { 190 | return((r64_t) { }); 191 | } 192 | 193 | /* Test for too many dividers */ 194 | s = str; 195 | if(*s == ':' || *s == '/') 196 | { 197 | return((r64_t) { }); 198 | } 199 | 200 | /* Apply divider */ 201 | a = r64_div(a, b); 202 | } 203 | 204 | /* Looks good, return the result */ 205 | if(endptr != NULL) *endptr = s; 206 | return(a); 207 | } 208 | 209 | cint16_t *sin_cint16(unsigned int length, unsigned int cycles, double level) 210 | { 211 | cint16_t *lut; 212 | unsigned int i; 213 | double d; 214 | 215 | lut = malloc(length * sizeof(cint16_t)); 216 | if(!lut) 217 | { 218 | return(NULL); 219 | } 220 | 221 | d = 2.0 * M_PI / length * cycles; 222 | for(i = 0; i < length; i++) 223 | { 224 | lut[i].i = round(cos(d * i) * level * INT16_MAX); 225 | lut[i].q = round(sin(d * i) * level * INT16_MAX); 226 | } 227 | 228 | return(lut); 229 | } 230 | 231 | double rc_window(double t, double left, double width, double rise) 232 | { 233 | double r; 234 | 235 | t -= left + width / 2; 236 | t = fabs(t) - (width - rise) / 2; 237 | 238 | if(t <= 0) 239 | { 240 | r = 1.0; 241 | } 242 | else if(t < rise) 243 | { 244 | /* Raised cosine edge */ 245 | //r = 0.5 + cos(t / rise * M_PI) / 2; 246 | 247 | /* Integrated raised cosine edge */ 248 | t = 1.0 - t / rise * 2; 249 | r = 0.5 * (1.0 + t + sin(M_PI * t) / M_PI); 250 | } 251 | else 252 | { 253 | r = 0.0; 254 | } 255 | 256 | return(r); 257 | } 258 | 259 | double rrc(double x, double b, double t) 260 | { 261 | double r; 262 | 263 | /* Based on the Wikipedia page, https://en.wikipedia.org/w/index.php?title=Root-raised-cosine_filter&oldid=787851747 */ 264 | 265 | if(x == 0) 266 | { 267 | r = (1.0 / t) * (1.0 + b * (4.0 / M_PI - 1)); 268 | } 269 | else if(fabs(x) == t / (4.0 * b)) 270 | { 271 | r = b / (t * sqrt(2.0)) * ((1.0 + 2.0 / M_PI) * sin(M_PI / (4.0 * b)) + (1.0 - 2.0 / M_PI) * cos(M_PI / (4.0 * b))); 272 | } 273 | else 274 | { 275 | double t1 = (4.0 * b * (x / t)); 276 | double t2 = (sin(M_PI * (x / t) * (1.0 - b)) + 4.0 * b * (x / t) * cos(M_PI * (x / t) * (1.0 + b))); 277 | double t3 = (M_PI * (x / t) * (1.0 - t1 * t1)); 278 | 279 | r = (1.0 / t) * (t2 / t3); 280 | } 281 | 282 | return(r); 283 | } 284 | 285 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _COMMON_H 19 | #define _COMMON_H 20 | 21 | #include 22 | 23 | /* These factors where calculated with: f = M_PI / 2.0 / asin(0.9 - 0.1); */ 24 | #define RT1090 1.6939549523182869 /* Factor to convert 10-90% rise time to 0-100% */ 25 | #define RT2080 2.4410157268268087 /* Factor to convert 20-80% rise time to 0-100% */ 26 | 27 | /* As above but for integrated raised cosine edges */ 28 | #define IRT1090 2.0738786 /* 10-90% to 0-100% */ 29 | #define IRT2080 3.0546756 /* 10-90% to 0-100% */ 30 | 31 | typedef struct { 32 | int64_t num; 33 | int64_t den; 34 | } r64_t; 35 | 36 | typedef struct { 37 | int16_t i; 38 | int16_t q; 39 | } cint16_t; 40 | 41 | typedef struct { 42 | int32_t i; 43 | int32_t q; 44 | } cint32_t; 45 | 46 | extern int64_t gcd(int64_t a, int64_t b); 47 | extern r64_t r64_mul(r64_t a, r64_t b); 48 | extern r64_t r64_div(r64_t a, r64_t b); 49 | extern int r64_cmp(r64_t a, r64_t b); 50 | extern r64_t r64_nearest(r64_t ref, r64_t a, r64_t b); 51 | extern r64_t r64_parse_decimal(const char *str, const char **endptr); 52 | extern r64_t r64_parse(const char *str, const char **endptr); 53 | extern cint16_t *sin_cint16(unsigned int length, unsigned int cycles, double level); 54 | extern double rc_window(double t, double left, double width, double rise); 55 | extern double rrc(double x, double b, double t); 56 | 57 | static inline void cint16_mul(cint16_t *r, const cint16_t *a, const cint16_t *b) 58 | { 59 | int32_t i, q; 60 | 61 | i = (int32_t) a->i * (int32_t) b->i - (int32_t) a->q * (int32_t) b->q; 62 | q = (int32_t) a->i * (int32_t) b->q + (int32_t) a->q * (int32_t) b->i; 63 | 64 | r->i = i >> 15; 65 | r->q = q >> 15; 66 | } 67 | 68 | static inline void cint16_mula(cint16_t *r, const cint16_t *a, const cint16_t *b) 69 | { 70 | int32_t i, q; 71 | 72 | i = (int32_t) a->i * (int32_t) b->i - (int32_t) a->q * (int32_t) b->q; 73 | q = (int32_t) a->i * (int32_t) b->q + (int32_t) a->q * (int32_t) b->i; 74 | 75 | r->i += i >> 15; 76 | r->q += q >> 15; 77 | } 78 | 79 | static inline void cint32_mul(cint32_t *r, const cint32_t *a, const cint32_t *b) 80 | { 81 | int64_t i, q; 82 | 83 | i = (int64_t) a->i * (int64_t) b->i - (int64_t) a->q * (int64_t) b->q; 84 | q = (int64_t) a->i * (int64_t) b->q + (int64_t) a->q * (int64_t) b->i; 85 | 86 | r->i = i >> 31; 87 | r->q = q >> 31; 88 | } 89 | 90 | static inline void cint32_mula(cint32_t *r, const cint32_t *a, const cint32_t *b) 91 | { 92 | int64_t i, q; 93 | 94 | i = (int64_t) a->i * (int64_t) b->i - (int64_t) a->q * (int64_t) b->q; 95 | q = (int64_t) a->i * (int64_t) b->q + (int64_t) a->q * (int64_t) b->i; 96 | 97 | r->i += i >> 31; 98 | r->q += q >> 31; 99 | } 100 | 101 | #endif 102 | 103 | -------------------------------------------------------------------------------- /src/dance.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2020 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _DANCE_H 19 | #define _DANCE_H 20 | 21 | #include 22 | #include "common.h" 23 | 24 | /* DANCE bit and symbol rates */ 25 | #define DANCE_BIT_RATE 2048000 26 | #define DANCE_SYMBOL_RATE (DANCE_BIT_RATE / 2) 27 | 28 | /* Modes of operation */ 29 | #define DANCE_MODE_A 0x00 /* 4x 32kHz 14/10-bit companded channels */ 30 | #define DANCE_MODE_B 0x01 /* 2x 48kHz 16-bit linear channel */ 31 | 32 | /* Channel modes */ 33 | #define DANCE_MODE_STEREO 0x00 34 | #define DANCE_MODE_2_MONO 0x01 35 | #define DANCE_MODE_1_MONO 0x02 36 | #define DANCE_MODE_NONE 0x03 37 | 38 | /* Audio sample rates for DANCE */ 39 | #define DANCE_A_AUDIO_RATE 32000 40 | #define DANCE_B_AUDIO_RATE 48000 41 | 42 | /* Length of a DANCE frame in bits, bytes and symbols */ 43 | #define DANCE_FRAME_BITS 2048 44 | #define DANCE_FRAME_BYTES (DANCE_FRAME_BITS / 8) 45 | #define DANCE_FRAME_SYMS (DANCE_FRAME_BITS / 2) 46 | 47 | /* Length of a DANCE frame in audio samples */ 48 | #define DANCE_A_AUDIO_LEN (DANCE_A_AUDIO_RATE / 1000) 49 | #define DANCE_B_AUDIO_LEN (DANCE_B_AUDIO_RATE / 1000) 50 | #define DANCE_AUDIO_LEN DANCE_B_AUDIO_LEN 51 | 52 | #define DANCE_A_50_10_US_NTAPS 77 53 | #define DANCE_B_50_10_US_NTAPS 59 54 | #define DANCE_50_10_US_NTAPS DANCE_A_50_10_US_NTAPS 55 | 56 | typedef struct { 57 | int p; 58 | int16_t buf[DANCE_50_10_US_NTAPS]; 59 | const int16_t *taps; 60 | int ntaps; 61 | } _dance_fir_t; 62 | 63 | typedef struct { 64 | 65 | uint8_t mode_12; 66 | uint8_t mode_34; 67 | unsigned int frame; 68 | uint8_t prn[DANCE_FRAME_BYTES]; 69 | uint8_t frames[2][DANCE_FRAME_BYTES]; 70 | 71 | /* FIR filters */ 72 | const int16_t *fir_taps; 73 | int fir_ntaps; 74 | _dance_fir_t fir[4]; 75 | 76 | } dance_enc_t; 77 | 78 | typedef struct { 79 | 80 | dance_enc_t enc; 81 | 82 | int16_t audio[DANCE_AUDIO_LEN * 2]; 83 | 84 | int ntaps; 85 | int16_t *taps; 86 | int16_t *hist; 87 | 88 | int dsym; /* Differential symbol */ 89 | 90 | cint16_t *bb; 91 | cint16_t *bb_start; 92 | cint16_t *bb_end; 93 | int bb_len; 94 | 95 | int sps; 96 | int ds; 97 | int dsl; 98 | int decimation; 99 | 100 | cint16_t *cc; 101 | cint16_t *cc_start; 102 | cint16_t *cc_end; 103 | 104 | uint8_t frame[DANCE_FRAME_BYTES]; 105 | int frame_bit; 106 | 107 | } dance_mod_t; 108 | 109 | extern void dance_encode_init(dance_enc_t *s); 110 | extern void dance_encode_frame_a( 111 | dance_enc_t *s, uint8_t *frame, 112 | const int16_t *a1, int a1step, 113 | const int16_t *a2, int a2step, 114 | const int16_t *a3, int a3step, 115 | const int16_t *a4, int a4step 116 | ); 117 | extern void dance_encode_frame_b( 118 | dance_enc_t *s, uint8_t *frame, 119 | const int16_t *a1, int a1step, 120 | const int16_t *a2, int a2step 121 | ); 122 | 123 | extern int dance_mod_init(dance_mod_t *s, uint8_t mode, unsigned int sample_rate, unsigned int frequency, double beta, double level); 124 | extern void dance_mod_input(dance_mod_t *s, const int16_t *audio); 125 | extern int dance_mod_output(dance_mod_t *s, int16_t *iq, size_t samples); 126 | extern int dance_mod_free(dance_mod_t *s); 127 | 128 | #endif 129 | 130 | -------------------------------------------------------------------------------- /src/demo.tti: -------------------------------------------------------------------------------- 1 | DE,edit-tf 2 | PS,8000 3 | PN,1007F 4 | SC,3F7F 5 | RE,0 6 | OL,1,D ]G \ 7 | OL,2,T oooooooooooooooooo 8 | OL,3,TZ9)9)9)9)9)9)9)9)9)9)9)9)9)9)9)9)9)9) 9 | OL,4,TZ ! "$ " ! $ ! $ 10 | OL,5,WZx t`~}0xt`~5`~=`~}0x t 11 | OL,6,WZ j7#k5#+/j}' "##!  12 | OL,7,W ||j5 j5 ju    13 | OL,8,W //j5 j7o}0    14 | OL,9,W  j7#k5px|j5 k  +tx' 15 | OL,10,W + '"o5 j?!+'"o5 j? + "o?! 16 | OL,11,TZ $ 0 ( 00 0 ` 0$ 2( 0! 17 | OL,12,TZfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfdfd 18 | OL,13,T }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} 19 | OL,14,D ]G \ 20 | OL,15,D ]G Hello, World! \ 21 | OL,16,D ]G \ 22 | OL,17,D ]G \ 23 | OL,18,D ]G \ 24 | OL,19,D ]G \ 25 | OL,20,D ]G \ 26 | OL,21,D ]G \ 27 | OL,22,D ]G https://sanslogic.co.uk/hacktv \ 28 | OL,23,D ]G \ 29 | -------------------------------------------------------------------------------- /src/eurocrypt.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2020 Alex L. James */ 4 | /* Copyright 2020 Philip Heron */ 5 | /* */ 6 | /* This program is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* This program is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see . */ 18 | 19 | #ifndef _EUROCRYPT_H 20 | #define _EUROCRYPT_H 21 | 22 | #define ECM_PAYLOAD_BYTES 45 23 | #define EMMU 0x00 24 | #define EMMS 0xF8 25 | #define EMMC 0xC7 26 | #define EMMG 0x3F 27 | 28 | typedef struct { 29 | const char *id; /* Mode id */ 30 | int des_algo; /* Eurocrypt M or S2 algo */ 31 | int packet_type; /* Eurocrypt M or S2 packet */ 32 | uint8_t key[16]; /* Decryption keys */ 33 | uint8_t ppid[3]; /* Programme provider identifier */ 34 | char date[10]; /* Broadcast date */ 35 | uint8_t theme[2]; /* Theme */ 36 | char channame[32];/* Channel name to display */ 37 | } ec_mode_t; 38 | 39 | typedef struct { 40 | const char *id; /* Mode id */ 41 | int des_algo; /* Eurocrypt M or S2 algo */ 42 | int packet_type; /* Eurocrypt M or S2 packet */ 43 | uint8_t key[16]; /* Decryption key */ 44 | uint8_t ppid[3]; /* Programme provider identifier */ 45 | uint8_t sa[3]; /* Shared Address */ 46 | uint8_t ua[5]; /* Unique Address */ 47 | int emmtype; 48 | } em_mode_t; 49 | 50 | typedef struct { 51 | 52 | const ec_mode_t *mode; 53 | const em_mode_t *emmode; 54 | 55 | /* Encrypted even and odd control words */ 56 | uint8_t ecw[2][8]; 57 | 58 | /* Decrypted even and odd control words */ 59 | uint8_t cw[2][8]; 60 | 61 | /* Hash */ 62 | uint8_t ecm_hash[8]; 63 | uint8_t emm_hash[8]; 64 | 65 | /* ECM packet */ 66 | int ecm_addr; 67 | uint8_t ecm_pkt[MAC_PAYLOAD_BYTES * 2]; 68 | 69 | /* Packet continuities */ 70 | int ecm_cont; 71 | int emm_cont; 72 | 73 | /* EMM packet */ 74 | int emm_addr; 75 | uint8_t emms_pkt[MAC_PAYLOAD_BYTES]; 76 | uint8_t emmu_pkt[MAC_PAYLOAD_BYTES * 2]; 77 | uint8_t emmg_pkt[MAC_PAYLOAD_BYTES * 2]; 78 | uint8_t enc_data[8]; 79 | 80 | } eurocrypt_t; 81 | 82 | extern int eurocrypt_init(vid_t *s, const char *mode); 83 | extern void eurocrypt_next_frame(vid_t *s, int frame); 84 | 85 | #endif 86 | 87 | -------------------------------------------------------------------------------- /src/fifo.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2024 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "fifo.h" 23 | 24 | int fifo_init(fifo_t *fifo, size_t count, size_t length) 25 | { 26 | int i; 27 | 28 | /* There must be at least 3 blocks of 1 byte */ 29 | if(count < 3) return(-1); 30 | if(length < 1) return(-1); 31 | 32 | fifo->count = count; 33 | fifo->blocks = calloc(sizeof(fifo_block_t), count); 34 | if(!fifo->blocks) 35 | { 36 | return(-1); 37 | } 38 | 39 | fifo->blocks->data = calloc(length, count); 40 | if(!fifo->blocks->data) 41 | { 42 | free(fifo->blocks); 43 | return(-1); 44 | } 45 | 46 | for(i = 0; i < count; i++) 47 | { 48 | pthread_mutex_init(&fifo->blocks[i].mutex, NULL); 49 | pthread_cond_init(&fifo->blocks[i].cond, NULL); 50 | fifo->blocks[i].readers = 0; 51 | fifo->blocks[i].writing = 1; 52 | fifo->blocks[i].data = (uint8_t *) fifo->blocks->data + (length * i); 53 | fifo->blocks[i].length = length; 54 | fifo->blocks[i].prev = &fifo->blocks[(i + count - 1) % count]; 55 | fifo->blocks[i].next = &fifo->blocks[(i + 1) % count]; 56 | } 57 | 58 | /* The writer starts on the first block */ 59 | fifo->block = fifo->blocks; 60 | fifo->block->writing = 1; 61 | fifo->offset = 0; 62 | 63 | return(0); 64 | } 65 | 66 | void fifo_reader_init(fifo_reader_t *reader, fifo_t *fifo, int prefill) 67 | { 68 | /* Readers start on the last (empty) block, waiting for the writer */ 69 | reader->block = fifo->block->prev; 70 | reader->block->readers++; 71 | reader->offset = reader->block->length; 72 | reader->eof = 0; 73 | reader->prefill = NULL; 74 | 75 | if(prefill != 0) 76 | { 77 | /* The prefill block cannot be either of the last two blocks */ 78 | if(prefill < 0 || prefill > fifo->count - 2) prefill = fifo->count - 2; 79 | 80 | reader->prefill = &fifo->blocks[prefill - 1]; 81 | } 82 | } 83 | 84 | void fifo_reader_close(fifo_reader_t *reader) 85 | { 86 | fifo_block_t *block = reader->block; 87 | 88 | if(reader->block != NULL && reader->eof == 0) 89 | { 90 | pthread_mutex_lock(&block->mutex); 91 | block->readers--; 92 | pthread_cond_signal(&block->cond); 93 | pthread_mutex_unlock(&block->mutex); 94 | 95 | reader->block = NULL; 96 | reader->eof = 1; 97 | } 98 | } 99 | 100 | void fifo_close(fifo_t *fifo) 101 | { 102 | fifo_block_t *block = fifo->block; 103 | 104 | if(block == NULL) return; 105 | 106 | block->length = fifo->offset; 107 | 108 | if(block->length > 0) 109 | { 110 | fifo_block_t *next = block->next; 111 | 112 | pthread_mutex_lock(&next->mutex); 113 | 114 | /* Wait for the next block to be read */ 115 | while(next->readers > 0) 116 | { 117 | pthread_cond_wait(&next->cond, &next->mutex); 118 | } 119 | 120 | next->writing = 0; 121 | next->length = 0; 122 | 123 | pthread_mutex_unlock(&next->mutex); 124 | } 125 | 126 | /* Mark current block as ready */ 127 | pthread_mutex_lock(&block->mutex); 128 | block->writing = 0; 129 | pthread_cond_signal(&block->cond); 130 | pthread_mutex_unlock(&block->mutex); 131 | 132 | fifo->block = (block->length == 0 ? block : block->next); 133 | fifo->offset = 0; 134 | } 135 | 136 | void fifo_free(fifo_t *fifo) 137 | { 138 | fifo_block_t *block; 139 | 140 | if(fifo->block == NULL) return; 141 | 142 | /* Send out EOF signal */ 143 | fifo_close(fifo); 144 | 145 | block = fifo->block->next; 146 | 147 | /* TODO: Wait for all readers to end */ 148 | while(block->length > 0) 149 | { 150 | pthread_mutex_lock(&block->mutex); 151 | 152 | while(block->readers > 0) 153 | { 154 | pthread_cond_wait(&block->cond, &block->mutex); 155 | } 156 | 157 | block->writing = 0; 158 | block->length = 0; 159 | 160 | pthread_mutex_unlock(&block->mutex); 161 | 162 | block = block->next; 163 | } 164 | 165 | /* Tear down the FIFO */ 166 | for(int i = 0; i < fifo->count; i++) 167 | { 168 | pthread_cond_destroy(&fifo->blocks[i].cond); 169 | pthread_mutex_destroy(&fifo->blocks[i].mutex); 170 | } 171 | 172 | free(fifo->blocks->data); 173 | free(fifo->blocks); 174 | 175 | fifo->block = NULL; 176 | } 177 | 178 | size_t fifo_read(fifo_reader_t *reader, void **ptr, size_t length, int wait) 179 | { 180 | fifo_block_t *block = reader->block; 181 | 182 | if(block == NULL || reader->eof) 183 | { 184 | /* End of line */ 185 | return(-1); 186 | } 187 | 188 | if(reader->prefill) 189 | { 190 | pthread_mutex_lock(&reader->prefill->mutex); 191 | 192 | if(wait) 193 | { 194 | /* Wait until the next block is written to */ 195 | while(reader->prefill->writing == 1 && reader->prefill->length != 0) 196 | { 197 | pthread_cond_wait(&reader->prefill->cond, &reader->prefill->mutex); 198 | } 199 | } 200 | else if(reader->prefill->writing == 1 && reader->prefill->length != 0) 201 | { 202 | /* Non-blocking */ 203 | pthread_mutex_unlock(&reader->prefill->mutex); 204 | return(0); 205 | } 206 | 207 | pthread_mutex_unlock(&reader->prefill->mutex); 208 | 209 | reader->prefill = NULL; 210 | } 211 | 212 | if(reader->offset == block->length) 213 | { 214 | fifo_block_t *next = block->next; 215 | 216 | pthread_mutex_lock(&next->mutex); 217 | 218 | if(wait) 219 | { 220 | /* Wait until the next block is written to */ 221 | while(next->writing == 1 && next->length != 0) 222 | { 223 | pthread_cond_wait(&next->cond, &next->mutex); 224 | } 225 | } 226 | else if(next->writing == 1 && next->length != 0) 227 | { 228 | /* Non-blocking */ 229 | pthread_mutex_unlock(&next->mutex); 230 | return(0); 231 | } 232 | 233 | if(next->length == 0) 234 | { 235 | /* End of stream */ 236 | reader->eof = 1; 237 | } 238 | else 239 | { 240 | next->readers++; 241 | } 242 | 243 | pthread_mutex_unlock(&next->mutex); 244 | 245 | pthread_mutex_lock(&block->mutex); 246 | block->readers--; 247 | pthread_cond_signal(&block->cond); 248 | pthread_mutex_unlock(&block->mutex); 249 | 250 | /* Move to the next block */ 251 | reader->block = block = next; 252 | reader->offset = 0; 253 | 254 | if(reader->eof) 255 | { 256 | return(-1); 257 | } 258 | } 259 | 260 | /* Limit reads to the current block */ 261 | if(length > block->length - reader->offset) 262 | { 263 | length = block->length - reader->offset; 264 | } 265 | 266 | *ptr = (uint8_t *) block->data + reader->offset; 267 | reader->offset += length; 268 | 269 | return(length); 270 | } 271 | 272 | size_t fifo_write_ptr(fifo_t *fifo, void **ptr, int wait) 273 | { 274 | fifo_block_t *block = fifo->block; 275 | 276 | if(block == NULL || block->length == 0) return(-1); 277 | 278 | if(fifo->offset == block->length) 279 | { 280 | fifo_block_t *next = block->next; 281 | 282 | pthread_mutex_lock(&next->mutex); 283 | 284 | if(wait) 285 | { 286 | /* Wait for the next block to be read */ 287 | while(next->readers > 0) 288 | { 289 | pthread_cond_wait(&next->cond, &next->mutex); 290 | } 291 | } 292 | else if(next->readers > 0) 293 | { 294 | pthread_mutex_unlock(&next->mutex); 295 | return(0); 296 | } 297 | 298 | next->writing = 1; 299 | 300 | pthread_mutex_unlock(&next->mutex); 301 | 302 | /* Mark current block as ready */ 303 | pthread_mutex_lock(&block->mutex); 304 | block->writing = 0; 305 | pthread_cond_signal(&block->cond); 306 | pthread_mutex_unlock(&block->mutex); 307 | 308 | fifo->block = block = next; 309 | fifo->offset = 0; 310 | } 311 | 312 | *ptr = (uint8_t *) block->data + fifo->offset; 313 | 314 | return(block->length - fifo->offset); 315 | } 316 | 317 | void fifo_write(fifo_t *fifo, size_t length) 318 | { 319 | fifo->offset += length; 320 | } 321 | 322 | -------------------------------------------------------------------------------- /src/fifo.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2024 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _FIFO_H 19 | #define _FIFO_H 20 | 21 | /* Single writer / multi reader FIFO */ 22 | 23 | typedef struct _fifo_block_t { 24 | 25 | pthread_mutex_t mutex; 26 | pthread_cond_t cond; 27 | 28 | int readers; 29 | int writing; 30 | 31 | void *data; 32 | size_t length; 33 | 34 | struct _fifo_block_t *prev, *next; 35 | 36 | } fifo_block_t; 37 | 38 | typedef struct { 39 | 40 | size_t count; 41 | fifo_block_t *blocks; 42 | 43 | fifo_block_t *block; 44 | size_t offset; 45 | 46 | } fifo_t; 47 | 48 | typedef struct { 49 | 50 | fifo_block_t *block; 51 | size_t offset; 52 | 53 | int eof; 54 | fifo_block_t *prefill; 55 | 56 | } fifo_reader_t; 57 | 58 | /* Initalise and allocate memory for a FIFO. 59 | * 60 | * fifo: Pointer to uninitalised FIFO 61 | * count: Number of blocks (min: 3) 62 | * length: Length of each block in bytes (min: 1) 63 | * 64 | * Returns 0 on success, or -1 if out of memory 65 | */ 66 | extern int fifo_init(fifo_t *fifo, size_t count, size_t length); 67 | 68 | /* Mark the FIFO as closed. Readers can continue 69 | * reading any remaining data. 70 | * 71 | * fifo: Pointer to initalised FIFO 72 | */ 73 | extern void fifo_close(fifo_t *fifo); 74 | 75 | /* Free a FIFO. Waits until all readers have 76 | * finished and releases memory. 77 | * 78 | * fifo: Pointer to initalised FIFO 79 | */ 80 | extern void fifo_free(fifo_t *fifo); 81 | 82 | /* Request a pointer into the FIFO for writing. 83 | * 84 | * fifo: Pointer to initalised FIFO 85 | * ptr: Pointer to where the pointer is stored 86 | * wait: Set to 1 to wait on a free block 87 | * 88 | * Returns Number of bytes available in buffer, 89 | * 0 if no blocks are free and wait == 0, or 90 | * -1 if the FIFO is closed. 91 | * 92 | * ptr is only valid if the return is > 0 93 | */ 94 | extern size_t fifo_write_ptr(fifo_t *fifo, void **ptr, int wait); 95 | 96 | /* Submit data written to the pointer returned 97 | * by fifo_write_ptr(). 98 | * 99 | * fifo: Pointer to initalised FIFO 100 | * length: Number of bytes written 101 | * 102 | * length must not be greater than the 103 | * value returned by fifo_write_ptr(). 104 | */ 105 | extern void fifo_write(fifo_t *fifo, size_t length); 106 | 107 | /* Initalise a FIFO reader. 108 | * 109 | * reader: Pointer to an uninitalised FIFO reader 110 | * fifo: Pointer to an initalised FIFO 111 | * prefill: Number of blocks that must be written to 112 | * before reading begins (max: num. blocks - 2), 113 | * or -1 to automatically use the max value 114 | * 115 | * This must be called in the same thread that 116 | * called fifo_init(), and before any writes 117 | * have been made. 118 | */ 119 | extern void fifo_reader_init(fifo_reader_t *reader, fifo_t *fifo, int prefill); 120 | 121 | /* Close a FIFO reader. 122 | * 123 | * reader: Pointer to an initalised FIFO reader 124 | */ 125 | extern void fifo_reader_close(fifo_reader_t *reader); 126 | 127 | /* Read data from the FIFO 128 | * 129 | * reader: Pointer to an initalised FIFO reader 130 | * ptr: Pointer to where the pointer is stored 131 | * length: Maximum number of bytes to return 132 | * wait: Set to 1 to wait on data being available 133 | * 134 | * Returns number of bytes avaliable at ptr, 135 | * 0 if wait == 0 and no data was ready, or 136 | * -1 if the FIFO reader is closed. 137 | */ 138 | extern size_t fifo_read(fifo_reader_t *reader, void **ptr, size_t length, int wait); 139 | 140 | #endif 141 | 142 | -------------------------------------------------------------------------------- /src/fir.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _FIR_H 19 | #define _FIR_H 20 | 21 | #include "common.h" 22 | 23 | typedef struct { 24 | 25 | int type; 26 | 27 | int interpolation; 28 | int decimation; 29 | 30 | int ntaps; 31 | int ataps; 32 | int16_t *itaps; 33 | int16_t *qtaps; 34 | 35 | int owin; 36 | int lwin; 37 | int16_t *win; 38 | int d; 39 | 40 | const int16_t *in; 41 | size_t in_samples; 42 | size_t in_step; 43 | 44 | } fir_int16_t; 45 | 46 | typedef struct { 47 | 48 | int type; 49 | 50 | int interpolation; 51 | int decimation; 52 | 53 | int ntaps; 54 | int ataps; 55 | int32_t *itaps; 56 | int32_t *qtaps; 57 | 58 | int owin; 59 | int lwin; 60 | int32_t *win; 61 | int d; 62 | 63 | } fir_int32_t; 64 | 65 | extern void fir_normalise(double *taps, size_t ntaps, double f); 66 | 67 | extern void fir_low_pass(double *taps, size_t ntaps, double sample_rate, double cutoff, double width, double gain); 68 | extern int fir_gaussian_low_pass_ntaps(double sample_rate, double cutoff); 69 | extern void fir_gaussian_low_pass(double *taps, int ntaps, double sample_rate, double cutoff, double gain); 70 | extern void fir_band_reject(double *taps, size_t ntaps, double sample_rate, double low_cutoff, double high_cutoff, double width, double gain); 71 | extern void fir_complex_band_pass(double *taps, size_t ntaps, double sample_rate, double low_cutoff, double high_cutoff, double width, double gain); 72 | 73 | extern int fir_int16_init(fir_int16_t *s, const double *taps, int ntaps, int interpolation, int decimation, int delay); 74 | extern void fir_int16_feed(fir_int16_t *s, const int16_t *in, size_t samples, size_t step); 75 | extern size_t fir_int16_process(fir_int16_t *s, int16_t *out, size_t samples, size_t step); 76 | extern size_t fir_int16_process_block(fir_int16_t *s, int16_t *out, const int16_t *in, size_t samples, int step); 77 | extern size_t fir_int16_output_size(fir_int16_t *s, size_t samples); 78 | extern void fir_int16_free(fir_int16_t *s); 79 | 80 | extern int fir_int16_resampler_init(fir_int16_t *s, r64_t out_rate, r64_t in_rate); 81 | 82 | extern int fir_int16_complex_init(fir_int16_t *s, const double *taps, int ntaps, int interpolation, int decimation, int delay); 83 | extern size_t fir_int16_complex_process(fir_int16_t *s, int16_t *out, size_t samples, size_t step); 84 | 85 | extern int fir_int16_scomplex_init(fir_int16_t *s, const double *taps, int ntaps, int interpolation, int decimation, int delay); 86 | extern size_t fir_int16_scomplex_process(fir_int16_t *s, int16_t *out, size_t samples, size_t step); 87 | 88 | extern int fir_int32_init(fir_int32_t *s, const double *taps, int ntaps, int interpolation, int decimation, int delay); 89 | extern size_t fir_int32_process(fir_int32_t *s, int32_t *out, const int32_t *in, size_t samples); 90 | extern void fir_int32_free(fir_int32_t *s); 91 | 92 | typedef struct { 93 | double a[2]; 94 | double b[2]; 95 | double ix; 96 | double iy; 97 | } iir_int16_t; 98 | 99 | extern int iir_int16_init(iir_int16_t *s, const double *a, const double *b); 100 | extern size_t iir_int16_process(iir_int16_t *s, int16_t *out, const int16_t *in, size_t samples, size_t step); 101 | extern void iir_int16_free(iir_int16_t *s); 102 | 103 | typedef struct { 104 | 105 | /* Input fir filters */ 106 | fir_int32_t vfir; 107 | fir_int32_t ffir; 108 | 109 | /* Limiter shape */ 110 | int width; 111 | int16_t *shape; 112 | 113 | /* Limiter state */ 114 | int16_t level; 115 | int32_t *fix; 116 | int32_t *var; 117 | int16_t *att; 118 | int p; 119 | int h; 120 | 121 | } limiter_t; 122 | 123 | extern void limiter_free(limiter_t *s); 124 | extern int limiter_init(limiter_t *s, int16_t level, int width, const double *vtaps, const double *ftaps, int ntaps); 125 | extern void limiter_process(limiter_t *s, int16_t *out, const int16_t *vin, const int16_t *fin, int samples, int step); 126 | 127 | #endif 128 | 129 | -------------------------------------------------------------------------------- /src/hacktv.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _HACKTV_H 19 | #define _HACKTV_H 20 | 21 | #include 22 | #include "video.h" 23 | #include "rf.h" 24 | 25 | /* Return codes */ 26 | #define HACKTV_OK 0 27 | #define HACKTV_ERROR -1 28 | #define HACKTV_OUT_OF_MEMORY -2 29 | 30 | /* Standard audio sample rate */ 31 | #define HACKTV_AUDIO_SAMPLE_RATE 32000 32 | 33 | /* Program state */ 34 | typedef struct { 35 | 36 | /* Configuration */ 37 | char *output_type; 38 | char *output; 39 | char *mode; 40 | int samplerate; 41 | int pixelrate; 42 | float level; 43 | float deviation; 44 | float gamma; 45 | int interlace; 46 | av_fit_mode_t fit_mode; 47 | r64_t min_aspect; 48 | r64_t max_aspect; 49 | int repeat; 50 | int shuffle; 51 | int verbose; 52 | char *teletext; 53 | char *wss; 54 | char *videocrypt; 55 | char *videocrypt2; 56 | char *videocrypts; 57 | int syster; 58 | int systeraudio; 59 | char *eurocrypt; 60 | int acp; 61 | int vits; 62 | int vitc; 63 | int cc608; 64 | int filter; 65 | int nocolour; 66 | int s_video; 67 | float volume; 68 | int noaudio; 69 | int nonicam; 70 | int a2stereo; 71 | int scramble_video; 72 | int scramble_audio; 73 | uint64_t frequency; 74 | int amp; 75 | int gain; 76 | char *antenna; 77 | int file_type; 78 | int showecm; 79 | int chid; 80 | int mac_audio_stereo; 81 | int mac_audio_quality; 82 | int mac_audio_protection; 83 | int mac_audio_companded; 84 | char *sis; 85 | int swap_iq; 86 | int64_t offset; 87 | char *passthru; 88 | int ec_mat_rating; 89 | char *ec_ppv; 90 | int nodate; 91 | int invert_video; 92 | char *raw_bb_file; 93 | int16_t raw_bb_blanking_level; 94 | int16_t raw_bb_white_level; 95 | int secam_field_id; 96 | int secam_field_id_lines; 97 | int list_modes; 98 | int json; 99 | char *ffmt; 100 | char *fopts; 101 | int fl2k_audio; 102 | 103 | /* Video encoder state */ 104 | vid_t vid; 105 | 106 | /* RF sink interface */ 107 | rf_t rf; 108 | 109 | } hacktv_t; 110 | 111 | #endif 112 | 113 | -------------------------------------------------------------------------------- /src/mac.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | /* -=== D/D2-MAC encoder ===- */ 19 | 20 | #ifndef _MAC_H 21 | #define _MAC_H 22 | 23 | #define MAC_CLOCK_RATE 20250000 24 | #define MAC_WIDTH 1296 25 | #define MAC_LINES 625 26 | 27 | /* The two data modes */ 28 | #define MAC_MODE_D 0 29 | #define MAC_MODE_D2 1 30 | 31 | /* MAC VSAM modes */ 32 | #define MAC_VSAM_DOUBLE_CUT (0 << 0) 33 | #define MAC_VSAM_UNSCRAMBLED (1 << 0) 34 | #define MAC_VSAM_SINGLE_CUT (2 << 0) 35 | 36 | #define MAC_VSAM_FREE_ACCESS (0 << 2) 37 | #define MAC_VSAM_CONTROLLED_ACCESS (1 << 2) 38 | 39 | #define MAC_VSAM_FREE_ACCESS_DOUBLE_CUT 0 /* 000: free access, double-cut component rotation scrambling */ 40 | #define MAC_VSAM_FREE_ACCESS_UNSCRAMBLED 1 /* 001: free access, unscrambled */ 41 | #define MAC_VSAM_FREE_ACCESS_SINGLE_CUT 2 /* 010: free access, single-cut line rotation scrambling */ 42 | #define MAC_VSAM_CONTROLLED_ACCESS_DOUBLE_CUT 4 /* 100: controlled access, double-cut component rotation scrambling */ 43 | #define MAC_VSAM_CONTROLLED_ACCESS_SINGLE_CUT 6 /* 110: controlled access, single-cut line rotation scrambling */ 44 | 45 | /* Video aspect ratios */ 46 | #define MAC_RATIO_4_3 0 47 | #define MAC_RATIO_16_9 1 48 | 49 | /* Number of bits and bytes in a packet, bytes rounded up */ 50 | #define MAC_PACKET_BITS 751 51 | #define MAC_PACKET_BYTES 94 52 | 53 | /* Number of bits and bytes in a packet payload */ 54 | #define MAC_PAYLOAD_BITS 728 55 | #define MAC_PAYLOAD_BYTES 91 56 | 57 | /* Number of packets in the transmit queue */ 58 | #define MAC_QUEUE_LEN 12 59 | 60 | /* Maximum number of bytes per line (for D-MAC, D2 is half) */ 61 | #define MAC_LINE_BYTES (MAC_WIDTH / 8) 62 | 63 | /* Audio defines */ 64 | #define MAC_MEDIUM_QUALITY 0 65 | #define MAC_HIGH_QUALITY 1 66 | 67 | #define MAC_MONO 0 68 | #define MAC_STEREO 1 69 | 70 | #define MAC_COMPANDED 0 71 | #define MAC_LINEAR 1 72 | 73 | #define MAC_FIRST_LEVEL_PROTECTION 0 74 | #define MAC_SECOND_LEVEL_PROTECTION 1 75 | 76 | /* CA PRBS defines */ 77 | #define MAC_PRBS_CW_FA (((uint64_t) 1 << 60) - 1) 78 | #define MAC_PRBS_CW_MASK (((uint64_t) 1 << 60) - 1) 79 | #define MAC_PRBS_SR1_MASK (((uint32_t) 1 << 31) - 1) 80 | #define MAC_PRBS_SR2_MASK (((uint32_t) 1 << 29) - 1) 81 | #define MAC_PRBS_SR3_MASK (((uint32_t) 1 << 31) - 1) 82 | #define MAC_PRBS_SR4_MASK (((uint32_t) 1 << 29) - 1) 83 | #define MAC_PRBS_SR5_MASK (((uint32_t) 1 << 61) - 1) 84 | 85 | #include "eurocrypt.h" 86 | 87 | typedef struct { 88 | uint8_t pkt[MAC_PAYLOAD_BYTES]; 89 | int address; 90 | int continuity; 91 | int scramble; /* 0 = Don't scramble, 1 = Scramble */ 92 | } _mac_packet_queue_item_t; 93 | 94 | typedef struct { 95 | 96 | /* The packet queue */ 97 | _mac_packet_queue_item_t pkts[MAC_QUEUE_LEN]; /* Copy of the packets */ 98 | int len; /* Number of packets in the queue */ 99 | int p; /* Index of the next free slot */ 100 | 101 | } mac_packet_queue_t; 102 | 103 | typedef struct { 104 | 105 | mac_packet_queue_t queue; /* Packet queue for this subframe */ 106 | uint8_t pkt[MAC_PACKET_BYTES]; /* The current packet */ 107 | int pkt_bits; /* Bits sent of the current packet */ 108 | 109 | /* Channel continuity counters */ 110 | int service_continuity; /* Channel 0 -- Service information */ 111 | int audio_continuity; /* Channel 224 -- Audio packets */ 112 | int dummy_continuity; /* Channel 1023 -- Dummy packets */ 113 | 114 | } mac_subframe_t; 115 | 116 | typedef struct { 117 | 118 | /* Input audio */ 119 | const int16_t *audio; 120 | size_t audio_len; 121 | 122 | /* Output audio / data */ 123 | int samples_per_block; 124 | int src_samples_per_block; 125 | int bits_per_sample; 126 | int block_len; 127 | int16_t j17[64 * 2]; 128 | uint8_t block[120]; 129 | uint8_t pkt[MAC_PACKET_BYTES]; 130 | 131 | int x; 132 | int pktx; 133 | int j17x; 134 | 135 | /* Channel configuration */ 136 | int high_quality; 137 | int stereo; 138 | int linear; 139 | int protection; 140 | 141 | /* Packet details */ 142 | int address; 143 | int continuity; 144 | int scramble; 145 | int conditional; 146 | 147 | /* Encoder settings */ 148 | struct { 149 | fir_int16_t fir; 150 | int offset; 151 | int len; 152 | int src_offset; 153 | int src_len; 154 | int sf_offset; 155 | int sf_len; 156 | } channel[2]; 157 | 158 | /* SI packets */ 159 | uint8_t si_pkt[MAC_PACKET_BYTES]; 160 | int si_timer; 161 | 162 | } mac_audioenc_t; 163 | 164 | typedef struct { 165 | 166 | uint8_t vsam; /* VSAM Vision scrambling and access mode */ 167 | uint8_t ratio; /* 0: 4:3, 1: 16:9 */ 168 | r64_t ratio_threshold; 169 | 170 | /* Main TV audio */ 171 | mac_audioenc_t audio; 172 | 173 | /* 1 = Teletext enabled */ 174 | int teletext; 175 | 176 | /* UDT (Unified Date and Time) sequence */ 177 | uint8_t udt[25]; 178 | 179 | /* RDF sequence index */ 180 | int rdf; 181 | 182 | /* The data subframes */ 183 | mac_subframe_t subframes[2]; 184 | 185 | /* PRBS seed, per-line */ 186 | uint16_t prbs[MAC_LINES]; 187 | 188 | /* Duobinary state */ 189 | int polarity; 190 | int16_t *lut; 191 | int width; 192 | 193 | /* Video properties */ 194 | int chrominance_width; 195 | int chrominance_left; 196 | int white_ref_left; 197 | int black_ref_left; 198 | int black_ref_right; 199 | 200 | /* PRBS generators */ 201 | uint64_t cw; 202 | uint64_t sr1; 203 | uint64_t sr2; 204 | uint64_t sr3; 205 | uint64_t sr4; 206 | int video_scale[MAC_WIDTH]; 207 | 208 | /* Eurocrypt state */ 209 | int eurocrypt; 210 | eurocrypt_t ec; 211 | int ec_mat_rating; 212 | 213 | } mac_t; 214 | 215 | extern void mac_golay_encode(uint8_t *data, int blocks); 216 | 217 | extern int mac_init(vid_t *s); 218 | extern void mac_free(vid_t *s); 219 | 220 | extern int mac_write_packet(vid_t *s, int subframe, int address, int continuity, const uint8_t *data, int scramble); 221 | extern int mac_write_audio(vid_t *s, mac_audioenc_t *enc, int subframe, const int16_t *audio, int samples); 222 | 223 | extern int mac_audioenc_init(mac_audioenc_t *enc, int high_quality, int stereo, int protection, int companded, int scramble, int conditional); 224 | extern int mac_audioenc_free(mac_audioenc_t *enc); 225 | extern const uint8_t *mac_audioenc_read(mac_audioenc_t *enc); 226 | extern int mac_audioenc_write(mac_audioenc_t *enc, const int16_t *audio, size_t samples); 227 | 228 | extern int mac_next_line(vid_t *s, void *arg, int nlines, vid_line_t **lines); 229 | 230 | #endif 231 | 232 | -------------------------------------------------------------------------------- /src/nicam728.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | /* NICAM-728 stereo encoder 19 | * 20 | * Based on the BBC RD document "NICAM 728 - DIGITAL 21 | * TWO-CHANNEL STEREO FOR TERRESTRIAL TELEVISION"; 22 | * http://downloads.bbc.co.uk/rd/pubs/reports/1990-06.pdf 23 | * 24 | * http://www.etsi.org/deliver/etsi_en/300100_300199/300163/01.02.01_60/en_300163v010201p.pdf 25 | * 26 | * NICAM was designed for 14-bit PCM samples, but for 27 | * simplicity this encoder expects 16-bit samples. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "nicam728.h" 35 | 36 | /* Pre-calculated J.17 pre-emphasis filter taps, 32kHz sample rate */ 37 | static const int32_t _j17_taps[_J17_NTAPS] = { 38 | -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -3, -3, -3, -3, -5, -5, 39 | -6, -7, -9, -10, -13, -14, -18, -21, -27, -32, -42, -51, -69, -86, -120, 40 | -159, -233, -332, -524, -814, -1402, -2372, -4502, 25590, -4502, -2372, 41 | -1402, -814, -524, -332, -233, -159, -120, -86, -69, -51, -42, -32, -27, 42 | -21, -18, -14, -13, -10, -9, -7, -6, -5, -5, -3, -3, -3, -3, -2, -2, -1, 43 | -1, -1, -1, -1, -1, -1, -1, 0, -1 44 | }; 45 | 46 | /* RF symbols */ 47 | static const int _step[4] = { 0, 3, 1, 2 }; 48 | static const int _syms[4] = { 0, 1, 3, 2 }; 49 | 50 | /* NICAM scaling factors */ 51 | 52 | typedef struct { 53 | int factor; 54 | int shift; 55 | int coding_range; 56 | int protection_range; 57 | } _scale_factor_t; 58 | 59 | static const _scale_factor_t _scale_factors[8] = { 60 | { 0, 2, 5, 7 }, /* 0b000 */ 61 | { 1, 2, 5, 7 }, /* 0b001 */ 62 | { 2, 2, 5, 6 }, /* 0b010 */ 63 | { 4, 2, 5, 5 }, /* 0b100 */ 64 | { 3, 3, 4, 4 }, /* 0b011 */ 65 | { 5, 4, 3, 3 }, /* 0b101 */ 66 | { 6, 5, 2, 2 }, /* 0b110 */ 67 | { 7, 6, 1, 1 }, /* 0b111 */ 68 | }; 69 | 70 | static const _scale_factor_t *_scale_factor(int16_t *pcm, int step) 71 | { 72 | int i, b; 73 | int16_t s; 74 | 75 | /* Calculate the optimal scale factor for this audio block */ 76 | b = 1; 77 | 78 | /* Test each sample if it requires a larger range */ 79 | for(i = 0; b < 7 && i < NICAM_AUDIO_LEN; i++) 80 | { 81 | /* Negative values use the same scales */ 82 | s = (*pcm < 0) ? ~*pcm : *pcm; 83 | 84 | /* Test if the scale factor needs to be increased */ 85 | while(b < 7 && s >> (b + 8)) 86 | { 87 | b++; 88 | } 89 | 90 | pcm += step; 91 | } 92 | 93 | return(&_scale_factors[b]); 94 | } 95 | 96 | static void _prn(uint8_t prn[NICAM_FRAME_BYTES - 1]) 97 | { 98 | /* Generate the full PRN sequence for a NICAM-728 packet 99 | * First 20 bits of the sequence should be: 100 | * 0000 0111 1011 1110 0010 .... 101 | * 07 BE 2. ... 102 | */ 103 | 104 | int poly = 0x1FF; 105 | int x, i; 106 | 107 | for(x = 0; x < NICAM_FRAME_BYTES - 1; x++) 108 | { 109 | prn[x] = 0x00; 110 | 111 | for(i = 0; i < 8; i++) 112 | { 113 | uint8_t b; 114 | 115 | b = poly & 1; 116 | b ^= (poly >> 4) & 1; 117 | 118 | poly >>= 1; 119 | poly |= b << 8; 120 | 121 | prn[x] <<= 1; 122 | prn[x] |= b; 123 | } 124 | } 125 | } 126 | 127 | static uint8_t _parity(unsigned int value) 128 | { 129 | uint8_t p = 0; 130 | 131 | while(value) 132 | { 133 | p ^= value & 1; 134 | value >>= 1; 135 | } 136 | 137 | return(p); 138 | } 139 | 140 | void _process_audio(nicam_enc_t *s, int16_t dst[NICAM_AUDIO_LEN * 2], const int16_t src[NICAM_AUDIO_LEN * 2]) 141 | { 142 | const _scale_factor_t *scale[2]; 143 | int32_t l, r; 144 | int x, xi; 145 | 146 | /* Apply J.17 pre-emphasis filter */ 147 | for(x = 0; x < NICAM_AUDIO_LEN; x++) 148 | { 149 | s->fir_l[s->fir_p] = src ? src[x * 2 + 0] : 0; 150 | s->fir_r[s->fir_p] = src ? src[x * 2 + 1] : 0; 151 | if(++s->fir_p == _J17_NTAPS) s->fir_p = 0; 152 | 153 | for(l = r = xi = 0; xi < _J17_NTAPS; xi++) 154 | { 155 | l += (int32_t) s->fir_l[s->fir_p] * _j17_taps[xi]; 156 | r += (int32_t) s->fir_r[s->fir_p] * _j17_taps[xi]; 157 | if(++s->fir_p == _J17_NTAPS) s->fir_p = 0; 158 | } 159 | 160 | dst[x * 2 + 0] = l >> 15; 161 | dst[x * 2 + 1] = r >> 15; 162 | } 163 | 164 | /* Calculate the scale factors for each channel */ 165 | scale[0] = _scale_factor(dst + 0, 2); 166 | scale[1] = _scale_factor(dst + 1, 2); 167 | 168 | /* Scale and append each sample to the frame */ 169 | for(xi = x = 0; x < NICAM_AUDIO_LEN * 2; x++) 170 | { 171 | /* Shift down the selected range */ 172 | dst[x] = (dst[x] >> scale[x & 1]->shift) & 0x3FF; 173 | 174 | /* Add the parity bit (6 MSBs only) */ 175 | dst[x] |= _parity(dst[x] >> 4) << 10; 176 | 177 | /* Add scale-factor code if necessary */ 178 | if(x < 54) 179 | { 180 | dst[x] ^= ((scale[x & 1]->factor >> (2 - (x / 2 % 3))) & 1) << 10; 181 | } 182 | } 183 | } 184 | 185 | void nicam_encode_init(nicam_enc_t *s, uint8_t mode, uint8_t reserve) 186 | { 187 | memset(s, 0, sizeof(nicam_enc_t)); 188 | 189 | s->mode = mode; 190 | s->reserve = reserve; 191 | 192 | _prn(s->prn); 193 | } 194 | 195 | void nicam_encode_frame(nicam_enc_t *s, uint8_t frame[NICAM_FRAME_BYTES], const int16_t audio[NICAM_AUDIO_LEN * 2]) 196 | { 197 | int16_t j17_audio[NICAM_AUDIO_LEN * 2]; 198 | int x, xi; 199 | 200 | /* Encode the audio */ 201 | _process_audio(s, j17_audio, audio); 202 | 203 | /* Initialise the NICAM frame header with the FAW (Frame Alignment Word) */ 204 | frame[0] = NICAM_FAW; 205 | 206 | /* Set the application control bits */ 207 | frame[1] = (((~s->frame) >> 3) & 1) << 7; /* C0 frame flag-bit. Toggled every 8 frames */ 208 | frame[1] |= ((s->mode >> 2) & 1) << 6; /* C1 */ 209 | frame[1] |= ((s->mode >> 1) & 1) << 5; /* C2 */ 210 | frame[1] |= ((s->mode >> 0) & 1) << 4; /* C3 */ 211 | frame[1] |= (s->reserve & 1) << 3; /* C4 reserve sound switching flag */ 212 | 213 | /* The additional bits AD0-AD10 and audio are all zero */ 214 | for(x = 2; x < NICAM_FRAME_BYTES; x++) 215 | { 216 | frame[x] = 0; 217 | } 218 | 219 | /* Pack the encoded audio into the frame */ 220 | for(xi = x = 0; x < NICAM_AUDIO_LEN * 2; x++) 221 | { 222 | int b; 223 | 224 | for(b = 0; b < 11; b++, j17_audio[x] >>= 1) 225 | { 226 | /* Apply the bit to the interleaved location */ 227 | if(j17_audio[x] & 1) 228 | { 229 | frame[3 + (xi / 8)] |= 1 << (7 - (xi % 8)); 230 | } 231 | 232 | /* Calculate next interleaved bit location */ 233 | xi += 16; 234 | if(xi >= NICAM_FRAME_BITS - 24) 235 | { 236 | xi -= NICAM_FRAME_BITS - 24 - 1; 237 | } 238 | } 239 | } 240 | 241 | /* Apply the PRN */ 242 | for(x = 0; x < NICAM_FRAME_BYTES - 1; x++) 243 | { 244 | frame[x + 1] ^= s->prn[x]; 245 | } 246 | 247 | /* Increment the frame counter */ 248 | s->frame++; 249 | } 250 | 251 | static double _hamming(double x) 252 | { 253 | if(x < -1 || x > 1) return(0); 254 | return(0.54 - 0.46 * cos((M_PI * (1.0 + x)))); 255 | } 256 | 257 | int nicam_mod_init(nicam_mod_t *s, uint8_t mode, uint8_t reserve, unsigned int sample_rate, unsigned int frequency, double beta, double level) 258 | { 259 | double sps; 260 | double t; 261 | double r; 262 | int x, n; 263 | 264 | memset(s, 0, sizeof(nicam_mod_t)); 265 | 266 | /* Samples per symbol */ 267 | sps = (double) sample_rate / 364000.0; 268 | 269 | /* Calculate the number of taps needed to cover 5 symbols, rounded up to odd number */ 270 | s->ntaps = ((unsigned int) (sps * 5) + 1) | 1; 271 | 272 | s->taps = malloc(sizeof(int16_t) * s->ntaps); 273 | if(!s->taps) 274 | { 275 | return(-1); 276 | } 277 | 278 | /* Generate the filter taps */ 279 | n = s->ntaps / 2; 280 | for(x = -n; x <= n; x++) 281 | { 282 | t = ((double) x) / sps; 283 | 284 | r = rrc(t, beta, 1.0) * _hamming((double) x / n); 285 | r *= M_SQRT1_2 * INT16_MAX * level; 286 | 287 | s->taps[x + n] = lround(r); 288 | } 289 | 290 | /* Allocate memory for the baseband buffer */ 291 | s->bb_start = calloc(s->ntaps, sizeof(cint16_t)); 292 | s->bb_end = s->bb_start + s->ntaps; 293 | s->bb = s->bb_start; 294 | s->bb_len = 0; 295 | 296 | if(!s->bb_start) 297 | { 298 | return(-1); 299 | } 300 | 301 | /* Setup values for the sample rate error correction */ 302 | n = gcd(sample_rate, NICAM_SYMBOL_RATE); 303 | 304 | s->decimation = NICAM_SYMBOL_RATE / n; 305 | s->sps = (sample_rate + NICAM_SYMBOL_RATE - 1) / NICAM_SYMBOL_RATE; 306 | s->dsl = (s->sps * s->decimation) % (sample_rate / n); 307 | s->ds = 0; 308 | 309 | /* Setup the mixer signal */ 310 | n = gcd(sample_rate, frequency); 311 | x = sample_rate / n; 312 | s->cc_start = sin_cint16(x, frequency / n, 1.0); 313 | s->cc_end = s->cc_start + x; 314 | s->cc = s->cc_start; 315 | 316 | if(!s->cc) 317 | { 318 | return(-1); 319 | } 320 | 321 | /* Initialise the encoder */ 322 | nicam_encode_init(&s->enc, mode, reserve); 323 | s->frame_bit = NICAM_FRAME_BITS; 324 | 325 | return(0); 326 | } 327 | 328 | int nicam_mod_free(nicam_mod_t *s) 329 | { 330 | free(s->cc_start); 331 | free(s->bb_start); 332 | free(s->taps); 333 | 334 | return(0); 335 | } 336 | 337 | void nicam_mod_input(nicam_mod_t *s, const int16_t audio[NICAM_AUDIO_LEN * 2]) 338 | { 339 | memcpy(s->audio, audio, sizeof(int16_t) * NICAM_AUDIO_LEN * 2); 340 | } 341 | 342 | int nicam_mod_output(nicam_mod_t *s, int16_t *iq, size_t samples) 343 | { 344 | cint16_t *ciq = (cint16_t *) iq; 345 | int x, i; 346 | int16_t r; 347 | 348 | for(x = 0; x < samples;) 349 | { 350 | /* Output and clear the buffer */ 351 | for(; x < samples && s->bb_len; x++, s->bb_len--) 352 | { 353 | cint16_mula(ciq++, s->bb, s->cc); 354 | 355 | s->bb->i = 0; 356 | s->bb->q = 0; 357 | 358 | if(++s->bb == s->bb_end) 359 | { 360 | s->bb = s->bb_start; 361 | } 362 | 363 | if(++s->cc == s->cc_end) 364 | { 365 | s->cc = s->cc_start; 366 | } 367 | } 368 | 369 | if(s->bb_len > 0) 370 | { 371 | break; 372 | } 373 | 374 | if(s->frame_bit == NICAM_FRAME_BITS) 375 | { 376 | /* Encode the next frame */ 377 | nicam_encode_frame(&s->enc, s->frame, s->audio); 378 | s->frame_bit = 0; 379 | } 380 | 381 | /* Read out the next 2-bit symbol, USB first */ 382 | s->dsym += _step[(s->frame[s->frame_bit >> 3] >> (6 - (s->frame_bit & 0x07))) & 0x03]; 383 | s->dsym &= 0x03; 384 | s->frame_bit += 2; 385 | 386 | /* Encode the symbol */ 387 | for(i = 0; i < s->ntaps; i++) 388 | { 389 | r = s->taps[i]; 390 | s->bb->i += (_syms[s->dsym] & 1 ? r : -r); 391 | s->bb->q += (_syms[s->dsym] & 2 ? r : -r); 392 | 393 | if(++s->bb == s->bb_end) 394 | { 395 | s->bb = s->bb_start; 396 | } 397 | } 398 | 399 | /* Calculate length of the next block */ 400 | s->bb_len = s->sps; 401 | 402 | s->ds += s->dsl; 403 | if(s->ds >= s->decimation) 404 | { 405 | s->bb_len--; 406 | s->ds -= s->decimation; 407 | } 408 | } 409 | 410 | return(0); 411 | } 412 | 413 | -------------------------------------------------------------------------------- /src/nicam728.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _NICAM_H 19 | #define _NICAM_H 20 | 21 | #include 22 | #include "common.h" 23 | 24 | /* NICAM bit and symbol rates */ 25 | #define NICAM_BIT_RATE 728000 26 | #define NICAM_SYMBOL_RATE (NICAM_BIT_RATE / 2) 27 | 28 | /* Audio sample rate for NICAM */ 29 | #define NICAM_AUDIO_RATE 32000 30 | 31 | /* Length of a NICAM frame in bits, bytes and symbols */ 32 | #define NICAM_FRAME_BITS 728 33 | #define NICAM_FRAME_BYTES (NICAM_FRAME_BITS / 8) 34 | #define NICAM_FRAME_SYMS (NICAM_FRAME_BITS / 2) 35 | 36 | /* Length of a NICAM frame in audio samples */ 37 | #define NICAM_AUDIO_LEN (NICAM_AUDIO_RATE / 1000) 38 | 39 | /* Frame alignment word (0b01001110) */ 40 | #define NICAM_FAW 0x4E 41 | 42 | /* Modes of operation */ 43 | #define NICAM_MODE_STEREO 0x00 /* Single stereo audio signal */ 44 | #define NICAM_MODE_DUAL_MONO 0x02 /* Two independent mono audio signals */ 45 | #define NICAM_MODE_MONO_DATA 0x04 /* One mono audio signal and one data channel */ 46 | #define NICAM_MODE_DATA 0x06 /* One data channel */ 47 | 48 | /* Taps in J.17 pre-emphasis filter */ 49 | #define _J17_NTAPS 83 50 | 51 | typedef struct { 52 | 53 | uint8_t mode; 54 | uint8_t reserve; 55 | 56 | unsigned int frame; 57 | 58 | uint8_t prn[NICAM_FRAME_BYTES - 1]; 59 | 60 | int fir_p; 61 | int16_t fir_l[_J17_NTAPS]; 62 | int16_t fir_r[_J17_NTAPS]; 63 | 64 | } nicam_enc_t; 65 | 66 | typedef struct { 67 | 68 | nicam_enc_t enc; 69 | 70 | int16_t audio[NICAM_AUDIO_LEN * 2]; 71 | 72 | int ntaps; 73 | int16_t *taps; 74 | int16_t *hist; 75 | 76 | int dsym; /* Differential symbol */ 77 | 78 | cint16_t *bb; 79 | cint16_t *bb_start; 80 | cint16_t *bb_end; 81 | int bb_len; 82 | 83 | int sps; 84 | int ds; 85 | int dsl; 86 | int decimation; 87 | 88 | cint16_t *cc; 89 | cint16_t *cc_start; 90 | cint16_t *cc_end; 91 | 92 | uint8_t frame[NICAM_FRAME_BYTES]; 93 | int frame_bit; 94 | 95 | } nicam_mod_t; 96 | 97 | extern void nicam_encode_init(nicam_enc_t *s, uint8_t mode, uint8_t reserve); 98 | extern void nicam_encode_frame(nicam_enc_t *s, uint8_t frame[NICAM_FRAME_BYTES], const int16_t audio[NICAM_AUDIO_LEN * 2]); 99 | 100 | extern int nicam_mod_init(nicam_mod_t *s, uint8_t mode, uint8_t reserve, unsigned int sample_rate, unsigned int frequency, double beta, double level); 101 | extern void nicam_mod_input(nicam_mod_t *s, const int16_t audio[NICAM_AUDIO_LEN * 2]); 102 | extern int nicam_mod_output(nicam_mod_t *s, int16_t *iq, size_t samples); 103 | extern int nicam_mod_free(nicam_mod_t *s); 104 | 105 | #endif 106 | 107 | -------------------------------------------------------------------------------- /src/rf.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2023 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include "rf.h" 21 | 22 | /* RF sink callback handlers */ 23 | int rf_write(rf_t *s, const int16_t *iq_data, size_t samples) 24 | { 25 | if(s->write) 26 | { 27 | return(s->write(s->ctx, iq_data, samples)); 28 | } 29 | 30 | return(RF_ERROR); 31 | } 32 | 33 | int rf_write_audio(rf_t *s, const int16_t *audio, size_t samples) 34 | { 35 | if(s->write_audio) 36 | { 37 | return(s->write_audio(s->ctx, audio, samples)); 38 | } 39 | 40 | return(RF_OK); 41 | } 42 | 43 | int rf_close(rf_t *s) 44 | { 45 | if(s->close) 46 | { 47 | return(s->close(s->ctx)); 48 | } 49 | 50 | return(RF_OK); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/rf.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2023 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _RF_H 19 | #define _RF_H 20 | 21 | /* Return codes */ 22 | #define RF_OK 0 23 | #define RF_ERROR -1 24 | #define RF_OUT_OF_MEMORY -2 25 | 26 | /* Signal types */ 27 | #define RF_INT16_COMPLEX 0 28 | #define RF_INT16_REAL 1 29 | 30 | /* File output types */ 31 | #define RF_UINT8 0 32 | #define RF_INT8 1 33 | #define RF_UINT16 2 34 | #define RF_INT16 3 35 | #define RF_INT32 4 36 | #define RF_FLOAT 5 /* 32-bit float */ 37 | 38 | /* RF output function prototypes */ 39 | typedef int (*rf_write_t)(void *ctx, const int16_t *iq_data, size_t samples); 40 | typedef int (*rf_write_audio_t)(void *ctx, const int16_t *audio, size_t samples); 41 | typedef int (*rf_close_t)(void *ctx); 42 | 43 | typedef struct { 44 | 45 | void *ctx; 46 | rf_write_t write; 47 | rf_write_t write_audio; 48 | rf_close_t close; 49 | 50 | } rf_t; 51 | 52 | extern int rf_write(rf_t *s, const int16_t *iq_data, size_t samples); 53 | extern int rf_write_audio(rf_t *s, const int16_t *audio, size_t samples); 54 | extern int rf_close(rf_t *s); 55 | 56 | #include "rf_file.h" 57 | #include "rf_hackrf.h" 58 | #include "rf_soapysdr.h" 59 | #include "rf_fl2k.h" 60 | 61 | #endif 62 | 63 | -------------------------------------------------------------------------------- /src/rf_file.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "rf.h" 23 | 24 | /* File sink */ 25 | typedef struct { 26 | FILE *f; 27 | void *data; 28 | size_t data_size; 29 | size_t samples; 30 | int complex; 31 | int type; 32 | } rf_file_t; 33 | 34 | static int _rf_file_write_uint8_real(void *private, const int16_t *iq_data, size_t samples) 35 | { 36 | rf_file_t *rf = private; 37 | uint8_t *u8 = rf->data; 38 | int i; 39 | 40 | while(samples) 41 | { 42 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 43 | { 44 | u8[i] = (iq_data[0] - INT16_MIN) >> 8; 45 | } 46 | 47 | fwrite(rf->data, rf->data_size, i, rf->f); 48 | 49 | samples -= i; 50 | } 51 | 52 | return(RF_OK); 53 | } 54 | 55 | static int _rf_file_write_int8_real(void *private, const int16_t *iq_data, size_t samples) 56 | { 57 | rf_file_t *rf = private; 58 | int8_t *i8 = rf->data; 59 | int i; 60 | 61 | while(samples) 62 | { 63 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 64 | { 65 | i8[i] = iq_data[0] >> 8; 66 | } 67 | 68 | fwrite(rf->data, rf->data_size, i, rf->f); 69 | 70 | samples -= i; 71 | } 72 | 73 | return(RF_OK); 74 | } 75 | 76 | static int _rf_file_write_uint16_real(void *private, const int16_t *iq_data, size_t samples) 77 | { 78 | rf_file_t *rf = private; 79 | uint16_t *u16 = rf->data; 80 | int i; 81 | 82 | while(samples) 83 | { 84 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 85 | { 86 | u16[i] = (iq_data[0] - INT16_MIN); 87 | } 88 | 89 | fwrite(rf->data, rf->data_size, i, rf->f); 90 | 91 | samples -= i; 92 | } 93 | 94 | return(RF_OK); 95 | } 96 | 97 | static int _rf_file_write_int16_real(void *private, const int16_t *iq_data, size_t samples) 98 | { 99 | rf_file_t *rf = private; 100 | int16_t *i16 = rf->data; 101 | int i; 102 | 103 | while(samples) 104 | { 105 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 106 | { 107 | i16[i] = iq_data[0]; 108 | } 109 | 110 | fwrite(rf->data, rf->data_size, i, rf->f); 111 | 112 | samples -= i; 113 | } 114 | 115 | return(RF_OK); 116 | } 117 | 118 | static int _rf_file_write_int32_real(void *private, const int16_t *iq_data, size_t samples) 119 | { 120 | rf_file_t *rf = private; 121 | int32_t *i32 = rf->data; 122 | int i; 123 | 124 | while(samples) 125 | { 126 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 127 | { 128 | i32[i] = (iq_data[0] << 16) + iq_data[0]; 129 | } 130 | 131 | fwrite(rf->data, rf->data_size, i, rf->f); 132 | 133 | samples -= i; 134 | } 135 | 136 | return(RF_OK); 137 | } 138 | 139 | static int _rf_file_write_float_real(void *private, const int16_t *iq_data, size_t samples) 140 | { 141 | rf_file_t *rf = private; 142 | float *f32 = rf->data; 143 | int i; 144 | 145 | while(samples) 146 | { 147 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 148 | { 149 | f32[i] = (float) iq_data[0] * (1.0 / 32767.0); 150 | } 151 | 152 | fwrite(rf->data, rf->data_size, i, rf->f); 153 | 154 | samples -= i; 155 | } 156 | 157 | return(RF_OK); 158 | } 159 | 160 | static int _rf_file_write_uint8_complex(void *private, const int16_t *iq_data, size_t samples) 161 | { 162 | rf_file_t *rf = private; 163 | uint8_t *u8 = rf->data; 164 | int i; 165 | 166 | while(samples) 167 | { 168 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 169 | { 170 | u8[i * 2 + 0] = (iq_data[0] - INT16_MIN) >> 8; 171 | u8[i * 2 + 1] = (iq_data[1] - INT16_MIN) >> 8; 172 | } 173 | 174 | fwrite(rf->data, rf->data_size, i, rf->f); 175 | 176 | samples -= i; 177 | } 178 | 179 | return(RF_OK); 180 | } 181 | 182 | static int _rf_file_write_int8_complex(void *private, const int16_t *iq_data, size_t samples) 183 | { 184 | rf_file_t *rf = private; 185 | int8_t *i8 = rf->data; 186 | int i; 187 | 188 | while(samples) 189 | { 190 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 191 | { 192 | i8[i * 2 + 0] = iq_data[0] >> 8; 193 | i8[i * 2 + 1] = iq_data[1] >> 8; 194 | } 195 | 196 | fwrite(rf->data, rf->data_size, i, rf->f); 197 | 198 | samples -= i; 199 | } 200 | 201 | return(RF_OK); 202 | } 203 | 204 | static int _rf_file_write_uint16_complex(void *private, const int16_t *iq_data, size_t samples) 205 | { 206 | rf_file_t *rf = private; 207 | uint16_t *u16 = rf->data; 208 | int i; 209 | 210 | while(samples) 211 | { 212 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 213 | { 214 | u16[i * 2 + 0] = (iq_data[0] - INT16_MIN); 215 | u16[i * 2 + 1] = (iq_data[1] - INT16_MIN); 216 | } 217 | 218 | fwrite(rf->data, rf->data_size, i, rf->f); 219 | 220 | samples -= i; 221 | } 222 | 223 | return(RF_OK); 224 | } 225 | 226 | static int _rf_file_write_int16_complex(void *private, const int16_t *iq_data, size_t samples) 227 | { 228 | rf_file_t *rf = private; 229 | 230 | fwrite(iq_data, sizeof(int16_t) * 2, samples, rf->f); 231 | 232 | return(RF_OK); 233 | } 234 | 235 | static int _rf_file_write_int32_complex(void *private, const int16_t *iq_data, size_t samples) 236 | { 237 | rf_file_t *rf = private; 238 | int32_t *i32 = rf->data; 239 | int i; 240 | 241 | while(samples) 242 | { 243 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 244 | { 245 | i32[i * 2 + 0] = (iq_data[0] << 16) + iq_data[0]; 246 | i32[i * 2 + 1] = (iq_data[1] << 16) + iq_data[1]; 247 | } 248 | 249 | fwrite(rf->data, rf->data_size, i, rf->f); 250 | 251 | samples -= i; 252 | } 253 | 254 | return(RF_OK); 255 | } 256 | 257 | static int _rf_file_write_float_complex(void *private, const int16_t *iq_data, size_t samples) 258 | { 259 | rf_file_t *rf = private; 260 | float *f32 = rf->data; 261 | int i; 262 | 263 | while(samples) 264 | { 265 | for(i = 0; i < rf->samples && i < samples; i++, iq_data += 2) 266 | { 267 | f32[i * 2 + 0] = (float) iq_data[0] * (1.0 / 32767.0); 268 | f32[i * 2 + 1] = (float) iq_data[1] * (1.0 / 32767.0); 269 | } 270 | 271 | fwrite(rf->data, rf->data_size, i, rf->f); 272 | 273 | samples -= i; 274 | } 275 | 276 | return(RF_OK); 277 | } 278 | 279 | static int _rf_file_close(void *private) 280 | { 281 | rf_file_t *rf = private; 282 | 283 | if(rf->f && rf->f != stdout) fclose(rf->f); 284 | if(rf->data) free(rf->data); 285 | free(rf); 286 | 287 | return(RF_OK); 288 | } 289 | 290 | int rf_file_open(rf_t *s, char *filename, int type, int complex) 291 | { 292 | rf_file_t *rf = calloc(1, sizeof(rf_file_t)); 293 | 294 | if(!rf) 295 | { 296 | perror("calloc"); 297 | return(RF_ERROR); 298 | } 299 | 300 | rf->complex = complex != 0; 301 | rf->type = type; 302 | 303 | if(filename == NULL) 304 | { 305 | fprintf(stderr, "No output filename provided.\n"); 306 | _rf_file_close(rf); 307 | return(RF_ERROR); 308 | } 309 | else if(strcmp(filename, "-") == 0) 310 | { 311 | rf->f = stdout; 312 | } 313 | else 314 | { 315 | rf->f = fopen(filename, "wb"); 316 | 317 | if(!rf->f) 318 | { 319 | perror("fopen"); 320 | _rf_file_close(rf); 321 | return(RF_ERROR); 322 | } 323 | } 324 | 325 | /* Find the size of the output data type */ 326 | switch(type) 327 | { 328 | case RF_UINT8: rf->data_size = sizeof(uint8_t); break; 329 | case RF_INT8: rf->data_size = sizeof(int8_t); break; 330 | case RF_UINT16: rf->data_size = sizeof(uint16_t); break; 331 | case RF_INT16: rf->data_size = sizeof(int16_t); break; 332 | case RF_INT32: rf->data_size = sizeof(int32_t); break; 333 | case RF_FLOAT: rf->data_size = sizeof(float); break; 334 | default: 335 | fprintf(stderr, "%s: Unrecognised data type %d\n", __func__, type); 336 | _rf_file_close(rf); 337 | return(RF_ERROR); 338 | } 339 | 340 | /* Double the size for complex types */ 341 | if(rf->complex) rf->data_size *= 2; 342 | 343 | /* Number of samples in the temporary buffer */ 344 | rf->samples = 4096; 345 | 346 | /* Allocate the memory, unless the output is int16 complex */ 347 | if(rf->type != RF_INT16 || !rf->complex) 348 | { 349 | rf->data = malloc(rf->data_size * rf->samples); 350 | if(!rf->data) 351 | { 352 | perror("malloc"); 353 | _rf_file_close(rf); 354 | return(RF_ERROR); 355 | } 356 | } 357 | 358 | /* Register the callback functions */ 359 | s->ctx = rf; 360 | s->close = _rf_file_close; 361 | 362 | switch(type) 363 | { 364 | case RF_UINT8: s->write = rf->complex ? _rf_file_write_uint8_complex : _rf_file_write_uint8_real; break; 365 | case RF_INT8: s->write = rf->complex ? _rf_file_write_int8_complex : _rf_file_write_int8_real; break; 366 | case RF_UINT16: s->write = rf->complex ? _rf_file_write_uint16_complex : _rf_file_write_uint16_real; break; 367 | case RF_INT16: s->write = rf->complex ? _rf_file_write_int16_complex : _rf_file_write_int16_real; break; 368 | case RF_INT32: s->write = rf->complex ? _rf_file_write_int32_complex : _rf_file_write_int32_real; break; 369 | case RF_FLOAT: s->write = rf->complex ? _rf_file_write_float_complex : _rf_file_write_float_real; break; 370 | } 371 | 372 | return(RF_OK); 373 | } 374 | 375 | -------------------------------------------------------------------------------- /src/rf_file.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _FILE_H 19 | #define _FILE_H 20 | 21 | extern int rf_file_open(rf_t *s, char *filename, int type, int complex); 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /src/rf_fl2k.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2019 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "rf.h" 25 | #include "fifo.h" 26 | #include "fir.h" 27 | #include "spdif.h" 28 | 29 | #define BUFFERS 4 30 | 31 | typedef struct { 32 | 33 | fl2k_dev_t *d; 34 | unsigned int sample_rate; 35 | int abort; 36 | 37 | fifo_t buffer[3]; 38 | fifo_reader_t reader[3]; 39 | int phase; 40 | 41 | int baseband; 42 | int audio_mode; 43 | 44 | /* Analogue audio */ 45 | int interp; 46 | uint16_t audio[2]; 47 | uint16_t err[2]; 48 | 49 | /* SPDIF audio */ 50 | int16_t pcm[SPDIF_BLOCK_SAMPLES]; 51 | int pcm_len; 52 | fir_int16_t spdif_resampler; 53 | 54 | } fl2k_t; 55 | 56 | static void _callback(fl2k_data_info_t *data_info) 57 | { 58 | fl2k_t *rf = data_info->ctx; 59 | void *channels[3] = { 60 | &data_info->r_buf, 61 | &data_info->g_buf, 62 | &data_info->b_buf 63 | }; 64 | int i; 65 | 66 | if(data_info->device_error) 67 | { 68 | rf->abort = 1; 69 | return; 70 | } 71 | 72 | for(; rf->phase < 3; rf->phase++) 73 | { 74 | i = fifo_read(&rf->reader[rf->phase], channels[rf->phase], FL2K_BUF_LEN, 0); 75 | if(i == 0) 76 | { 77 | if(rf->reader[rf->phase].prefill == NULL) fprintf(stderr, "U"); 78 | break; 79 | } 80 | } 81 | 82 | if(rf->phase == 3) rf->phase = 0; 83 | 84 | data_info->sampletype_signed = 0; 85 | } 86 | 87 | static int _rf_write(void *private, const int16_t *iq_data, size_t samples) 88 | { 89 | fl2k_t *rf = private; 90 | uint8_t *buf[2]; 91 | int i, r; 92 | 93 | if(rf->abort) 94 | { 95 | return(RF_ERROR); 96 | } 97 | 98 | r = 0; 99 | 100 | while(samples > 0) 101 | { 102 | r = fifo_write_ptr(&rf->buffer[0], (void **) &buf[0], 1); 103 | if(r < 0) break; 104 | 105 | if(!rf->baseband) 106 | { 107 | i = fifo_write_ptr(&rf->buffer[1], (void **) &buf[1], 1); 108 | if(i < r) r = i; 109 | if(r < 0) break; 110 | } 111 | 112 | for(i = 0; i < r && i < samples; i++) 113 | { 114 | buf[0][i] = (iq_data[i * 2] - INT16_MIN) >> 8; 115 | } 116 | 117 | fifo_write(&rf->buffer[0], i); 118 | 119 | if(!rf->baseband) 120 | { 121 | for(i = 0; i < r && i < samples; i++) 122 | { 123 | buf[1][i] = (iq_data[i * 2 + 1] - INT16_MIN) >> 8; 124 | } 125 | 126 | fifo_write(&rf->buffer[1], i); 127 | } 128 | 129 | iq_data += i * 2; 130 | samples -= i; 131 | } 132 | 133 | return(r >= 0 ? RF_OK : RF_ERROR); 134 | } 135 | 136 | static int _rf_write_audio(void *private, const int16_t *audio, size_t samples) 137 | { 138 | fl2k_t *rf = private; 139 | uint8_t *buf[2]; 140 | int i, r, c; 141 | 142 | if(audio == NULL) return(RF_OK); 143 | 144 | r = 0; 145 | samples /= 2; 146 | 147 | while(samples > 0) 148 | { 149 | r = fifo_write_ptr(&rf->buffer[1], (void **) &buf[0], 1); 150 | if(r < 0) break; 151 | 152 | i = fifo_write_ptr(&rf->buffer[2], (void **) &buf[1], 1); 153 | if(i < 0) break; 154 | 155 | if(i < r) r = i; 156 | 157 | for(i = 0; i < r && samples > 0; i++) 158 | { 159 | rf->interp += 32000; /* TODO: Don't hardwire this */ 160 | if(rf->interp >= rf->sample_rate) 161 | { 162 | rf->interp -= rf->sample_rate; 163 | rf->audio[0] = audio[0] - INT16_MIN; 164 | rf->audio[1] = audio[1] - INT16_MIN; 165 | samples--; 166 | audio += 2; 167 | } 168 | 169 | for(c = 0; c < 2; c++) 170 | { 171 | /* Apply a delta-sigma modulation / dither using the lost 172 | * lower 8-bits of the 16-bit audio samples. Use a low pass 173 | * filter on the output to reconstruct the full signal */ 174 | 175 | buf[c][i] = (rf->audio[c] & 0xFE00) >> 8; 176 | rf->err[c] += rf->audio[c] & 0x1FF; 177 | if(rf->err[c] >= 0x1FF) 178 | { 179 | buf[c][i]++; 180 | rf->err[c] -= 0x1FF; 181 | } 182 | } 183 | } 184 | 185 | fifo_write(&rf->buffer[1], i); 186 | fifo_write(&rf->buffer[2], i); 187 | } 188 | 189 | return(r >= 0 ? RF_OK : RF_ERROR); 190 | } 191 | 192 | static int _rf_write_audio_spdif(void *private, const int16_t *audio, size_t samples) 193 | { 194 | fl2k_t *rf = private; 195 | uint8_t *buf; 196 | uint8_t block[SPDIF_BLOCK_BYTES]; 197 | int16_t block_s[SPDIF_BLOCK_BITS * 5]; 198 | int r, i; 199 | int16_t s; 200 | 201 | if(audio == NULL) return(RF_OK); 202 | 203 | r = 0; 204 | 205 | while(samples > 0) 206 | { 207 | /* Copy audio PCM samples */ 208 | r = SPDIF_BLOCK_SAMPLES - rf->pcm_len; 209 | if(r > samples) r = samples; 210 | 211 | memcpy(&rf->pcm[rf->pcm_len], audio, r * sizeof(int16_t)); 212 | audio += r; 213 | rf->pcm_len += r; 214 | samples -= r; 215 | 216 | /* Incomplete PCM block, return for more */ 217 | if(rf->pcm_len < SPDIF_BLOCK_SAMPLES) return(RF_OK); 218 | 219 | /* Encode the SPDIF block (32000 kHz, 4096000 bit/s) */ 220 | spdif_block(block, rf->pcm); 221 | rf->pcm_len = 0; 222 | 223 | for(r = 0; r < SPDIF_BLOCK_BITS * 5; r++) 224 | { 225 | i = r / 5; 226 | block_s[r] = (block[i >> 3] >> (7 - (i & 7))) & 1 ? 23405 : -23405; 227 | } 228 | 229 | fir_int16_feed(&rf->spdif_resampler, block_s, r, 1); 230 | 231 | /* Feed the output of the resampler into the FIFO */ 232 | do 233 | { 234 | r = fifo_write_ptr(&rf->buffer[2], (void **) &buf, 1); 235 | if(r < 0) break; 236 | 237 | r = fir_int16_process(&rf->spdif_resampler, &s, 1, 1); 238 | if(r <= 0) break; 239 | 240 | buf[0] = (s - INT16_MIN) >> 8; 241 | 242 | fifo_write(&rf->buffer[2], 1); 243 | } 244 | while(r > 0); 245 | } 246 | 247 | return(r >= 0 ? RF_OK : RF_ERROR); 248 | } 249 | 250 | static int _rf_close(void *private) 251 | { 252 | fl2k_t *rf = private; 253 | int i; 254 | 255 | rf->abort = 1; 256 | 257 | fl2k_stop_tx(rf->d); 258 | fl2k_close(rf->d); 259 | 260 | for(i = 0; i < 3; i++) 261 | { 262 | fifo_reader_close(&rf->reader[i]); 263 | } 264 | 265 | for(i = 0; i < 3; i++) 266 | { 267 | fifo_free(&rf->buffer[i]); 268 | } 269 | 270 | if(rf->audio_mode == FL2K_AUDIO_SPDIF) 271 | { 272 | fir_int16_free(&rf->spdif_resampler); 273 | } 274 | 275 | free(rf); 276 | 277 | return(RF_OK); 278 | } 279 | 280 | int rf_fl2k_open(rf_t *s, const char *device, unsigned int sample_rate, int baseband, int audio_mode) 281 | { 282 | fl2k_t *rf; 283 | int r; 284 | 285 | rf = calloc(1, sizeof(fl2k_t)); 286 | if(!rf) 287 | { 288 | return(RF_OUT_OF_MEMORY); 289 | } 290 | 291 | rf->abort = 0; 292 | rf->sample_rate = sample_rate; 293 | rf->baseband = baseband ? 1 : 0; 294 | rf->audio_mode = audio_mode; 295 | 296 | r = device ? atoi(device) : 0; 297 | 298 | fl2k_open(&rf->d, r); 299 | if(rf->d == NULL) 300 | { 301 | fprintf(stderr, "fl2k_open() failed to open device #%d.\n", r); 302 | _rf_close(rf); 303 | return(RF_ERROR); 304 | } 305 | 306 | /* Red channel is composite video / in-phase complex component */ 307 | fifo_init(&rf->buffer[0], BUFFERS, FL2K_BUF_LEN); 308 | fifo_reader_init(&rf->reader[0], &rf->buffer[0], -1); 309 | 310 | if(!rf->baseband) 311 | { 312 | /* Green channel is chrominance / quadrature complex component */ 313 | fifo_init(&rf->buffer[1], BUFFERS, FL2K_BUF_LEN); 314 | fifo_reader_init(&rf->reader[1], &rf->buffer[1], 0); 315 | } 316 | 317 | if(audio_mode == FL2K_AUDIO_STEREO) 318 | { 319 | if(!rf->baseband) 320 | { 321 | fprintf(stderr, "fl2k: Stereo audio is not available with S-Video or complex modes\n"); 322 | _rf_close(rf); 323 | return(RF_ERROR); 324 | } 325 | 326 | rf->interp = 0; 327 | 328 | /* Green channel is left audio */ 329 | fifo_init(&rf->buffer[1], BUFFERS, FL2K_BUF_LEN); 330 | fifo_reader_init(&rf->reader[1], &rf->buffer[1], 0); 331 | 332 | /* Blue channel is right audio */ 333 | fifo_init(&rf->buffer[2], BUFFERS, FL2K_BUF_LEN); 334 | fifo_reader_init(&rf->reader[2], &rf->buffer[2], 0); 335 | 336 | /* Register the callback */ 337 | s->write_audio = _rf_write_audio; 338 | } 339 | else if(audio_mode == FL2K_AUDIO_SPDIF) 340 | { 341 | /* SPDIF init */ 342 | rf->pcm_len = 0; 343 | r = fir_int16_resampler_init( 344 | &rf->spdif_resampler, 345 | (r64_t) { rf->sample_rate, 1 }, 346 | (r64_t) { spdif_bitrate(32000) * 5, 1 } 347 | ); 348 | 349 | /* Blue channel is S/PDIF digital audio */ 350 | fifo_init(&rf->buffer[2], BUFFERS, FL2K_BUF_LEN); 351 | fifo_reader_init(&rf->reader[2], &rf->buffer[2], 0); 352 | 353 | /* Register the callback */ 354 | s->write_audio = _rf_write_audio_spdif; 355 | } 356 | 357 | rf->phase = 0; 358 | 359 | r = fl2k_start_tx(rf->d, _callback, rf, 0); 360 | if(r < 0) 361 | { 362 | fprintf(stderr, "fl2k_start_tx() failed: %d\n", r); 363 | _rf_close(rf); 364 | return(RF_ERROR); 365 | } 366 | 367 | r = fl2k_set_sample_rate(rf->d, rf->sample_rate); 368 | if(r < 0) 369 | { 370 | fprintf(stderr, "fl2k_set_sample_rate() failed: %d\n", r); 371 | _rf_close(rf); 372 | return(RF_ERROR); 373 | } 374 | 375 | /* Read back the actual frequency */ 376 | r = fl2k_get_sample_rate(rf->d); 377 | if(r != rf->sample_rate) 378 | { 379 | //fprintf(stderr, "fl2k sample rate changed from %d > %d\n", s->vid.sample_rate, r); 380 | //_rf_close(rf); 381 | //return(RF_ERROR); 382 | } 383 | 384 | /* Register the callback functions */ 385 | s->ctx = rf; 386 | s->write = _rf_write; 387 | s->close = _rf_close; 388 | 389 | return(RF_OK); 390 | } 391 | 392 | -------------------------------------------------------------------------------- /src/rf_fl2k.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2019 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _FL2K_H 19 | #define _FL2K_H 20 | 21 | #define FL2K_AUDIO_NONE 0 22 | #define FL2K_AUDIO_MONO 1 23 | #define FL2K_AUDIO_STEREO 2 24 | #define FL2K_AUDIO_SPDIF 3 25 | 26 | extern int rf_fl2k_open(rf_t *s, const char *device, unsigned int sample_rate, int baseband, int audio_mode); 27 | 28 | #endif 29 | 30 | -------------------------------------------------------------------------------- /src/rf_hackrf.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _HACKRF_H 19 | #define _HACKRF_H 20 | 21 | extern int rf_hackrf_open(rf_t *s, const char *serial, uint32_t sample_rate, uint64_t frequency_hz, unsigned int txvga_gain, unsigned char amp_enable, unsigned char baseband); 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /src/rf_soapysdr.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "rf.h" 26 | 27 | #define BUF_LEN 4096 28 | 29 | typedef struct { 30 | 31 | /* SoapySDR device and stream */ 32 | SoapySDRDevice *d; 33 | SoapySDRStream *s; 34 | 35 | int scale; 36 | int16_t txbuf[BUF_LEN * 2]; 37 | 38 | } soapysdr_t; 39 | 40 | static int _rf_write(void *private, const int16_t *iq_data, size_t samples) 41 | { 42 | soapysdr_t *rf = private; 43 | const void *buffs[1]; 44 | int flags = 0; 45 | int l; 46 | int r; 47 | 48 | while(samples > 0) 49 | { 50 | if(rf->scale) 51 | { 52 | buffs[0] = rf->txbuf; 53 | l = (samples > BUF_LEN ? BUF_LEN : samples); 54 | 55 | for(r = 0; r < 2 * l; r++) 56 | { 57 | rf->txbuf[r] = iq_data[r] * rf->scale / INT16_MAX; 58 | } 59 | } 60 | else 61 | { 62 | buffs[0] = iq_data; 63 | l = samples; 64 | } 65 | 66 | samples -= l; 67 | iq_data += l * 2; 68 | 69 | while(l > 0) 70 | { 71 | r = SoapySDRDevice_writeStream(rf->d, rf->s, buffs, l, &flags, 0, 100000); 72 | 73 | if(r <= 0) 74 | { 75 | return(RF_ERROR); 76 | } 77 | 78 | l -= r; 79 | buffs[0] = (int16_t *) buffs[0] + r * 2; 80 | } 81 | } 82 | 83 | return(RF_OK); 84 | } 85 | 86 | static int _rf_close(void *private) 87 | { 88 | soapysdr_t *rf = private; 89 | 90 | SoapySDRDevice_deactivateStream(rf->d, rf->s, 0, 0); 91 | SoapySDRDevice_closeStream(rf->d, rf->s); 92 | 93 | SoapySDRDevice_unmake(rf->d); 94 | 95 | return(RF_OK); 96 | } 97 | 98 | int rf_soapysdr_open(rf_t *s, const char *device, unsigned int sample_rate, unsigned int frequency_hz, unsigned int gain, const char *antenna) 99 | { 100 | soapysdr_t *rf; 101 | SoapySDRKwargs *results; 102 | size_t length; 103 | char *sn; 104 | double fullscale; 105 | 106 | rf = calloc(1, sizeof(soapysdr_t)); 107 | if(!rf) 108 | { 109 | return(RF_OUT_OF_MEMORY); 110 | } 111 | 112 | /* Display a list of devices */ 113 | results = SoapySDRDevice_enumerate(NULL, &length); 114 | 115 | if(length <= 0) 116 | { 117 | fprintf(stderr, "No SoapySDR devices found.\n"); 118 | free(rf); 119 | return(RF_ERROR); 120 | } 121 | 122 | /*for(i = 0; i < length; i++) 123 | { 124 | fprintf(stderr, "Found device #%ld: ", i); 125 | 126 | for(j = 0; j < results[i].size; j++) 127 | { 128 | fprintf(stderr, "%s=%s, ", results[i].keys[j], results[i].vals[j]); 129 | } 130 | 131 | fprintf(stderr, "\n"); 132 | }*/ 133 | 134 | SoapySDRKwargsList_clear(results, length); 135 | 136 | /* Prepare the device for output */ 137 | rf->d = SoapySDRDevice_makeStrArgs(device); 138 | 139 | if(rf->d == NULL) 140 | { 141 | fprintf(stderr, "SoapySDRDevice_make() failed: %s\n", SoapySDRDevice_lastError()); 142 | free(rf); 143 | return(RF_ERROR); 144 | } 145 | 146 | if(SoapySDRDevice_setSampleRate(rf->d, SOAPY_SDR_TX, 0, sample_rate) != 0) 147 | { 148 | fprintf(stderr, "SoapySDRDevice_setSampleRate() failed: %s\n", SoapySDRDevice_lastError()); 149 | free(rf); 150 | return(RF_ERROR); 151 | } 152 | 153 | if(SoapySDRDevice_setFrequency(rf->d, SOAPY_SDR_TX, 0, frequency_hz, NULL) != 0) 154 | { 155 | fprintf(stderr, "SoapySDRDevice_setFrequency() failed: %s\n", SoapySDRDevice_lastError()); 156 | free(rf); 157 | return(RF_ERROR); 158 | } 159 | 160 | if(SoapySDRDevice_setGain(rf->d, SOAPY_SDR_TX, 0, gain) != 0) 161 | { 162 | fprintf(stderr, "SoapySDRDevice_setGain() failed: %s\n", SoapySDRDevice_lastError()); 163 | free(rf); 164 | return(RF_ERROR); 165 | } 166 | 167 | if(antenna && SoapySDRDevice_setAntenna(rf->d, SOAPY_SDR_TX, 0, antenna) != 0) 168 | { 169 | fprintf(stderr, "SoapySDRDevice_setAntenna() failed: %s\n", SoapySDRDevice_lastError()); 170 | free(rf); 171 | return(RF_ERROR); 172 | } 173 | 174 | /* Query the native stream format, see if we need to scale the output */ 175 | sn = SoapySDRDevice_getNativeStreamFormat(rf->d, SOAPY_SDR_TX, 0, &fullscale); 176 | if(sn && strcmp(sn, "CS16") == 0) 177 | { 178 | rf->scale = fullscale; 179 | 180 | /* Always use an odd value (eg. 2048 gets adjusted to 2047) */ 181 | if((rf->scale & 1) == 0) 182 | { 183 | rf->scale--; 184 | } 185 | 186 | /* No scaling necessary if the full scale is accepted */ 187 | if(rf->scale < 0 || rf->scale >= INT16_MAX) 188 | { 189 | rf->scale = 0; 190 | } 191 | } 192 | 193 | #if defined(SOAPY_SDR_API_VERSION) && (SOAPY_SDR_API_VERSION >= 0x00080000) 194 | rf->s = SoapySDRDevice_setupStream(rf->d, SOAPY_SDR_TX, "CS16", NULL, 0, NULL); 195 | if(rf->s == NULL) 196 | #else 197 | if(SoapySDRDevice_setupStream(rf->d, &rf->s, SOAPY_SDR_TX, SOAPY_SDR_CS16, NULL, 0, NULL) != 0) 198 | #endif 199 | { 200 | fprintf(stderr, "SoapySDRDevice_setupStream() failed: %s\n", SoapySDRDevice_lastError()); 201 | free(rf); 202 | return(RF_ERROR); 203 | } 204 | 205 | SoapySDRDevice_activateStream(rf->d, rf->s, 0, 0, 0); 206 | 207 | /* Register the callback functions */ 208 | s->ctx = rf; 209 | s->write = _rf_write; 210 | s->close = _rf_close; 211 | 212 | return(RF_OK); 213 | } 214 | 215 | -------------------------------------------------------------------------------- /src/rf_soapysdr.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _SOAPYSDR_H 19 | #define _SOAPYSDR_H 20 | 21 | extern int rf_soapysdr_open(rf_t *s, const char *device, unsigned int sample_rate, unsigned int frequency_hz, unsigned int gain, const char *antenna); 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /src/sis.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2024 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | /* -=== SiS (Sound-in-Syncs) encoder ===- */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include "video.h" 24 | #include "vbidata.h" 25 | 26 | static double _raised_cosine(double x) 27 | { 28 | if(x <= -1 || x >= 1) return(0); 29 | return((1.0 + cos(M_PI * x)) / 2); 30 | } 31 | 32 | static int __init_quits(vbidata_lut_t *lut, unsigned int nsymbols, unsigned int dwidth, int level, double bwidth, double offset) 33 | { 34 | int l; 35 | int b, x; 36 | int levels[2]; 37 | vbidata_lut_t lc; 38 | vbidata_lut_t *lptr = (lut ? lut : &lc); 39 | 40 | nsymbols *= 2; 41 | 42 | levels[0] = level / 2 / 0.75; 43 | levels[1] = level / 4 / 0.75; 44 | 45 | l = 0; 46 | 47 | for(b = 0; b < nsymbols; b++) 48 | { 49 | double t = -bwidth * (b / 2) - offset; 50 | 51 | lptr->offset = lptr->length = 0; 52 | 53 | for(x = 0; x < dwidth; x++) 54 | { 55 | double h = _raised_cosine((t + x) / bwidth) * levels[b & 1]; 56 | vbidata_update(lptr, lut ? 1 : 0, x, round(h)); 57 | } 58 | 59 | l += 2 + lptr->length; 60 | 61 | if(lut) 62 | { 63 | lptr = (vbidata_lut_t *) &lptr->value[lptr->length]; 64 | } 65 | } 66 | 67 | /* End of LUT marker */ 68 | if(lut) 69 | { 70 | lptr->length = -1; 71 | } 72 | 73 | l++; 74 | 75 | return(l * sizeof(int16_t)); 76 | } 77 | 78 | vbidata_lut_t *_init_quits(unsigned int nsymbols, unsigned int dwidth, int level, double bwidth, double offset) 79 | { 80 | int l; 81 | vbidata_lut_t *lut; 82 | 83 | /* Calculate the length of the lookup-table and allocate memory */ 84 | l = __init_quits(NULL, nsymbols, dwidth, level, bwidth, offset); 85 | 86 | lut = malloc(l); 87 | if(!lut) 88 | { 89 | return(NULL); 90 | } 91 | 92 | /* Generate the lookup-table and return */ 93 | __init_quits(lut, nsymbols, dwidth, level, bwidth, offset); 94 | 95 | return(lut); 96 | } 97 | 98 | int sis_init(sis_t *s, const char *sismode, vid_t *vid, uint8_t mode, uint8_t reserve) 99 | { 100 | double left, rise, width; 101 | int i; 102 | 103 | memset(s, 0, sizeof(sis_t)); 104 | 105 | if(strcmp(sismode, "dcsis") == 0) 106 | { 107 | /* Nothing yet */ 108 | } 109 | else 110 | { 111 | fprintf(stderr, "Unrecognised SiS mode '%s'.\n", sismode); 112 | return(VID_ERROR); 113 | } 114 | 115 | /* Render the "quits" - the 4-level symbols */ 116 | s->lut = _init_quits( 117 | 25, vid->width, 118 | round((vid->white_level - vid->black_level)), 119 | (double) vid->width / 382, 120 | (double) vid->width / 382 * 3.32 /* Measured */ 121 | ); 122 | if(!s->lut) 123 | { 124 | return(VID_OUT_OF_MEMORY); 125 | } 126 | 127 | /* Render the blank window - timings measured from captures */ 128 | left = 0.2e-6; 129 | rise = 80e-9; 130 | width = 4.56e-6; 131 | s->blank_left = floor(vid->pixel_rate * (left - rise / 2)); 132 | s->blank_width = ceil(vid->pixel_rate * (width + rise)); 133 | s->blank_win = malloc(s->blank_width * sizeof(int16_t)); 134 | s->blank_level = vid->sync_level; /* Blank to the sync level */ 135 | if(!s->blank_win) 136 | { 137 | free(s->lut); 138 | return(VID_OUT_OF_MEMORY); 139 | } 140 | 141 | for(i = s->blank_left; i < s->blank_left + s->blank_width; i++) 142 | { 143 | double t = 1.0 / vid->pixel_rate * i; 144 | s->blank_win[i - s->blank_left] = round(rc_window(t, left, width, rise) * INT16_MAX); 145 | } 146 | 147 | /* Init the NICAM encoder */ 148 | nicam_encode_init(&s->nicam, mode, reserve); 149 | 150 | return(VID_OK); 151 | } 152 | 153 | void sis_free(sis_t *s) 154 | { 155 | if(!s) return; 156 | 157 | free(s->blank_win); 158 | free(s->lut); 159 | 160 | memset(s, 0, sizeof(sis_t)); 161 | } 162 | 163 | int sis_render(vid_t *s, void *arg, int nlines, vid_line_t **lines) 164 | { 165 | sis_t *sis = arg; 166 | vid_line_t *l = lines[0]; 167 | uint8_t gc[2][4] = { { 3, 0, 2, 1 }, { 0, 3, 1, 2 } }; 168 | uint8_t vbi[7]; 169 | int x, nb; 170 | 171 | /* Rate limit by varying the length of the data burst (nb) */ 172 | nb = 50; 173 | if((sis->re += 44) >= 125) 174 | { 175 | nb -= 4; 176 | sis->re -= 125; 177 | } 178 | 179 | memset(vbi, 0, 7); 180 | vbi[0] = 0xC0; 181 | 182 | for(x = 2; x < nb; x += 2, sis->frame_bit += 2) 183 | { 184 | uint8_t sym; 185 | 186 | if(sis->frame_bit >= NICAM_FRAME_BITS) 187 | { 188 | /* Encode the next frame */ 189 | nicam_encode_frame(&sis->nicam, sis->frame, sis->audio); 190 | sis->frame_bit = 0; 191 | } 192 | 193 | /* Read the next NICAM bit pair */ 194 | sym = (sis->frame[sis->frame_bit >> 3] >> (6 - (sis->frame_bit & 0x07))) & 0x03; 195 | 196 | /* Apply grey coding */ 197 | sym = gc[x & 4 ? 1 : 0][sym]; 198 | 199 | /* Push it into the data burst */ 200 | vbi[x >> 3] |= sym << (6 - (x & 0x07)); 201 | } 202 | 203 | /* Blank the data area */ 204 | for(x = sis->blank_left; x < sis->blank_left + sis->blank_width; x++) 205 | { 206 | l->output[x * 2] = (l->output[x * 2] * (INT16_MAX - sis->blank_win[x - sis->blank_left]) 207 | + sis->blank_level * sis->blank_win[x - sis->blank_left]) >> 15; 208 | } 209 | 210 | vbidata_render(sis->lut, vbi, 50 - nb, nb, VBIDATA_MSB_FIRST, l); 211 | 212 | return(1); 213 | } 214 | 215 | int sis_write_audio(sis_t *s, const int16_t *audio) 216 | { 217 | memcpy(s->audio, audio, sizeof(int16_t) * NICAM_AUDIO_LEN * 2); 218 | return(VID_OK); 219 | } 220 | 221 | -------------------------------------------------------------------------------- /src/sis.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2024 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _SIS_H 19 | #define _SIS_H 20 | 21 | typedef struct { 22 | vbidata_lut_t *lut; 23 | int16_t audio[NICAM_AUDIO_LEN * 2]; 24 | nicam_enc_t nicam; 25 | uint8_t frame[NICAM_FRAME_BYTES]; 26 | int frame_bit; 27 | int re; 28 | 29 | int blank_left; 30 | int blank_width; 31 | int16_t *blank_win; 32 | int16_t blank_level; 33 | 34 | } sis_t; 35 | 36 | extern int sis_init(sis_t *s, const char *sismode, vid_t *vid, uint8_t mode, uint8_t reserve); 37 | extern void sis_free(sis_t *s); 38 | extern int sis_render(vid_t *s, void *arg, int nlines, vid_line_t **lines); 39 | 40 | extern int sis_write_audio(sis_t *s, const int16_t *audio); 41 | 42 | #endif 43 | 44 | -------------------------------------------------------------------------------- /src/spdif.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2025 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include "spdif.h" 21 | 22 | uint32_t spdif_bitrate(uint32_t sample_rate) 23 | { 24 | return(sample_rate * 128); 25 | } 26 | 27 | static void _spdif_subframe(uint8_t out[8], int sample, uint8_t aux, int16_t pcm, uint8_t v, uint8_t u, uint8_t c) 28 | { 29 | uint32_t sf; 30 | int i, p; 31 | 32 | /* Build the subframe */ 33 | sf = (aux & 0xF) << 4; /* 4-bit Aux */ 34 | sf |= (pcm & 0xFFFF) << 12; /* 16-bit PCM */ 35 | sf |= (v & 0x1) << 28; /* Validity bit */ 36 | sf |= (u & 0x1) << 29; /* User data bit */ 37 | sf |= (c & 0x1) << 30; /* Channel status bit */ 38 | 39 | /* Calculate the parity bit */ 40 | for(i = 0; i < 31; i++) 41 | { 42 | sf ^= ((sf >> i) & 1) << 31; 43 | } 44 | 45 | /* Generate biphase bitstream (MSB first) */ 46 | memset(out, 0, 8); 47 | out[0] = (sample & 1 ? 0xE4 : (sample ? 0xE2 : 0xE8)); 48 | for(p = 1, i = 4; i < 32; i++) 49 | { 50 | out[i >> 2] |= p << (7 - ((i & 3) << 1)); 51 | p ^= (sf >> i) & 1; 52 | 53 | out[i >> 2] |= p << (6 - ((i & 3) << 1)); 54 | p ^= 1; 55 | } 56 | } 57 | 58 | void spdif_block(uint8_t b[SPDIF_BLOCK_BYTES], const int16_t pcm[SPDIF_BLOCK_SAMPLES]) 59 | { 60 | uint8_t cs[24]; 61 | int i; 62 | 63 | memset(cs, 0, 24); 64 | cs[0] |= 0 << 7; /* Consumer (S/PDIF) */ 65 | cs[0] |= 0 << 6; /* Normal */ 66 | cs[0] |= 1 << 5; /* Copy permit */ 67 | cs[0] |= 0 << 4; /* 2 channels */ 68 | cs[0] |= 0 << 2; /* No pre-emphasis */ 69 | 70 | for(i = 0; i < SPDIF_BLOCK_SAMPLES; i++, b += 8) 71 | { 72 | _spdif_subframe( 73 | b, 74 | i, 75 | 0, 76 | *(pcm++), 77 | 0, 78 | 0, 79 | (cs[i >> 4] >> (7 - ((i >> 1) & 7))) & 1 80 | ); 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/spdif.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2025 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _SPDIF_H 19 | #define _SPDIF_H 20 | 21 | #include 22 | 23 | #define SPDIF_BLOCK_SAMPLES (192 * 2) 24 | #define SPDIF_BLOCK_BYTES (SPDIF_BLOCK_SAMPLES * 8) 25 | #define SPDIF_BLOCK_BITS (SPDIF_BLOCK_BYTES * 8) 26 | 27 | extern uint32_t spdif_bitrate(uint32_t sample_rate); 28 | extern void spdif_block(uint8_t b[SPDIF_BLOCK_BYTES], const int16_t pcm[SPDIF_BLOCK_SAMPLES]); 29 | 30 | #endif 31 | 32 | -------------------------------------------------------------------------------- /src/syster.h: -------------------------------------------------------------------------------- 1 | /* Nagravision Syster encoder for hacktv */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Alex L. James */ 4 | /* Copyright 2018 Philip Heron */ 5 | /* */ 6 | /* This program is free software: you can redistribute it and/or modify */ 7 | /* it under the terms of the GNU General Public License as published by */ 8 | /* the Free Software Foundation, either version 3 of the License, or */ 9 | /* (at your option) any later version. */ 10 | /* */ 11 | /* This program is distributed in the hope that it will be useful, */ 12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 14 | /* GNU General Public License for more details. */ 15 | /* */ 16 | /* You should have received a copy of the GNU General Public License */ 17 | /* along with this program. If not, see . */ 18 | 19 | #ifndef _SYSTER_H 20 | #define _SYSTER_H 21 | 22 | #include 23 | #include "video.h" 24 | #include "vbidata.h" 25 | 26 | #define NG_VBI_WIDTH 284 27 | #define NG_VBI_BYTES 28 28 | 29 | #define NG_MSG_BYTES 84 30 | 31 | #define NG_FIELD_1_START 23 32 | #define NG_FIELD_2_START 336 33 | #define NG_LINES_PER_FIELD 287 34 | 35 | /* NG_DELAY_LINES needs to be long enough for the scrambler to access any 36 | * line in the next field from at least the last 32 lines of the current. 37 | * This is a safe amount and can probably be reduced. */ 38 | 39 | #define NG_DELAY_LINES (625 + NG_FIELD_1_START + NG_LINES_PER_FIELD - (NG_FIELD_2_START + NG_LINES_PER_FIELD - 32)) 40 | 41 | /* Entitlement control messages */ 42 | typedef struct { 43 | uint64_t cw; 44 | uint8_t ecm[16]; 45 | } ng_ecm_t; 46 | 47 | typedef struct { 48 | 49 | /* VBI */ 50 | vbidata_lut_t *lut; 51 | uint8_t vbi[10][NG_VBI_BYTES]; 52 | int vbi_seq; 53 | int block_seq; 54 | 55 | /* EMM */ 56 | int next_ppua; 57 | 58 | /* PRBS state */ 59 | uint64_t cw; 60 | uint32_t sr1; 61 | uint32_t sr2; 62 | 63 | /* PRNG seed values */ 64 | int s; /* 0, ..., 127 */ 65 | int r; /* 0, ..., 255 */ 66 | 67 | /* The line order for the next field (0-287) */ 68 | int order[NG_LINES_PER_FIELD]; 69 | 70 | /* Audio inversion FIR filter */ 71 | int16_t *firli, *firlq; /* Left channel, I + Q */ 72 | int16_t *firri, *firrq; /* Right channel, I + Q */ 73 | int mixx; 74 | int firx; 75 | 76 | } ng_t; 77 | 78 | extern int ng_init(ng_t *s, vid_t *vs); 79 | extern void ng_free(ng_t *s); 80 | extern void ng_invert_audio(ng_t *s, int16_t *audio, size_t samples); 81 | extern int ng_render_line(vid_t *s, void *arg, int nlines, vid_line_t **lines); 82 | 83 | #endif 84 | 85 | -------------------------------------------------------------------------------- /src/teletext.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _TELETEXT_H 19 | #define _TELETEXT_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include "video.h" 25 | #include "vbidata.h" 26 | 27 | #define TT_OK 0 28 | #define TT_ERROR 1 29 | #define TT_NO_PACKET 2 30 | #define TT_OUT_OF_MEMORY 3 31 | 32 | typedef struct _tt_page_t { 33 | 34 | /* The page number, 0x100 - 0x8FF */ 35 | uint16_t page; 36 | 37 | /* Subpage number: 0x00 - 0xFF */ 38 | uint8_t subpage; 39 | 40 | /* Subcode: 0x0000 - 0x3F7F */ 41 | uint16_t subcode; 42 | 43 | /* Page status 0x0000 - 0xFFFF */ 44 | uint16_t page_status; 45 | 46 | /* Cycle mode / time */ 47 | int cycle_mode; /* 0 = Seconds, 1 = Cycles */ 48 | int cycle_time; /* Seconds / cycles until next subpage */ 49 | int cycle_count; /* Cycle counter */ 50 | 51 | /* Fastext links */ 52 | int links[6]; 53 | 54 | /* Flag to transmit the erasure code, only done on a new subpage */ 55 | int erase; /* 0 = Don't erase, 1 = Erase */ 56 | 57 | /* The number of packets that make up the page, 58 | * not including the header */ 59 | int packets; 60 | 61 | /* The number of packets that can be transmitted 62 | * within 20ms of the header packet */ 63 | int nodelay_packets; 64 | 65 | /* Pointer to the packets that make up this 66 | * page. Each packet is 45 bytes long and 67 | * represents the full VBI line. */ 68 | uint8_t *data; 69 | 70 | /* A pointer to the first subpage */ 71 | struct _tt_page_t *subpages; 72 | 73 | /* A pointer to the next subpage */ 74 | struct _tt_page_t *next_subpage; 75 | 76 | /* A pointer to the next page */ 77 | struct _tt_page_t *next; 78 | 79 | } tt_page_t; 80 | 81 | typedef struct { 82 | 83 | /* The magazine number, 1-8 */ 84 | int magazine; 85 | 86 | /* Set to 1 if the next magazine packet has to 87 | * to be a header filler packet */ 88 | int filler; 89 | 90 | /* A pointer to the first page */ 91 | tt_page_t *pages; 92 | 93 | /* A pointer to the currently active page */ 94 | tt_page_t *page; 95 | 96 | /* The currently active row */ 97 | int row; 98 | 99 | /* Timecode to resume sending display packets */ 100 | int delay; 101 | 102 | } tt_magazine_t; 103 | 104 | typedef struct { 105 | 106 | /* The current timestamp to use for the clock */ 107 | time_t timestamp; 108 | 109 | /* The number of ticks that represent 20ms. This is 110 | * used to enforce a minimum time between header 111 | * packets and displayable packets of the same page. 112 | * Sometimes referred to as the 20ms rule, page 113 | * erasure interval or page clearing interval. */ 114 | unsigned int header_delay; 115 | 116 | /* The number of clock ticks that represent 1s. 117 | * This is used to determine when to send the next 118 | * 8/30 packet, containing the updated time */ 119 | unsigned int second_delay; 120 | 121 | /* The currently active magazine */ 122 | unsigned int magazine; 123 | 124 | /* The available magazines */ 125 | tt_magazine_t magazines[8]; 126 | 127 | } tt_service_t; 128 | 129 | typedef struct { 130 | vid_t *vid; 131 | vbidata_lut_t *lut; 132 | FILE *raw; 133 | tt_service_t service; 134 | unsigned int timecode; 135 | } tt_t; 136 | 137 | extern int tt_init(tt_t *s, vid_t *vid, char *path); 138 | extern void tt_free(tt_t *s); 139 | extern int tt_next_packet(tt_t *s, uint8_t vbi[45], int frame, int line); 140 | extern int tt_render_line(vid_t *s, void *arg, int nlines, vid_line_t **lines); 141 | 142 | #endif 143 | 144 | -------------------------------------------------------------------------------- /src/vbidata.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "vbidata.h" 23 | #include "common.h" 24 | 25 | static double _sinc(double x) 26 | { 27 | return(sin(M_PI * x) / (M_PI * x)); 28 | } 29 | 30 | static double _raised_cosine(double x, double b, double t) 31 | { 32 | if(x == 0) return(1.0); 33 | return(_sinc(x / t) * (cos(M_PI * b * x / t) / (1.0 - (4.0 * b * b * x * x / (t * t))))); 34 | } 35 | 36 | void vbidata_update(vbidata_lut_t *lut, int render, int offset, int value) 37 | { 38 | if(value != 0) 39 | { 40 | if(lut->length == 0) 41 | { 42 | lut->offset = offset; 43 | } 44 | 45 | for(; lut->length < (offset - lut->offset); lut->length++) 46 | { 47 | if(render) 48 | { 49 | lut->value[lut->length] = 0; 50 | } 51 | } 52 | 53 | if(render) 54 | { 55 | lut->value[lut->length] = value; 56 | } 57 | 58 | lut->length++; 59 | } 60 | } 61 | 62 | int vbidata_update_step(vbidata_lut_t *lut, double offset, double width, double rise, int level) 63 | { 64 | int x1, x2; 65 | vbidata_lut_t lc; 66 | vbidata_lut_t *lptr = (lut ? lut : &lc); 67 | 68 | x1 = floor(offset - rise / 2); 69 | x2 = ceil(offset + width + rise / 2); 70 | 71 | lptr->length = 0; 72 | lptr->offset = 0; 73 | 74 | for(; x1 <= x2; x1++) 75 | { 76 | double h = rc_window(x1, offset, width, rise) * level; 77 | vbidata_update(lptr, lut ? 1 : 0, x1, round(h)); 78 | } 79 | 80 | return(2 + lptr->length); 81 | } 82 | 83 | static int _vbidata_init(vbidata_lut_t *lut, unsigned int nsymbols, unsigned int dwidth, int level, int filter, double bwidth, double beta, double offset) 84 | { 85 | int l; 86 | int b, x; 87 | vbidata_lut_t lc; 88 | vbidata_lut_t *lptr = (lut ? lut : &lc); 89 | 90 | l = 0; 91 | 92 | for(b = 0; b < nsymbols; b++) 93 | { 94 | double t = -bwidth * b - offset; 95 | 96 | lptr->offset = lptr->length = 0; 97 | 98 | for(x = 0; x < dwidth; x++) 99 | { 100 | double h = _raised_cosine((t + x) / bwidth, beta, 1) * level; 101 | vbidata_update(lptr, lut ? 1 : 0, x, round(h)); 102 | } 103 | 104 | l += 2 + lptr->length; 105 | 106 | if(lut) 107 | { 108 | lptr = (vbidata_lut_t *) &lptr->value[lptr->length]; 109 | } 110 | } 111 | 112 | /* End of LUT marker */ 113 | if(lut) 114 | { 115 | lptr->length = -1; 116 | } 117 | 118 | l++; 119 | 120 | return(l * sizeof(int16_t)); 121 | } 122 | 123 | vbidata_lut_t *vbidata_init(unsigned int nsymbols, unsigned int dwidth, int level, int filter, double bwidth, double beta, double offset) 124 | { 125 | int l; 126 | vbidata_lut_t *lut; 127 | 128 | /* Calculate the length of the lookup-table and allocate memory */ 129 | l = _vbidata_init(NULL, nsymbols, dwidth, level, filter, bwidth, beta, offset); 130 | 131 | lut = malloc(l); 132 | if(!lut) 133 | { 134 | return(NULL); 135 | } 136 | 137 | /* Generate the lookup-table and return */ 138 | _vbidata_init(lut, nsymbols, dwidth, level, filter, bwidth, beta, offset); 139 | 140 | return(lut); 141 | } 142 | 143 | static int _vbidata_init_step(vbidata_lut_t *lut, unsigned int nsymbols, unsigned int dwidth, int level, double width, double rise, double offset) 144 | { 145 | int l; 146 | int b; 147 | vbidata_lut_t *lptr = lut; 148 | 149 | l = 0; 150 | 151 | for(b = 0; b < nsymbols; b++, lptr = (vbidata_lut_t *) (lptr ? &lptr->value[lptr->length] : NULL)) 152 | { 153 | l += vbidata_update_step(lptr, offset + width * b, width, rise, level); 154 | } 155 | 156 | /* End of LUT marker */ 157 | if(lptr) 158 | { 159 | lptr->length = -1; 160 | } 161 | 162 | l++; 163 | 164 | return(l * sizeof(int16_t)); 165 | } 166 | 167 | vbidata_lut_t *vbidata_init_step(unsigned int nsymbols, unsigned int dwidth, int level, double width, double rise, double offset) 168 | { 169 | int l; 170 | vbidata_lut_t *lut; 171 | 172 | /* Calculate the length of the lookup-table and allocate memory */ 173 | l = _vbidata_init_step(NULL, nsymbols, dwidth, level, width, rise, offset); 174 | lut = malloc(l); 175 | if(!lut) 176 | { 177 | return(NULL); 178 | } 179 | 180 | /* Generate the lookup-table and return */ 181 | _vbidata_init_step(lut, nsymbols, dwidth, level, width, rise, offset); 182 | 183 | return(lut); 184 | } 185 | 186 | void vbidata_render(const vbidata_lut_t *lut, const uint8_t *src, int offset, int length, int order, vid_line_t *line) 187 | { 188 | int b = -offset; 189 | int x, lx; 190 | int bit; 191 | vid_line_t *l; 192 | 193 | /* LUT format: 194 | * 195 | * Array of int16's: 196 | * 197 | * [l][x][[v]...] = [length][x offset][[value]...] 198 | * [-1] = End of LUT 199 | */ 200 | 201 | for(; b < length && lut->length != -1; b++, lut = (vbidata_lut_t *) &lut->value[lut->length]) 202 | { 203 | bit = (b < 0 ? 0 : (src[b >> 3] >> (order == VBIDATA_LSB_FIRST ? (b & 7) : 7 - (b & 7))) & 1); 204 | 205 | if(bit) 206 | { 207 | x = 0; 208 | lx = lut->offset; 209 | l = line; 210 | 211 | /* Move to the previous line if the offset for this symbol is negative */ 212 | while(lx < 0 && l->width > 0) 213 | { 214 | l = l->previous; 215 | lx += l->width; 216 | } 217 | 218 | /* Lines with zero length mark a boundary we can't pass */ 219 | if(l->width == 0) 220 | { 221 | l = l->next; 222 | x = -lx; 223 | lx = 0; 224 | } 225 | 226 | /* Render the symbol - moving to the next line if necessary */ 227 | while(x < lut->length && l->width > 0) 228 | { 229 | for(; x < lut->length && lx < l->width; x++, lx++) 230 | { 231 | l->output[lx * 2] += lut->value[x]; 232 | } 233 | 234 | l = l->next; 235 | lx = 0; 236 | } 237 | } 238 | } 239 | } 240 | 241 | -------------------------------------------------------------------------------- /src/vbidata.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include "video.h" 19 | 20 | #ifndef _VBIDATA_H 21 | #define _VBIDATA_H 22 | 23 | #define VBIDATA_FILTER_RC (0) 24 | 25 | #define VBIDATA_LSB_FIRST (0) 26 | #define VBIDATA_MSB_FIRST (1) 27 | 28 | typedef struct { 29 | int16_t length; 30 | int16_t offset; 31 | int16_t value[]; 32 | } vbidata_lut_t; 33 | 34 | extern void vbidata_update(vbidata_lut_t *lut, int render, int offset, int value); 35 | extern int vbidata_update_step(vbidata_lut_t *lut, double offset, double width, double rise, int level); 36 | extern vbidata_lut_t *vbidata_init(unsigned int nsymbols, unsigned int dwidth, int level, int filter, double bwidth, double beta, double offset); 37 | extern vbidata_lut_t *vbidata_init_step(unsigned int nsymbols, unsigned int dwidth, int level, double width, double rise, double offset); 38 | extern void vbidata_render(const vbidata_lut_t *lut, const uint8_t *src, int offset, int length, int order, vid_line_t *line); 39 | 40 | #endif 41 | 42 | -------------------------------------------------------------------------------- /src/video.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _VIDEO_H 19 | #define _VIDEO_H 20 | 21 | #include 22 | #include "av.h" 23 | #include "nicam728.h" 24 | #include "dance.h" 25 | #include "fir.h" 26 | #include "fifo.h" 27 | 28 | typedef struct vid_line_t vid_line_t; 29 | typedef struct vid_t vid_t; 30 | 31 | #include "mac.h" 32 | #include "teletext.h" 33 | #include "wss.h" 34 | #include "videocrypt.h" 35 | #include "videocrypts.h" 36 | #include "syster.h" 37 | #include "acp.h" 38 | #include "vits.h" 39 | #include "vitc.h" 40 | #include "cc608.h" 41 | #include "vbidata.h" 42 | #include "sis.h" 43 | 44 | /* Return codes */ 45 | #define VID_OK 0 46 | #define VID_ERROR -1 47 | #define VID_OUT_OF_MEMORY -2 48 | 49 | /* Frame type */ 50 | #define VID_RASTER_625 0 51 | #define VID_RASTER_525 1 52 | #define VID_RASTER_405 2 53 | #define VID_RASTER_819 3 54 | #define VID_BAIRD_240 4 55 | #define VID_BAIRD_30 5 56 | #define VID_NBTV_32 6 57 | #define VID_APOLLO_320 7 58 | #define VID_MAC 8 59 | #define VID_CBS_405 9 60 | 61 | /* Frame orientation */ 62 | #define VID_ROTATE_0 (0 << 0) 63 | #define VID_ROTATE_90 (1 << 0) 64 | #define VID_ROTATE_180 (2 << 0) 65 | #define VID_ROTATE_270 (3 << 0) 66 | #define VID_HFLIP (1 << 2) 67 | #define VID_VFLIP (1 << 3) 68 | 69 | /* Output modulation types */ 70 | #define VID_NONE 0 71 | #define VID_AM 1 72 | #define VID_VSB 2 73 | #define VID_FM 3 74 | 75 | /* Colour modes */ 76 | #define VID_MONOCHROME 0 77 | #define VID_PAL 1 78 | #define VID_NTSC 2 79 | #define VID_SECAM 3 80 | #define VID_APOLLO_FSC 4 81 | #define VID_CBS_FSC 5 82 | 83 | /* Audio pre-emphasis modes */ 84 | //#define VID_NONE 0 85 | #define VID_50US 1 86 | #define VID_75US 2 87 | #define VID_J17 3 88 | 89 | /* RF modulation */ 90 | 91 | typedef struct { 92 | int16_t level; 93 | int32_t counter; 94 | cint32_t phase; 95 | cint32_t *lut; 96 | 97 | limiter_t limiter; 98 | int16_t sample; 99 | 100 | /* FM energy dispersal */ 101 | div_t ed_delta; 102 | div_t ed_counter; 103 | div_t ed_overflow; 104 | 105 | } _mod_fm_t; 106 | 107 | typedef struct { 108 | int16_t level; 109 | int32_t counter; 110 | cint32_t phase; 111 | cint32_t delta; 112 | 113 | int16_t sample; 114 | 115 | } _mod_am_t; 116 | 117 | typedef struct { 118 | int32_t counter; 119 | cint32_t phase; 120 | cint32_t delta; 121 | } _mod_offset_t; 122 | 123 | 124 | 125 | typedef struct { 126 | 127 | /* Output type */ 128 | int output_type; 129 | 130 | /* Output modulation */ 131 | int modulation; 132 | 133 | /* Video bandwidth options */ 134 | double video_bw; 135 | 136 | /* VSB modulation options */ 137 | double vsb_upper_bw; 138 | double vsb_lower_bw; 139 | 140 | /* FM modulation options */ 141 | double fm_level; 142 | double fm_deviation; 143 | double fm_energy_dispersal; 144 | 145 | /* Overall signal level (pre-modulation) */ 146 | double level; 147 | 148 | /* Swap the IQ in complex signals */ 149 | int swap_iq; 150 | 151 | /* Raw video baseband input */ 152 | char *raw_bb_file; 153 | int16_t raw_bb_blanking_level; 154 | int16_t raw_bb_white_level; 155 | 156 | /* Signal offset and passthru */ 157 | int64_t offset; 158 | char *passthru; 159 | 160 | /* Level of each component */ 161 | double video_level; 162 | double fm_mono_level; 163 | double fm_left_level; 164 | double fm_right_level; 165 | double am_audio_level; 166 | double nicam_level; 167 | double dance_level; 168 | 169 | /* Video */ 170 | int type; 171 | 172 | r64_t frame_rate; 173 | r64_t frame_aspects[2]; 174 | int frame_orientation; 175 | 176 | int lines; 177 | int hline; 178 | int interlaced; /* 0 = Non-interlaced, 1 = TFF, 2 = BFF */ 179 | int active_lines; 180 | 181 | int interlace; /* 0 = Update image per frame, 1 = per field */ 182 | 183 | double hsync_width; 184 | double vsync_short_width; 185 | double vsync_long_width; 186 | double sync_rise; /* The 10% - 90% rise time */ 187 | 188 | int invert_video; 189 | double white_level; 190 | double black_level; 191 | double blanking_level; 192 | double sync_level; 193 | 194 | double active_width; 195 | double active_left; 196 | 197 | double gamma; 198 | 199 | char *teletext; 200 | 201 | char *wss; 202 | 203 | char *videocrypt; 204 | char *videocrypt2; 205 | char *videocrypts; 206 | int syster; 207 | int showecm; 208 | int systeraudio; 209 | int acp; 210 | int vits; 211 | int vitc; 212 | int cc608; 213 | char *sis; 214 | char *eurocrypt; 215 | int ec_mat_rating; 216 | char *ec_ppv; 217 | int nodate; 218 | 219 | /* RGB weights, should add up to 1.0 */ 220 | double rw_co; 221 | double gw_co; 222 | double bw_co; 223 | 224 | int colour_mode; 225 | r64_t colour_carrier; 226 | double colour_bw; 227 | int s_video; 228 | 229 | double burst_width; 230 | double burst_left; 231 | double burst_level; 232 | double burst_rise; 233 | 234 | double fsc_flag_width; 235 | double fsc_flag_left; 236 | double fsc_flag_level; 237 | 238 | double ev_co; 239 | double eu_co; 240 | 241 | int secam_field_id; 242 | int secam_field_id_lines; 243 | 244 | /* Audio */ 245 | int volume; 246 | 247 | /* FM audio (Mono) */ 248 | double fm_mono_carrier; 249 | double fm_mono_deviation; 250 | int fm_mono_preemph; 251 | 252 | /* FM audio (Stereo Left) */ 253 | double fm_left_carrier; 254 | double fm_left_deviation; 255 | int fm_left_preemph; 256 | 257 | /* FM audio (Stereo Right) */ 258 | double fm_right_carrier; 259 | double fm_right_deviation; 260 | int fm_right_preemph; 261 | 262 | /* A2 Stereo / Zweikanalton */ 263 | int a2stereo; 264 | 265 | /* Stereo NICAM audio */ 266 | double nicam_carrier; 267 | double nicam_beta; 268 | 269 | /* DANCE audio */ 270 | double dance_carrier; 271 | double dance_beta; 272 | 273 | /* AM audio */ 274 | double am_mono_carrier; 275 | double am_mono_bandwidth; 276 | 277 | /* D/D2-MAC options */ 278 | int mac_mode; 279 | uint16_t chid; 280 | int mac_audio_stereo; 281 | int mac_audio_quality; 282 | int mac_audio_protection; 283 | int mac_audio_companded; 284 | int scramble_video; 285 | int scramble_audio; 286 | 287 | /* Video filter enable flag */ 288 | int vfilter; 289 | 290 | } vid_config_t; 291 | 292 | typedef struct { 293 | const char *id; 294 | const vid_config_t *conf; 295 | const char *desc; 296 | } vid_configs_t; 297 | 298 | typedef struct { 299 | int16_t y; 300 | int16_t u; 301 | int16_t v; 302 | } _yuv16_t; 303 | 304 | struct vid_line_t { 305 | 306 | /* The output line buffer */ 307 | int16_t *output; 308 | int width; 309 | 310 | /* Frame and line number */ 311 | int frame; 312 | int line; 313 | 314 | /* Colour subcarrier (complex) */ 315 | const cint16_t *lut; 316 | 317 | /* Status */ 318 | int vbialloc; 319 | 320 | /* Audio output */ 321 | const int16_t *audio; 322 | size_t audio_len; 323 | 324 | /* Pointer the previous and next line */ 325 | vid_line_t *previous; 326 | vid_line_t *next; 327 | }; 328 | 329 | /* Line process function prototypes */ 330 | typedef int (*vid_lineprocess_process_t)(vid_t *s, void *arg, int nlines, vid_line_t **lines); 331 | typedef void (*vid_lineprocess_free_t)(vid_t *s, void *arg); 332 | typedef struct _lineprocess_t _lineprocess_t; 333 | 334 | struct _lineprocess_t { 335 | 336 | /* A simple identifier for this process */ 337 | char name[16]; 338 | 339 | /* Line window */ 340 | int nlines; 341 | vid_line_t **lines; 342 | 343 | /* Process callbacks */ 344 | vid_lineprocess_process_t process; 345 | vid_lineprocess_free_t free; 346 | 347 | /* Callback parameters */ 348 | vid_t *vid; 349 | void *arg; 350 | }; 351 | 352 | struct vid_t { 353 | 354 | /* AV source */ 355 | av_t av; 356 | 357 | /* Signal configuration */ 358 | vid_config_t conf; 359 | int sample_rate; 360 | 361 | /* Video setup */ 362 | int pixel_rate; 363 | 364 | int width; 365 | int half_width; 366 | int active_width; 367 | int active_left; 368 | 369 | vbidata_lut_t *syncs; 370 | 371 | int16_t white_level; 372 | int16_t black_level; 373 | int16_t blanking_level; 374 | int16_t sync_level; 375 | 376 | _yuv16_t *yuv_level_lookup; 377 | 378 | unsigned int colour_lookup_width; 379 | unsigned int colour_lookup_offset; 380 | cint16_t *colour_lookup; 381 | 382 | cint16_t burst_phase; 383 | int burst_left; 384 | int burst_width; 385 | int16_t *burst_win; 386 | 387 | _mod_fm_t fm_secam; 388 | iir_int16_t fm_secam_iir; 389 | fir_int16_t fm_secam_fir; 390 | int16_t fm_secam_dmin[2]; 391 | int16_t fm_secam_dmax[2]; 392 | fir_int16_t secam_l_fir; 393 | cint16_t *fm_secam_bell; 394 | int16_t secam_fsync_level; 395 | int secam_field_id_lines; 396 | 397 | vbidata_lut_t *fsc_syncs; 398 | 399 | /* PAL/NTSC chrominance baseband buffer */ 400 | int16_t *chrominance_buffer; 401 | fir_int16_t chrominance_fir; 402 | 403 | /* Video state */ 404 | av_frame_t vframe; 405 | int vframe_x; 406 | int vframe_y; 407 | 408 | /* The frame and line number being rendered next */ 409 | int bframe; 410 | int bline; 411 | 412 | /* The frame and line number returned by vid_next_line() */ 413 | int frame; 414 | int line; 415 | 416 | /* Raw baseband video file */ 417 | FILE *raw_bb_file; 418 | 419 | /* Teletext state */ 420 | tt_t tt; 421 | 422 | /* WSS state */ 423 | wss_t wss; 424 | 425 | /* Videocrypt state */ 426 | vc_t vc; 427 | vcs_t vcs; 428 | 429 | /* Nagravision Syster state */ 430 | ng_t ng; 431 | 432 | /* ACP state */ 433 | acp_t acp; 434 | 435 | /* VITS state */ 436 | vits_t vits; 437 | 438 | /* VITC state */ 439 | vitc_t vitc; 440 | 441 | /* CEA/EIA-608 state */ 442 | cc608_t cc608; 443 | 444 | /* Audio state */ 445 | fifo_t audiofifo; 446 | fifo_reader_t audio_reader; 447 | int16_t *audiobuffer; 448 | size_t audiobuffer_samples; 449 | int interp; 450 | 451 | /* FM Mono/Stereo audio state */ 452 | _mod_fm_t fm_mono; 453 | _mod_fm_t fm_left; 454 | _mod_fm_t fm_right; 455 | 456 | /* Zweikanalton / A2 Stereo state */ 457 | int a2stereo_system_m; 458 | _mod_am_t a2stereo_pilot; 459 | _mod_am_t a2stereo_signal; 460 | 461 | /* NICAM stereo audio state */ 462 | nicam_mod_t nicam; 463 | int16_t nicam_buf[NICAM_AUDIO_LEN * 2]; 464 | size_t nicam_buf_len; 465 | 466 | /* SiS state */ 467 | sis_t sis; 468 | 469 | /* DANCE audio state */ 470 | dance_mod_t dance; 471 | int16_t dance_buf[DANCE_AUDIO_LEN * 2]; 472 | size_t dance_buf_len; 473 | 474 | /* AM Mono audio state */ 475 | _mod_am_t am_mono; 476 | 477 | /* FM Video state */ 478 | _mod_fm_t fm_video; 479 | 480 | /* Offset signal */ 481 | _mod_offset_t offset; 482 | 483 | /* Passthru source */ 484 | FILE *passthru; 485 | int16_t *passline; 486 | 487 | /* D/D2-MAC specific data */ 488 | mac_t mac; 489 | 490 | /* Output line(s) buffer */ 491 | int olines; 492 | vid_line_t *oline; 493 | int max_width; 494 | 495 | /* Line processes */ 496 | int nprocesses; 497 | _lineprocess_t *processes; 498 | _lineprocess_t *output_process; 499 | }; 500 | 501 | extern const vid_configs_t vid_configs[]; 502 | 503 | extern int vid_init(vid_t *s, unsigned int sample_rate, unsigned int pixel_rate, const vid_config_t * const conf); 504 | extern void vid_free(vid_t *s); 505 | extern void vid_info(vid_t *s); 506 | extern size_t vid_get_framebuffer_length(vid_t *s); 507 | extern vid_line_t *vid_next_line(vid_t *s); 508 | 509 | #endif 510 | 511 | -------------------------------------------------------------------------------- /src/videocrypt.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2017 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _VIDEOCRYPT_H 19 | #define _VIDEOCRYPT_H 20 | 21 | #include 22 | #include "video.h" 23 | 24 | #define VC_SAMPLE_RATE 14000000 25 | #define VC_WIDTH (VC_SAMPLE_RATE / 25 / 625) 26 | #define VC_VBI_LEFT 120 27 | #define VC_VBI_FIELD_1_START 12 28 | #define VC_VBI_FIELD_2_START 325 29 | #define VC_VBI_LINES_PER_FIELD 4 30 | #define VC_VBI_LINES_PER_FRAME (VC_VBI_LINES_PER_FIELD * 2) 31 | #define VC_VBI_SAMPLES_PER_BIT 18 32 | #define VC_VBI_BITS_PER_LINE 40 33 | #define VC_VBI_BYTES_PER_LINE (VC_VBI_BITS_PER_LINE / 8) 34 | #define VC_PACKET_LENGTH 32 35 | 36 | #define VC_LEFT 120 37 | #define VC_RIGHT (VC_LEFT + 710) 38 | #define VC_OVERLAP 15 39 | #define VC_FIELD_1_START 23 40 | #define VC_FIELD_2_START 335 41 | #define VC_LINES_PER_FIELD 287 42 | #define VC_LINES_PER_FRAME (VC_LINES_PER_FIELD * 2) 43 | 44 | #define VC_PRBS_CW_FA (((uint64_t) 1 << 60) - 1) 45 | #define VC_PRBS_CW_MASK (((uint64_t) 1 << 60) - 1) 46 | #define VC_PRBS_SR1_MASK (((uint32_t) 1 << 31) - 1) 47 | #define VC_PRBS_SR2_MASK (((uint32_t) 1 << 29) - 1) 48 | 49 | #define VC2_VBI_FIELD_1_START (VC_VBI_FIELD_1_START - 4) 50 | #define VC2_VBI_FIELD_2_START (VC_VBI_FIELD_2_START - 4) 51 | 52 | typedef struct { 53 | uint8_t mode; 54 | uint64_t codeword; 55 | uint8_t messages[7][32]; 56 | } _vc_block_t; 57 | 58 | typedef struct { 59 | uint8_t mode; 60 | uint64_t codeword; 61 | uint8_t messages[8][32]; 62 | } _vc2_block_t; 63 | 64 | typedef struct { 65 | 66 | uint8_t counter; 67 | 68 | /* VBI data */ 69 | vbidata_lut_t *lut; 70 | 71 | /* VC1 blocks */ 72 | const _vc_block_t *blocks; 73 | size_t block; 74 | size_t block_len; 75 | uint8_t message[32]; 76 | uint8_t vbi[VC_VBI_BYTES_PER_LINE * VC_VBI_LINES_PER_FRAME]; 77 | 78 | /* VC2 blocks */ 79 | const _vc2_block_t *blocks2; 80 | size_t block2; 81 | size_t block2_len; 82 | uint8_t message2[32]; 83 | uint8_t vbi2[VC_VBI_BYTES_PER_LINE * VC_VBI_LINES_PER_FRAME]; 84 | 85 | /* PRBS generator */ 86 | uint64_t cw; 87 | uint64_t sr1; 88 | uint64_t sr2; 89 | uint16_t c; 90 | 91 | int video_scale[VC_WIDTH]; 92 | 93 | } vc_t; 94 | 95 | extern int vc_init(vc_t *s, vid_t *vs, const char *mode, const char *mode2); 96 | extern void vc_free(vc_t *s); 97 | extern int vc_render_line(vid_t *s, void *arg, int nlines, vid_line_t **lines); 98 | 99 | #endif 100 | 101 | -------------------------------------------------------------------------------- /src/videocrypts.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2019 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _VIDEOCRYPTS_H 19 | #define _VIDEOCRYPTS_H 20 | 21 | #include 22 | #include "video.h" 23 | 24 | #define VCS_SAMPLE_RATE 17734475 25 | #define VCS_WIDTH 1135 26 | #define VCS_VBI_LEFT 211 27 | #define VCS_VBI_FIELD_1_START 24 28 | #define VCS_VBI_FIELD_2_START 336 29 | #define VCS_VBI_LINES_PER_FIELD 4 30 | #define VCS_VBI_LINES_PER_FRAME (VCS_VBI_LINES_PER_FIELD * 2) 31 | #define VCS_VBI_SAMPLES_PER_BIT 22 32 | #define VCS_VBI_BITS_PER_LINE 40 33 | #define VCS_VBI_BYTES_PER_LINE (VCS_VBI_BITS_PER_LINE / 8) 34 | #define VCS_PACKET_LENGTH 32 35 | 36 | /* This is not correct - just a placeholder */ 37 | #define VCS_PRBS_CW_FA (((uint64_t) 1 << 60) - 1) 38 | 39 | /* VCS_DELAY_LINES needs to be long enough for the scrambler to access any 40 | * line in the next block, which may be in the next field... */ 41 | 42 | #define VCS_DELAY_LINES 125 43 | 44 | typedef struct { 45 | uint8_t mode; 46 | uint8_t channel; 47 | uint64_t codeword; 48 | uint8_t messages[8][32]; 49 | } _vcs_block_t; 50 | 51 | typedef struct { 52 | 53 | uint8_t counter; 54 | 55 | /* VBI symbols */ 56 | vbidata_lut_t *lut; 57 | 58 | /* VCS blocks */ 59 | const _vcs_block_t *blocks; 60 | size_t block_num; 61 | size_t block_len; 62 | uint8_t message[32]; 63 | uint8_t vbi[VCS_VBI_BYTES_PER_LINE * VCS_VBI_LINES_PER_FRAME]; 64 | 65 | int block[47]; 66 | 67 | } vcs_t; 68 | 69 | extern int vcs_init(vcs_t *s, vid_t *vs, const char *mode); 70 | extern void vcs_free(vcs_t *s); 71 | extern int vcs_render_line(vid_t *s, void *arg, int nlines, vid_line_t **lines); 72 | 73 | #endif 74 | 75 | -------------------------------------------------------------------------------- /src/vitc.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2023 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "video.h" 25 | #include "vbidata.h" 26 | 27 | static size_t _bits(uint8_t *data, size_t offset, uint64_t bits, size_t nbits) 28 | { 29 | uint64_t m = 1; 30 | uint8_t b; 31 | 32 | for(; nbits; nbits--, offset++, bits >>= 1) 33 | { 34 | b = 1 << (offset & 7); 35 | if(bits & m) data[offset >> 3] |= b; 36 | else data[offset >> 3] &= ~b; 37 | } 38 | 39 | return(offset); 40 | } 41 | 42 | int vitc_init(vitc_t *s, vid_t *vid) 43 | { 44 | int i; 45 | int hr; 46 | 47 | memset(s, 0, sizeof(vitc_t)); 48 | 49 | s->type = vid->conf.type; 50 | 51 | if(s->type == VID_RASTER_625) 52 | { 53 | s->lines[0] = 19; 54 | s->lines[1] = 332; 55 | hr = 116; 56 | 57 | /* Fh x 116 is specified for 625-line VITC in "EBU Tech 3097 58 | * EBU Time-And-Control Code For Television Tape-Recordings", 59 | * but other specs use 115 for both 525 and 625-line modes. 60 | * 61 | * The error margin is 115 ±2%, so 116 should be a safe value 62 | * for all cases. 63 | */ 64 | } 65 | else if(s->type == VID_RASTER_525) 66 | { 67 | s->lines[0] = 14; 68 | s->lines[1] = 277; 69 | hr = 115; 70 | } 71 | else 72 | { 73 | fprintf(stderr, "vitc: Unsupported video mode\n"); 74 | return(VID_ERROR); 75 | } 76 | 77 | if(vid->conf.frame_rate.num <= 30 && 78 | vid->conf.frame_rate.den == 1) 79 | { 80 | s->fps = vid->conf.frame_rate.num; 81 | s->frame_drop = 0; 82 | } 83 | else if(vid->conf.frame_rate.num == 30000 && 84 | vid->conf.frame_rate.den == 1001) 85 | { 86 | s->fps = 30; 87 | s->frame_drop = 1; 88 | } 89 | else 90 | { 91 | fprintf(stderr, "vitc: Unsupported frame rate %" PRId64 "/%" PRId64 "\n", 92 | vid->conf.frame_rate.num, vid->conf.frame_rate.den 93 | ); 94 | 95 | return(VID_ERROR); 96 | } 97 | 98 | /* Calculate the high level for the VBI data, 78.5% of the white level */ 99 | i = round((vid->white_level - vid->black_level) * 0.785); 100 | s->lut = vbidata_init_step(hr, vid->width, i, (double) vid->width / hr, vid->pixel_rate * 200e-9, 0); 101 | 102 | if(!s->lut) 103 | { 104 | return(VID_OUT_OF_MEMORY); 105 | } 106 | 107 | return(VID_OK); 108 | } 109 | 110 | void vitc_free(vitc_t *s) 111 | { 112 | free(s->lut); 113 | memset(s, 0, sizeof(vitc_t)); 114 | } 115 | 116 | int vitc_render(vid_t *s, void *arg, int nlines, vid_line_t **lines) 117 | { 118 | vitc_t *v = arg; 119 | vid_line_t *l = lines[0]; 120 | uint32_t timecode; 121 | uint32_t userdata; 122 | uint8_t data[12]; 123 | int x, i; 124 | uint8_t crc; 125 | int fn; 126 | 127 | if(l->line != v->lines[0] && l->line != (v->lines[0] + 2) && 128 | l->line != v->lines[1] && l->line != (v->lines[1] + 2)) 129 | { 130 | return(1); 131 | } 132 | 133 | fn = l->frame; 134 | 135 | if(v->frame_drop) 136 | { 137 | /* Frame drop, to compensate for 29.97 fps modes */ 138 | fn += (fn / 17982) * 18; 139 | fn += (fn % 18000 - 2) / 1798 * 2; 140 | } 141 | 142 | /* Build the timecode data */ 143 | timecode = (fn % v->fps % 10) << 0; /* Frame number, units */ 144 | timecode |= (fn % v->fps / 10) << 4; /* Frame number, tens */ 145 | timecode |= (v->frame_drop ? 1 : 0) << 6; /* 1 == drop frame mode */ 146 | timecode |= 1 << 7; /* 1 == colour framing */ 147 | 148 | fn /= v->fps; 149 | timecode |= (fn % 10) << 8; /* Seconds, units */ 150 | timecode |= (fn / 10 % 6) << 12; /* Seconds, tens */ 151 | if(v->type != VID_RASTER_625) 152 | { 153 | timecode |= (l->line >= v->lines[1] ? 1 : 0) << 15; /* Field flag, 0: first/odd field, 1: second/even field */ 154 | } 155 | 156 | fn /= 60; 157 | timecode |= (fn % 10) << 16; /* Minutes, units */ 158 | timecode |= (fn / 10 % 6) << 20; /* Minutes, tens */ 159 | 160 | fn /= 60; 161 | timecode |= (fn % 24 % 10) << 24; /* Hours, units */ 162 | timecode |= (fn % 24 / 10) << 28; /* Hours, tens */ 163 | if(v->type == VID_RASTER_625) 164 | { 165 | timecode |= (l->line >= v->lines[1] ? 1 : 0) << 31; /* Field flag, 0: first/odd field, 1: second/even field */ 166 | } 167 | 168 | /* User bits, not used here */ 169 | userdata = 0x00; 170 | 171 | /* Pack the data */ 172 | for(x = i = 0; i < 8; i++) 173 | { 174 | x = _bits(data, x, 0x01, 2); /* Sync */ 175 | x = _bits(data, x, timecode >> (i * 4), 4); 176 | x = _bits(data, x, userdata >> (i * 4), 4); 177 | } 178 | 179 | /* Calculate CRC */ 180 | x = _bits(data, x, 0x01, 2); /* Sync */ 181 | _bits(data, x, 0, 8); 182 | 183 | for(crc = i = 0; i < 11; i++) 184 | { 185 | crc ^= data[i]; 186 | } 187 | 188 | /* Rotate the CRC and add to line */ 189 | crc = ((crc << 6) | (crc >> 2)) & 0xFF; 190 | x = _bits(data, x, crc, 8); 191 | 192 | /* Render the line */ 193 | vbidata_render(v->lut, data, 21, x, VBIDATA_LSB_FIRST, l); 194 | 195 | l->vbialloc = 1; 196 | 197 | return(1); 198 | } 199 | 200 | -------------------------------------------------------------------------------- /src/vitc.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2023 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _VITC_H 19 | #define _VITC_H 20 | 21 | #include 22 | #include "video.h" 23 | #include "vbidata.h" 24 | 25 | typedef struct { 26 | int lines[2]; 27 | int type; 28 | int fps; 29 | int frame_drop; 30 | vbidata_lut_t *lut; 31 | } vitc_t; 32 | 33 | extern int vitc_init(vitc_t *s, vid_t *vid); 34 | extern void vitc_free(vitc_t *s); 35 | 36 | extern int vitc_render(vid_t *s, void *arg, int nlines, vid_line_t **lines); 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /src/vits.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2020 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | /* -=== VITS test signal inserter ===- */ 19 | 20 | /* TODO: The phase of the chrominance signal is likely wrong. 21 | * 22 | * The rise and fall shape of the five-riser staircase is specified as 23 | * "shaped by a Thomson filter (or similar network) with a transfer function 24 | * modulus having its first zero at 4.43 MHz to restrict the amplitude of 25 | * components of the luminance signal in the vicinity of the colour 26 | * sub-carrier". This code uses the same shape as the other parts: 27 | * "derived from the shaping network of the sine-squared pulse". 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "video.h" 36 | 37 | static const double _bursts_625[6] = { 38 | 0.5e6, 39 | 1.0e6, 40 | 2.0e6, 41 | 4.0e6, 42 | 4.8e6, 43 | 5.8e6, 44 | }; 45 | 46 | static const double _bursts_525[6] = { 47 | 0.50e6, 48 | 1.00e6, 49 | 2.00e6, 50 | 3.00e6, 51 | 3.58e6, 52 | 4.20e6, 53 | }; 54 | 55 | static double _pulse(double t, double position, double width, double amplitude) 56 | { 57 | double a; 58 | 59 | t -= position - width; 60 | 61 | if(t <= 0 || t >= width * 2) 62 | { 63 | return(0); 64 | } 65 | 66 | a = t / (width * 2) * M_PI; 67 | 68 | return(pow(sin(a), 2) * amplitude); 69 | } 70 | 71 | static int _init_625(vits_t *s, unsigned int sample_rate, int width, int level) 72 | { 73 | int i, x, b; 74 | double r, c, t; 75 | double ts, h; 76 | double bs[6]; 77 | 78 | /* Setup timing */ 79 | ts = 1.0 / 25 / 625; 80 | h = ts / 32; 81 | ts = ts / width; 82 | 83 | for(b = 0; b < 6; b++) 84 | { 85 | bs[b] = 2.0 * M_PI * _bursts_625[b]; 86 | } 87 | 88 | s->lines = 625; 89 | s->width = width; 90 | 91 | for(i = 0; i < 4; i++) 92 | { 93 | s->line[i] = malloc(sizeof(int16_t) * 2 * width); 94 | if(!s->line[i]) 95 | { 96 | perror("malloc"); 97 | vits_free(s); 98 | return(-1); 99 | } 100 | 101 | for(x = 0; x < width; x++) 102 | { 103 | t = ts * x; 104 | r = 0.0; 105 | c = 0.0; 106 | 107 | switch(i) 108 | { 109 | case 0: /* Line 17 */ 110 | r += rc_window(t, 6 * h, 5 * h, 200e-9) * 0.70; 111 | r += _pulse(t, 13 * h, 200e-9, 0.70); 112 | r += _pulse(t, 16 * h, 2000e-9, 0.70 / 2); 113 | c += _pulse(t, 16 * h, 2000e-9, 0.70 / 2); 114 | r += rc_window(t, 20 * h, 2 * h, 200e-9) * 0.14; 115 | r += rc_window(t, 22 * h, 2 * h, 200e-9) * 0.28; 116 | r += rc_window(t, 24 * h, 2 * h, 200e-9) * 0.42; 117 | r += rc_window(t, 26 * h, 2 * h, 200e-9) * 0.56; 118 | r += rc_window(t, 28 * h, 3 * h, 200e-9) * 0.70; 119 | break; 120 | 121 | case 1: /* Line 18 */ 122 | r += rc_window(t, 6 * h, 25 * h, 200e-9) * 0.35; 123 | r += rc_window(t, 6 * h, 2 * h, 200e-9) * 0.21; 124 | r += rc_window(t, 8 * h, 2 * h, 200e-9) * -0.21; 125 | 126 | for(b = 0; b < 6; b++) 127 | { 128 | r += rc_window(t, (12 + 3 * b) * h, 2 * h, 200e-9) * 0.21 129 | * sin((t - (12 + 3 * b) * h) * bs[b]); 130 | } 131 | 132 | break; 133 | 134 | case 2: /* Line 330 */ 135 | r += rc_window(t, 6 * h, 5 * h, 200e-9) * 0.70; 136 | r += _pulse(t, 13 * h, 200e-9, 0.70); 137 | c += rc_window(t, 15 * h, 15 * h, 1e-6) * 0.28 / 2; 138 | r += rc_window(t, 20 * h, 2 * h, 200e-9) * 0.14; 139 | r += rc_window(t, 22 * h, 2 * h, 200e-9) * 0.28; 140 | r += rc_window(t, 24 * h, 2 * h, 200e-9) * 0.42; 141 | r += rc_window(t, 26 * h, 2 * h, 200e-9) * 0.56; 142 | r += rc_window(t, 28 * h, 3 * h, 200e-9) * 0.70; 143 | break; 144 | 145 | case 3: /* Line 331 */ 146 | r += rc_window(t, 6 * h, 25 * h, 200e-9) * 0.35; 147 | c += rc_window(t, 7 * h, 7 * h, 1e-6) * 0.70 / 2; 148 | c += rc_window(t, 17 * h, 13 * h, 1e-6) * 0.42 / 2; 149 | break; 150 | } 151 | 152 | s->line[i][x * 2 + 0] = lround(r / 0.7 * level); 153 | s->line[i][x * 2 + 1] = lround(c / 0.7 * level); 154 | } 155 | } 156 | 157 | return(0); 158 | } 159 | 160 | static int _init_525(vits_t *s, unsigned int sample_rate, int width, int level) 161 | { 162 | int i, x, b; 163 | double r, c, t; 164 | double ts, h; 165 | double bs[6]; 166 | 167 | /* Setup timing */ 168 | ts = 1001.0 / 30000 / 525; 169 | h = ts / 128; 170 | ts = ts / width; 171 | 172 | for(b = 0; b < 6; b++) 173 | { 174 | bs[b] = 2.0 * M_PI * _bursts_525[b]; 175 | } 176 | 177 | s->lines = 525; 178 | s->width = width; 179 | 180 | for(i = 0; i < 2; i++) 181 | { 182 | s->line[i] = malloc(sizeof(int16_t) * 2 * width); 183 | if(!s->line[i]) 184 | { 185 | perror("malloc"); 186 | vits_free(s); 187 | return(-1); 188 | } 189 | 190 | for(x = 0; x < width; x++) 191 | { 192 | t = ts * x; 193 | r = 0.0; 194 | c = 0.0; 195 | 196 | switch(i) 197 | { 198 | case 0: /* Line 17 */ 199 | r += rc_window(t, 24 * h, 36 * h, 125e-9) * 100; 200 | r += _pulse(t, 68 * h, 250e-9, 100); 201 | r += _pulse(t, 75 * h, 1570e-9, 100 / 2); 202 | c += _pulse(t, 75 * h, 1570e-9, 100 / 2); 203 | r += rc_window(t, 92 * h, 6 * h, 250e-9) * 18; 204 | r += rc_window(t, 98 * h, 6 * h, 250e-9) * 36; 205 | r += rc_window(t, 104 * h, 6 * h, 250e-9) * 54; 206 | r += rc_window(t, 110 * h, 6 * h, 250e-9) * 72; 207 | r += rc_window(t, 116 * h, 8 * h, 250e-9) * 90; 208 | c += rc_window(t, 84 * h, 38 * h, 400e-9) * 40 / 2; 209 | break; 210 | 211 | case 1: /* Line 280 */ 212 | r += rc_window(t, 24 * h, 8 * h, 125e-9) * 100; 213 | r += rc_window(t, 32 * h, 92 * h, 125e-9) * 50; 214 | 215 | r += rc_window(t, 36 * h, 12 * h, 250e-9) * 50 / 2 216 | * sin((t - 36 * h) * bs[0]); 217 | 218 | for(b = 1; b < 6; b++) 219 | { 220 | r += rc_window(t, (40 + 8 * b) * h, 8 * h, 250e-9) * 50 / 2 221 | * sin((t - (40 + 8 * b) * h) * bs[b]); 222 | } 223 | 224 | c += rc_window(t, 92 * h, 8 * h, 400e-9) * 20 / 2; 225 | c += rc_window(t, 100 * h, 8 * h, 400e-9) * 40 / 2; 226 | c += rc_window(t, 108 * h, 12 * h, 400e-9) * 80 / 2; 227 | 228 | break; 229 | } 230 | 231 | s->line[i][x * 2 + 0] = lround(r / 100 * level); 232 | s->line[i][x * 2 + 1] = lround(c / 100 * level); 233 | } 234 | } 235 | 236 | return(0); 237 | } 238 | 239 | int vits_init(vits_t *s, unsigned int sample_rate, int width, int lines, int pal, int level) 240 | { 241 | int r; 242 | 243 | memset(s, 0, sizeof(vits_t)); 244 | 245 | if(pal) 246 | { 247 | /* The insertion signal is locked at 60 ± 5° from the positive (B-Y) axis for PAL */ 248 | double p = 60.0 * (M_PI / 180.0); 249 | s->cs_phase = (cint16_t) { 250 | round(cos(p) * INT16_MAX), 251 | round(sin(p) * INT16_MAX), 252 | }; 253 | } 254 | else 255 | { 256 | /* For NTSC is is the same as the burst, 180° */ 257 | s->cs_phase = (cint16_t) { 0, -INT16_MAX }; 258 | } 259 | 260 | if(lines == 625) r = _init_625(s, sample_rate, width, level); 261 | else if(lines == 525) r = _init_525(s, sample_rate, width, level); 262 | else r = -1; 263 | 264 | return(r); 265 | } 266 | 267 | void vits_free(vits_t *s) 268 | { 269 | int i; 270 | 271 | for(i = 0; i < 4; i++) 272 | { 273 | free(s->line[i]); 274 | } 275 | 276 | memset(s, 0, sizeof(vits_t)); 277 | } 278 | 279 | int vits_render(vid_t *s, void *arg, int nlines, vid_line_t **lines) 280 | { 281 | vits_t *v = arg; 282 | int x, i = -1; 283 | vid_line_t *l = lines[0]; 284 | 285 | if(v->lines == 625) 286 | { 287 | if(l->line == 17 || l->line == 18) i = l->line - 17; 288 | else if(l->line == 330 || l->line == 331) i = l->line - 330 + 2; 289 | } 290 | else if(v->lines == 525) 291 | { 292 | if(l->line == 17) i = l->line - 17; 293 | else if(l->line == 280) i = l->line - 280 + 1; 294 | } 295 | 296 | if(i < 0) return(0); 297 | if(!v->line[i]) return(0); 298 | 299 | for(x = 0; x < s->width; x++) 300 | { 301 | l->output[x * 2] += v->line[i][x * 2 + 0]; 302 | if(l->lut) 303 | { 304 | l->output[x * 2] += (((v->cs_phase.i * l->lut[x].q + 305 | v->cs_phase.q * l->lut[x].i) >> 15) * v->line[i][x * 2 + 1]) >> 15; 306 | } 307 | } 308 | 309 | l->vbialloc = 1; 310 | 311 | return(1); 312 | } 313 | 314 | -------------------------------------------------------------------------------- /src/vits.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2020 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _VITS_H 19 | #define _VITS_H 20 | 21 | #include 22 | #include "video.h" 23 | 24 | typedef struct { 25 | int width; 26 | int lines; 27 | int16_t *line[4]; 28 | cint16_t cs_phase; 29 | } vits_t; 30 | 31 | extern int vits_init(vits_t *s, unsigned int sample_rate, int width, int lines, int pal, int level); 32 | extern void vits_free(vits_t *s); 33 | 34 | extern int vits_render(vid_t *s, void *arg, int nlines, vid_line_t **lines); 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /src/wss.c: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | /* -=== WSS encoder ===- */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "video.h" 25 | #include "vbidata.h" 26 | 27 | typedef struct { 28 | const char *id; 29 | uint8_t code; 30 | r64_t aspect[2]; 31 | } _wss_modes_t; 32 | 33 | static const _wss_modes_t _wss_modes[] = { 34 | /* Name/ID, Parity | Code, Display Aspect Ratio(s) */ 35 | { "4:3", 0x08 | 0x00, { { 4, 3 } } }, 36 | { "14:9-letterbox", 0x00 | 0x01, { { 4, 3 } } }, 37 | { "14:9-top", 0x00 | 0x02, { { 4, 3 } } }, 38 | { "16:9-letterbox", 0x08 | 0x03, { { 4, 3 } } }, 39 | { "16:9-top", 0x00 | 0x04, { { 4, 3 } } }, 40 | { "16:9+-letterbox", 0x08 | 0x05, { { 4, 3 } } }, 41 | { "14:9-window", 0x08 | 0x06, { { 4, 3 } } }, 42 | { "16:9", 0x00 | 0x07, { { 16, 9 } } }, 43 | { "auto", 0xFF, { { 4, 3 }, { 16, 9 } } }, 44 | { }, 45 | }; 46 | 47 | static size_t _group_bits(uint8_t *vbi, uint8_t code, size_t offset, size_t length) 48 | { 49 | int i, b; 50 | 51 | while(length--) 52 | { 53 | for(i = 0; i < 6; i++, offset++) 54 | { 55 | if(i == 3) code ^= 1; 56 | 57 | b = 7 - (offset % 8); 58 | 59 | vbi[offset / 8] &= ~(1 << b); 60 | vbi[offset / 8] |= (code & 1) << b; 61 | } 62 | 63 | code >>= 1; 64 | } 65 | 66 | return(offset); 67 | } 68 | 69 | int wss_init(wss_t *s, vid_t *vid, char *mode) 70 | { 71 | int level; 72 | size_t o; 73 | 74 | memset(s, 0, sizeof(wss_t)); 75 | 76 | s->vid = vid; 77 | 78 | /* Calculate the threshold pixel aspect ratio for auto mode */ 79 | s->auto_threshold = r64_div( 80 | (r64_t) { 14, 9 }, 81 | (r64_t) { s->vid->active_width, s->vid->conf.active_lines } 82 | ); 83 | 84 | /* Find the mode settings */ 85 | s->code = 0; 86 | for(o = 0; _wss_modes[o].id != NULL; o++) 87 | { 88 | if(strcasecmp(mode, _wss_modes[o].id) == 0) 89 | { 90 | s->code = _wss_modes[o].code; 91 | vid->conf.frame_aspects[0] = _wss_modes[o].aspect[0]; 92 | vid->conf.frame_aspects[1] = _wss_modes[o].aspect[1]; 93 | break; 94 | } 95 | } 96 | 97 | if(s->code == 0) 98 | { 99 | fprintf(stderr, "wss: Unrecognised mode '%s'.\n", mode); 100 | return(VID_ERROR); 101 | } 102 | 103 | /* Calculate the high level for the VBI data */ 104 | level = round((vid->white_level - vid->black_level) * (5.0 / 7.0)); 105 | 106 | s->lut = vbidata_init_step( 107 | 137, vid->width, level, 108 | (double) vid->pixel_rate * 200e-9, 109 | (double) vid->pixel_rate * 200e-9, 110 | (double) vid->pixel_rate * 11e-6 111 | ); 112 | 113 | if(!s->lut) 114 | { 115 | return(VID_OUT_OF_MEMORY); 116 | } 117 | 118 | /* Prepare the VBI data. Start with the run-in and start code */ 119 | s->vbi[0] = 0xF8; // 11111000 120 | s->vbi[1] = 0xE3; // 11100011 121 | s->vbi[2] = 0x8E; // 10001110 122 | s->vbi[3] = 0x38; // 00111000 123 | s->vbi[4] = 0xF1; // 11110001 124 | s->vbi[5] = 0xE0; // 11100000 125 | s->vbi[6] = 0xF8; // 11111___ 126 | 127 | /* Group 1 (Aspect Ratio) */ 128 | o = _group_bits(s->vbi, s->code, 29 + 24, 4); 129 | 130 | /* Group 2 (Enhanced Services) */ 131 | o = _group_bits(s->vbi, 0x00, o, 4); 132 | 133 | /* Group 3 (Subtitles) */ 134 | o = _group_bits(s->vbi, 0x00, o, 3); 135 | 136 | /* Group 4 (Reserved) */ 137 | o = _group_bits(s->vbi, 0x00, o, 3); 138 | 139 | /* Calculate width of line to blank */ 140 | s->blank_width = round(s->vid->pixel_rate * 42.5e-6); 141 | 142 | return(VID_OK); 143 | } 144 | 145 | void wss_free(wss_t *s) 146 | { 147 | if(s == NULL) return; 148 | 149 | free(s->lut); 150 | 151 | memset(s, 0, sizeof(wss_t)); 152 | } 153 | 154 | int wss_render(vid_t *s, void *arg, int nlines, vid_line_t **lines) 155 | { 156 | wss_t *w = arg; 157 | vid_line_t *l = lines[0]; 158 | int x; 159 | 160 | /* WSS is rendered on line 23 */ 161 | if(l->line != 23) 162 | { 163 | return(1); 164 | } 165 | 166 | if(w->code == 0xFF) 167 | { 168 | int c; 169 | 170 | /* Auto mode selects between 4:3 and 16:9 depending 171 | * on the the pixel aspect ratio of the source frame */ 172 | 173 | c = r64_cmp( 174 | s->vframe.pixel_aspect_ratio, 175 | w->auto_threshold 176 | ); 177 | 178 | _group_bits(w->vbi, c <= 0 ? 0x08 : 0x07, 29 + 24, 4); 179 | } 180 | 181 | /* 42.5μs of line 23 needs to be blanked otherwise the WSS bits may 182 | * overlap active video */ 183 | for(x = s->half_width; x < w->blank_width; x++) 184 | { 185 | l->output[x * 2] = s->black_level; 186 | } 187 | 188 | vbidata_render(w->lut, w->vbi, 0, 137, VBIDATA_MSB_FIRST, l); 189 | 190 | l->vbialloc = 1; 191 | 192 | return(1); 193 | } 194 | 195 | -------------------------------------------------------------------------------- /src/wss.h: -------------------------------------------------------------------------------- 1 | /* hacktv - Analogue video transmitter for the HackRF */ 2 | /*=======================================================================*/ 3 | /* Copyright 2018 Philip Heron */ 4 | /* */ 5 | /* This program is free software: you can redistribute it and/or modify */ 6 | /* it under the terms of the GNU General Public License as published by */ 7 | /* the Free Software Foundation, either version 3 of the License, or */ 8 | /* (at your option) any later version. */ 9 | /* */ 10 | /* This program is distributed in the hope that it will be useful, */ 11 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 | /* GNU General Public License for more details. */ 14 | /* */ 15 | /* You should have received a copy of the GNU General Public License */ 16 | /* along with this program. If not, see . */ 17 | 18 | #ifndef _WSS_H 19 | #define _WSS_H 20 | 21 | #include 22 | #include "video.h" 23 | #include "vbidata.h" 24 | 25 | typedef struct { 26 | vid_t *vid; 27 | r64_t auto_threshold; 28 | uint8_t code; 29 | vbidata_lut_t *lut; 30 | uint8_t vbi[18]; 31 | int blank_width; 32 | } wss_t; 33 | 34 | extern int wss_init(wss_t *s, vid_t *vid, char *mode); 35 | extern void wss_free(wss_t *s); 36 | extern int wss_render(vid_t *s, void *arg, int nlines, vid_line_t **lines); 37 | 38 | #endif 39 | 40 | --------------------------------------------------------------------------------