nice refactors, now way cleaner to work with!
This commit is contained in:
41
src/encoder/image.c
Normal file
41
src/encoder/image.c
Normal file
@@ -0,0 +1,41 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "../../third_party/stb_image.h"
|
||||
#include "../../include/encoder/image.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
int image_load(image_t *img, const char *filename) {
|
||||
int ch;
|
||||
img->data = stbi_load(filename, &img->width, &img->height, &ch, 3);
|
||||
return img->data != NULL;
|
||||
}
|
||||
|
||||
int image_resize(image_t *src, image_t *dst, int new_w, int new_h) {
|
||||
dst->width = new_w;
|
||||
dst->height = new_h;
|
||||
dst->data = malloc(new_w * new_h * 3);
|
||||
if (!dst->data) return 0;
|
||||
|
||||
for (int y = 0; y < new_h; y++) {
|
||||
for (int x = 0; x < new_w; x++) {
|
||||
int sx = x * src->width / new_w;
|
||||
int sy = y * src->height / new_h;
|
||||
for (int c = 0; c < 3; c++) {
|
||||
dst->data[(y*new_w + x)*3 + c] =
|
||||
src->data[(sy*src->width + sx)*3 + c];
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void image_free(image_t *img) {
|
||||
if (img->data)
|
||||
stbi_image_free(img->data);
|
||||
img->data = NULL;
|
||||
}
|
||||
|
||||
void image_free_raw(image_t *img) {
|
||||
if (img->data)
|
||||
free(img->data); // if you do this call only once, cause of double frees
|
||||
img->data = NULL;
|
||||
}
|
||||
79
src/encoder/modes/martin.c
Normal file
79
src/encoder/modes/martin.c
Normal file
@@ -0,0 +1,79 @@
|
||||
// martin.c - includes look shit, but this has to be like this
|
||||
#include "encoder/modes/martin.h"
|
||||
#include "encoder/image.h"
|
||||
#include "encoder/sstv.h"
|
||||
#include "sound/wav.h"
|
||||
#include <stdint.h>
|
||||
|
||||
const martin_mode_t MARTIN_M1 = {
|
||||
.vis_code = 0x2C, // hex (dd -> 44d)
|
||||
.width = 320,
|
||||
.height = 256,
|
||||
.sync_ms = 4.862,
|
||||
.sync_porch_ms = 0.572,
|
||||
.separator_ms = 0.572,
|
||||
.scan_ms = 146.432, // per channel , per line
|
||||
};
|
||||
|
||||
const martin_mode_t MARTIN_M2 = {
|
||||
.vis_code = 0x28, // hex (dd -> 40d)
|
||||
.width = 320,
|
||||
.height = 256,
|
||||
.sync_ms = 4.862,
|
||||
.sync_porch_ms = 0.572,
|
||||
.separator_ms = 0.572,
|
||||
.scan_ms = 73.216, // half of M1
|
||||
};
|
||||
|
||||
static void scan_line_channel(wav_t *wav, uint8_t *line, int width, int channel, double scan_ms) {
|
||||
double px_ms = scan_ms / width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
uint8_t val = line[x * 3 + channel];
|
||||
double freq = 1500.0 + val * 3.1372549;
|
||||
sstv_tone(wav, freq, px_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void martin_encode_image(wav_t *wav, uint8_t *rgb, const martin_mode_t *mode) {
|
||||
for (int y = 0; y < mode->height; y++) {
|
||||
uint8_t *line = &rgb[y * mode->width * 3];
|
||||
|
||||
sstv_tone(wav, 1200.0, mode->sync_ms); // sync pulse
|
||||
sstv_tone(wav, 1500.0, mode->sync_porch_ms); // sync porch
|
||||
scan_line_channel(wav, line, mode->width, 1, mode->scan_ms); // G
|
||||
sstv_tone(wav, 1500.0, mode->separator_ms);
|
||||
scan_line_channel(wav, line, mode->width, 2, mode->scan_ms); // B
|
||||
sstv_tone(wav, 1500.0, mode->separator_ms);
|
||||
scan_line_channel(wav, line, mode->width, 0, mode->scan_ms); // R
|
||||
sstv_tone(wav, 1500.0, mode->separator_ms);
|
||||
}
|
||||
}
|
||||
|
||||
int sstv_encode_martin(const char *input_image, const char *output_wav, const martin_mode_t *mode) {
|
||||
image_t img, resized;
|
||||
wav_t wav;
|
||||
|
||||
if (!image_load(&img, input_image)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!image_resize(&img, &resized, mode->width, mode->height)) {
|
||||
image_free(&img);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!wave_open(&wav, output_wav, 44100)) {
|
||||
image_free(&img);
|
||||
image_free_raw(&resized);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sstv_vis_header_ex(&wav, mode->vis_code);
|
||||
martin_encode_image(&wav, resized.data, mode);
|
||||
wave_close(&wav);
|
||||
|
||||
image_free(&img);
|
||||
image_free_raw(&resized);
|
||||
|
||||
return 1;
|
||||
}
|
||||
43
src/encoder/modes/robot36.c
Normal file
43
src/encoder/modes/robot36.c
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "encoder/modes/robot36.h"
|
||||
#include "encoder/sstv.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// From Dayton Paper Appendix B - ITU BT.601
|
||||
// http://www.barberdsp.com/downloads/Dayton%20Paper.pdf
|
||||
static void rgb_to_ycbcr(uint8_t r, uint8_t g, uint8_t b,
|
||||
double *Y, double *RY, double *BY) {
|
||||
*Y = 16.0 + .003906 * ((65.738 * r) + (129.057 * g) + (25.064 * b));
|
||||
*RY = 128.0 + .003906 * ((112.439 * r) + (-94.154 * g) + (-18.285 * b));
|
||||
*BY = 128.0 + .003906 * ((-37.945 * r) + (-74.494 * g) + (112.439 * b));
|
||||
}
|
||||
|
||||
static void scan_line_y(wav_t *wav, uint8_t *line) {
|
||||
for (int x = 0; x < ROBOT36_WIDTH; x++) {
|
||||
double Y, RY, BY;
|
||||
rgb_to_ycbcr(line[x*3+0], line[x*3+1], line[x*3+2], &Y, &RY, &BY);
|
||||
sstv_tone(wav, 1500.0 + Y * 3.1372549, 88.0 / ROBOT36_WIDTH);
|
||||
}
|
||||
}
|
||||
|
||||
static void scan_line_chroma(wav_t *wav, uint8_t *line, int is_ry) {
|
||||
for (int x = 0; x < ROBOT36_WIDTH; x++) {
|
||||
double Y, RY, BY;
|
||||
rgb_to_ycbcr(line[x*3+0], line[x*3+1], line[x*3+2], &Y, &RY, &BY);
|
||||
double c = is_ry ? RY : BY;
|
||||
sstv_tone(wav, 1500.0 + c * 3.1372549, 44.0 / ROBOT36_WIDTH);
|
||||
}
|
||||
}
|
||||
|
||||
void robot36_encode_image(wav_t *wav, uint8_t *rgb) {
|
||||
for (int y = 0; y < ROBOT36_HEIGHT; y++) {
|
||||
uint8_t *line = &rgb[y * ROBOT36_WIDTH * 3];
|
||||
int even = (y % 2 == 0);
|
||||
|
||||
sstv_tone(wav, 1200, 9.0); // sync
|
||||
sstv_tone(wav, 1500, 3.0); // sync porch
|
||||
scan_line_y(wav, line);
|
||||
sstv_tone(wav, even ? 1500 : 2300, 4.5); // separator - tells decoder Cr vs Cb!
|
||||
sstv_tone(wav, 1900, 1.5); // porch before chroma
|
||||
scan_line_chroma(wav, line, even); // Cr on even, Cb on odd
|
||||
}
|
||||
}
|
||||
91
src/encoder/modes/scottie.c
Normal file
91
src/encoder/modes/scottie.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "encoder/modes/scottie.h"
|
||||
#include "encoder/image.h"
|
||||
#include "encoder/sstv.h"
|
||||
#include "sound/wav.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
const scottie_mode_t SCOTTIE_S1 = {
|
||||
.vis_code = 0x3C, // 60d
|
||||
.width = 320,
|
||||
.height = 256,
|
||||
.separator_ms = 1.5,
|
||||
.sync_ms = 9.0,
|
||||
.sync_porch_ms = 1.5,
|
||||
.scan_ms = 138.240,
|
||||
};
|
||||
|
||||
const scottie_mode_t SCOTTIE_S2 = {
|
||||
.vis_code = 0x38, // 56d
|
||||
.width = 320,
|
||||
.height = 256,
|
||||
.separator_ms = 1.5,
|
||||
.sync_ms = 9.0,
|
||||
.sync_porch_ms = 1.5,
|
||||
.scan_ms = 88.064,
|
||||
};
|
||||
|
||||
const scottie_mode_t SCOTTIE_DX = {
|
||||
.vis_code = 0x4C, // 76d
|
||||
.width = 320,
|
||||
.height = 256,
|
||||
.separator_ms = 1.5,
|
||||
.sync_ms = 9.0,
|
||||
.sync_porch_ms = 1.5,
|
||||
.scan_ms = 345.600,
|
||||
};
|
||||
|
||||
static void scan_line_channel(wav_t *wav, uint8_t *line, int width, int channel, double scan_ms) {
|
||||
double px_ms = scan_ms / width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
uint8_t val = line[x * 3 + channel];
|
||||
double freq = 1500.0 + val * 3.1372549;
|
||||
sstv_tone(wav, freq, px_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void scottie_encode_image(wav_t *wav, uint8_t *rgb, const scottie_mode_t *mode) {
|
||||
for (int y = 0; y < mode->height; y++) {
|
||||
uint8_t *line = &rgb[y * mode->width * 3];
|
||||
|
||||
if (y == 0) {
|
||||
sstv_tone(wav, 1200.0, mode->sync_ms);
|
||||
}
|
||||
|
||||
sstv_tone(wav, 1500.0, mode->separator_ms);
|
||||
scan_line_channel(wav, line, mode->width, 1, mode->scan_ms);
|
||||
sstv_tone(wav, 1500.0, mode->separator_ms);
|
||||
scan_line_channel(wav, line, mode->width, 2, mode->scan_ms);
|
||||
sstv_tone(wav, 1200.0, mode->sync_ms);
|
||||
sstv_tone(wav, 1500.0, mode->sync_porch_ms);
|
||||
scan_line_channel(wav, line, mode->width, 0, mode->scan_ms);
|
||||
}
|
||||
}
|
||||
|
||||
int sstv_encode_scottie(const char *input_image, const char *output_wav, const scottie_mode_t *mode) {
|
||||
image_t img, resized;
|
||||
wav_t wav;
|
||||
|
||||
if (!image_load(&img, input_image))
|
||||
return 0;
|
||||
|
||||
if (!image_resize(&img, &resized, mode->width, mode->height)) {
|
||||
image_free(&img);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!wave_open(&wav, output_wav, 44100)) {
|
||||
image_free(&img);
|
||||
image_free_raw(&resized);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sstv_vis_header_ex(&wav, mode->vis_code);
|
||||
scottie_encode_image(&wav, resized.data, mode);
|
||||
wave_close(&wav);
|
||||
|
||||
image_free(&img);
|
||||
image_free_raw(&resized);
|
||||
|
||||
return 1;
|
||||
}
|
||||
86
src/encoder/sstv.c
Normal file
86
src/encoder/sstv.c
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "sound/wav.h"
|
||||
#include "encoder/sstv.h"
|
||||
#include "encoder/image.h"
|
||||
#include "encoder/modes/robot36.h"
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PI 3.14159265358979
|
||||
#define AMPLITUDE 12000
|
||||
|
||||
static double phase = 0.0;
|
||||
static double sample_accum = 0.0; // accumulator
|
||||
|
||||
void sstv_tone(wav_t *wav, double freq, double duration_ms) {
|
||||
sample_accum += (duration_ms / 1000.0) * wav->sample_rate;
|
||||
uint32_t total_samples = (uint32_t)sample_accum;
|
||||
sample_accum -= total_samples;
|
||||
|
||||
double step = 2.0 * PI * freq / wav->sample_rate;
|
||||
for (uint32_t i = 0; i < total_samples; i++) {
|
||||
phase += step;
|
||||
if (phase > 2.0 * PI)
|
||||
phase -= 2.0 * PI;
|
||||
|
||||
int16_t sample = (int16_t)(AMPLITUDE * sin(phase));
|
||||
wave_write_sample(wav, sample);
|
||||
}
|
||||
}
|
||||
|
||||
void sstv_vis_header_ex(wav_t *wav, uint8_t vis_code) {
|
||||
// reset accumulator
|
||||
phase = 0.0;
|
||||
sample_accum = 0.0;
|
||||
|
||||
/* preamble */
|
||||
sstv_tone(wav, 1900, 300);
|
||||
sstv_tone(wav, 1200, 10);
|
||||
sstv_tone(wav, 1900, 300);
|
||||
|
||||
sstv_tone(wav, 1200, 30);
|
||||
|
||||
uint8_t parity = 0;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
uint8_t bit = (vis_code >> i) & 1;
|
||||
sstv_tone(wav, bit ? 1100 : 1300, 30);
|
||||
parity ^= bit;
|
||||
}
|
||||
|
||||
/* parity bit */
|
||||
sstv_tone(wav, parity ? 1100 : 1300, 30);
|
||||
|
||||
/* stop bit */
|
||||
sstv_tone(wav, 1200, 30);
|
||||
}
|
||||
|
||||
void sstv_vis_header(wav_t *wav) {
|
||||
sstv_vis_header_ex(wav, 0x08);
|
||||
}
|
||||
|
||||
int sstv_encode_robot36(const char *input_image, const char *output_wav) {
|
||||
image_t img, resized;
|
||||
wav_t wav;
|
||||
|
||||
if (!image_load(&img, input_image))
|
||||
return 0;
|
||||
|
||||
if (!image_resize(&img, &resized, ROBOT36_WIDTH, ROBOT36_HEIGHT)) {
|
||||
image_free(&img);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!wave_open(&wav, output_wav, 44100)) {
|
||||
image_free(&img);
|
||||
image_free_raw(&resized);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sstv_vis_header(&wav);
|
||||
robot36_encode_image(&wav, resized.data);
|
||||
wave_close(&wav);
|
||||
|
||||
image_free(&img);
|
||||
image_free_raw(&resized);
|
||||
|
||||
return 1;
|
||||
}
|
||||
69
src/main.c
Normal file
69
src/main.c
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "encoder/modes/martin.h"
|
||||
#include "encoder/modes/scottie.h"
|
||||
#include "encoder/sstv.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
static void usage(const char *prog) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s <mode> <input_image> <output_wav>\n"
|
||||
"\n"
|
||||
"Modes:\n"
|
||||
" robot36 Robot 36 (320x240)\n"
|
||||
" martin1 Martin M1 (320x256)\n"
|
||||
" martin2 Martin M2 (320x256)\n"
|
||||
" scottie1 Scottie S1 (320x256)\n"
|
||||
" scottie2 Scottie S2 (320x256)\n"
|
||||
" scottiedx Scottie DX (320x256)\n"
|
||||
"\n"
|
||||
"Example:\n"
|
||||
" %s mode photo.jpg output.wav\n",
|
||||
prog, prog);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 4) {
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *mode = argv[1];
|
||||
const char *input_img = argv[2];
|
||||
const char *output_wav = argv[3];
|
||||
|
||||
clock_t start = clock();
|
||||
|
||||
int ok = 0;
|
||||
|
||||
if (strcmp(mode, "robot36") == 0) {
|
||||
ok = sstv_encode_robot36(input_img, output_wav);
|
||||
} else if (strcmp(mode, "martin1") == 0) {
|
||||
ok = sstv_encode_martin(input_img, output_wav, &MARTIN_M1);
|
||||
} else if (strcmp(mode, "martin2") == 0) {
|
||||
ok = sstv_encode_martin(input_img, output_wav, &MARTIN_M2);
|
||||
} else if (strcmp(mode, "scottie1") == 0) {
|
||||
ok = sstv_encode_scottie(input_img, output_wav, &SCOTTIE_S1);
|
||||
} else if (strcmp(mode, "scottie2") == 0) {
|
||||
ok = sstv_encode_scottie(input_img, output_wav, &SCOTTIE_S2);
|
||||
} else if (strcmp(mode, "scottiedx") == 0) {
|
||||
ok = sstv_encode_scottie(input_img, output_wav, &SCOTTIE_DX);
|
||||
} else {
|
||||
fprintf(stderr, "unknown mode: %s\n\n", mode);
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
fprintf(stderr, "make sure the file exsists");
|
||||
return 1;
|
||||
}
|
||||
|
||||
clock_t end = clock();
|
||||
|
||||
double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
|
||||
|
||||
printf("encoded '%s' -> '%s' using '%s'\n", input_img, output_wav, mode);
|
||||
printf("took %.3f seconds\n", elapsed);
|
||||
return 0;
|
||||
}
|
||||
71
src/sound/wav.c
Normal file
71
src/sound/wav.c
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "sound/wav.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
static void write_u16(FILE *f, uint16_t v) {
|
||||
uint8_t b[2];
|
||||
b[0] = (uint8_t)(v & 0xff);
|
||||
b[1] = (uint8_t)((v >> 8) & 0xff);
|
||||
fwrite(b, 1, 2, f);
|
||||
}
|
||||
|
||||
static void write_u32(FILE *f, uint32_t v) {
|
||||
uint8_t b[4];
|
||||
b[0] = (uint8_t)(v & 0xff);
|
||||
b[1] = (uint8_t)((v >> 8) & 0xff);
|
||||
b[2] = (uint8_t)((v >> 16) & 0xff);
|
||||
b[3] = (uint8_t)((v >> 24) & 0xff);
|
||||
fwrite(b, 1, 4, f);
|
||||
}
|
||||
|
||||
int wave_open(wav_t *wav, const char *filename, uint32_t sample_rate) {
|
||||
|
||||
wav->file = fopen(filename, "wb");
|
||||
if (!wav->file)
|
||||
return 0;
|
||||
|
||||
wav->sample_rate = sample_rate;
|
||||
wav->samples_written = 0;
|
||||
|
||||
/* RIFF header */
|
||||
fwrite((uint8_t[]){'R','I','F','F'}, 1, 4, wav->file);
|
||||
write_u32(wav->file, 0); // Placeholder for file size
|
||||
fwrite((uint8_t[]){'W','A','V','E'}, 1, 4, wav->file);
|
||||
|
||||
/* fmt chunk */
|
||||
fwrite((uint8_t[]){'f','m','t',' '}, 1, 4, wav->file);
|
||||
write_u32(wav->file, 16); // PCM chunk size
|
||||
write_u16(wav->file, 1); // Audio format (1 = PCM)
|
||||
write_u16(wav->file, 1); // Channels (1 = mono)
|
||||
write_u32(wav->file, sample_rate); // Sample rate
|
||||
write_u32(wav->file, sample_rate * 2); // Byte rate (sr * block_align)
|
||||
write_u16(wav->file, 2); // Block align (mono 16-bit = 2)
|
||||
write_u16(wav->file, 16); // Bits per sample
|
||||
|
||||
/* data chunk */
|
||||
fwrite((uint8_t[]){'d','a','t','a'}, 1, 4, wav->file);
|
||||
write_u32(wav->file, 0); // Placeholder for data size
|
||||
|
||||
return 1;
|
||||
}
|
||||
void wave_write_sample(wav_t *wav, int16_t sample) {
|
||||
write_u16(wav->file, (uint16_t)sample);
|
||||
wav->samples_written++;
|
||||
}
|
||||
|
||||
void wave_close(wav_t *wav) {
|
||||
|
||||
uint32_t data_size = wav->samples_written * 2;
|
||||
uint32_t file_size = 36 + data_size;
|
||||
|
||||
/* Patch RIFF size */
|
||||
fseek(wav->file, 4, SEEK_SET);
|
||||
write_u32(wav->file, file_size);
|
||||
|
||||
/* Patch data chunk size */
|
||||
fseek(wav->file, 40, SEEK_SET);
|
||||
write_u32(wav->file, data_size);
|
||||
|
||||
fclose(wav->file);
|
||||
}
|
||||
Reference in New Issue
Block a user