40#define PAYLOAD_TYPE_BIT (1u << 31)
43#define LENGTH_MASK 0x7FFFFFFFu
59 if (x > 1.0)
return 1.0;
60 if (x < -1.0)
return -1.0;
69 double s =
clamp(sample);
70 int pcm = (s >= 0.0) ? (
int)(s * 32767.0 + 0.5)
71 : (int)(s * 32767.0 - 0.5);
72 if (pcm > 32767) pcm = 32767;
73 if (pcm < -32767) pcm = -32767;
82 return (
double)(float)((
double)pcm / 32767.0);
85static unsigned lsb_capacity(
unsigned signal_len)
92static unsigned lsb_encode(
const double *host,
double *output,
94 const unsigned char *data,
unsigned data_len,
97 unsigned capacity = lsb_capacity(signal_len);
98 if (data_len == 0 || capacity == 0)
100 if (data_len > capacity)
104 memcpy(output, host, signal_len *
sizeof(
double));
108 unsigned header = data_len | flags;
110 unsigned bit = (header >> i) & 1;
113 pcm = (pcm & ~1) | (
int)bit;
118 for (
unsigned b = 0; b < data_len; b++) {
119 unsigned char ch = data[b];
120 for (
unsigned bit_idx = 0; bit_idx < 8; bit_idx++) {
121 unsigned sample_idx =
HEADER_BITS + b * 8 + bit_idx;
122 unsigned bit = (ch >> bit_idx) & 1;
124 pcm = (pcm & ~1) | (
int)bit;
138 unsigned bit = (unsigned)(pcm & 1);
144static unsigned lsb_decode(
const double *stego,
unsigned signal_len,
145 unsigned char *data_out,
unsigned max_len)
153 unsigned capacity = lsb_capacity(signal_len);
154 if (msg_len == 0 || msg_len > capacity)
157 unsigned decode_len = msg_len;
158 if (decode_len > max_len)
159 decode_len = max_len;
161 for (
unsigned b = 0; b < decode_len; b++) {
162 unsigned char ch = 0;
163 for (
unsigned bit_idx = 0; bit_idx < 8; bit_idx++) {
164 unsigned sample_idx =
HEADER_BITS + b * 8 + bit_idx;
166 unsigned bit = (unsigned)(pcm & 1);
167 ch |= (
unsigned char)(bit << bit_idx);
190#define FREQ_LO 18500.0
191#define FREQ_HI 19500.0
195static unsigned chip_samples(
double sample_rate)
197 return (
unsigned)(
CHIP_MS * sample_rate / 1000.0);
200static unsigned freq_capacity_cs(
unsigned signal_len,
unsigned cs)
202 if (cs == 0)
return 0;
203 unsigned total_chips = signal_len / cs;
209static unsigned freq_capacity(
unsigned signal_len,
double sample_rate)
211 return freq_capacity_cs(signal_len, chip_samples(sample_rate));
223static double *_bfsk_sin_lo = NULL;
224static double *_bfsk_sin_hi = NULL;
225static unsigned _bfsk_cs = 0;
227static void _bfsk_setup(
double sample_rate)
229 unsigned cs = chip_samples(sample_rate);
230 if (cs == _bfsk_cs && _bfsk_sin_lo != NULL)
235 _bfsk_sin_lo = malloc(cs *
sizeof(
double));
236 _bfsk_sin_hi = malloc(cs *
sizeof(
double));
239 for (
unsigned s = 0; s < cs; s++) {
240 double t = (double)s / sample_rate;
241 _bfsk_sin_lo[s] = sin(2.0 * M_PI *
FREQ_LO * t);
242 _bfsk_sin_hi[s] = sin(2.0 * M_PI *
FREQ_HI * t);
258 unsigned chip_idx,
unsigned cs,
259 const double *carrier)
261 unsigned start = chip_idx * cs;
262 for (
unsigned s = 0; s < cs && (start + s) < signal_len; s++)
263 output[start + s] +=
TONE_AMP * carrier[s];
266static unsigned freq_encode(
const double *host,
double *output,
267 unsigned signal_len,
double sample_rate,
268 const unsigned char *data,
unsigned data_len,
271 unsigned cs = chip_samples(sample_rate);
272 unsigned capacity = freq_capacity_cs(signal_len, cs);
273 if (data_len == 0 || capacity == 0)
275 if (data_len > capacity)
279 memcpy(output, host, signal_len *
sizeof(
double));
282 _bfsk_setup(sample_rate);
285 unsigned header = data_len | flags;
287 unsigned bit = (header >> i) & 1;
289 bit ? _bfsk_sin_hi : _bfsk_sin_lo);
293 for (
unsigned b = 0; b < data_len; b++) {
294 unsigned char ch = data[b];
295 for (
unsigned bit_idx = 0; bit_idx < 8; bit_idx++) {
297 unsigned bit = (ch >> bit_idx) & 1;
299 bit ? _bfsk_sin_hi : _bfsk_sin_lo);
308 unsigned chip_idx,
unsigned cs,
309 const double *sin_lo,
const double *sin_hi,
312 unsigned start = chip_idx * cs;
313 double corr_lo = 0.0, corr_hi = 0.0;
314 for (
unsigned s = 0; s < cs && (start + s) < signal_len; s++) {
315 corr_lo += stego[start + s] * sin_lo[s];
316 corr_hi += stego[start + s] * sin_hi[s];
318 if (fabs(corr_hi) > fabs(corr_lo)) {
319 if (corr_out) *corr_out = fabs(corr_hi);
322 if (corr_out) *corr_out = fabs(corr_lo);
326static unsigned freq_decode(
const double *stego,
unsigned signal_len,
328 unsigned char *data_out,
unsigned max_len)
330 unsigned cs = chip_samples(sample_rate);
331 if (cs == 0 || max_len == 0)
334 unsigned total_chips = signal_len / cs;
339 _bfsk_setup(sample_rate);
342 unsigned raw_header = 0;
345 _bfsk_sin_lo, _bfsk_sin_hi,
347 raw_header |= (bit << i);
352 unsigned capacity = freq_capacity_cs(signal_len, cs);
353 if (msg_len == 0 || msg_len > capacity)
356 unsigned decode_len = msg_len;
357 if (decode_len > max_len)
358 decode_len = max_len;
360 for (
unsigned b = 0; b < decode_len; b++) {
361 unsigned char ch = 0;
362 for (
unsigned bit_idx = 0; bit_idx < 8; bit_idx++) {
365 _bfsk_sin_lo, _bfsk_sin_hi,
367 ch |= (
unsigned char)(bit << bit_idx);
389#define SPECTEXT_FREQ_LO 18000.0
390#define SPECTEXT_FREQ_HI 23500.0
391#define SPECTEXT_COL_MS 30.0
392#define SPECTEXT_COLS_PER_CHAR 8
393#define SPECTEXT_TARGET_SR 48000.0
394#define SPECTEXT_AMP 0.02
395#define SPECTEXT_NORM_PEAK 0.9
396#define SPECTEXT_PAD_SEC 0.25
399#define SPECTEXT_SEC_PER_CHAR \
400 (SPECTEXT_COL_MS / 1000.0 * SPECTEXT_COLS_PER_CHAR)
407 if (available <= 0.0)
return 0;
419static unsigned spectext_capacity(
unsigned signal_len,
double sample_rate)
421 double duration_sec = (double)signal_len / sample_rate;
424 unsigned lsb_cap = lsb_capacity(out_len);
425 return (vis_cap < lsb_cap) ? vis_cap : lsb_cap;
428static unsigned spectext_encode(
const double *host,
double *output,
429 unsigned signal_len,
double sample_rate,
430 const unsigned char *data,
unsigned data_len,
433 double duration_sec = (double)signal_len / sample_rate;
440 double *mixed = malloc(out_len *
sizeof(
double));
451 memcpy(mixed, host, out_len *
sizeof(
double));
461 const char *vis_text;
464 snprintf(vis_label,
sizeof(vis_label),
"[BIN %uB]", data_len);
465 vis_text = vis_label;
467 vis_text = (
const char *)data;
470 unsigned text_len = (unsigned)strlen(vis_text);
471 if (text_len > vis_chars)
472 text_len = vis_chars;
475 char *vis_substr = malloc(text_len + 1);
477 memcpy(vis_substr, vis_text, text_len);
478 vis_substr[text_len] =
'\0';
482 double *specbuf = calloc(out_len,
sizeof(
double));
494 if (pad_samples + spec_samples > out_len)
495 spec_samples = (out_len > pad_samples) ? out_len - pad_samples : 0;
496 for (
unsigned i = 0; i < spec_samples; i++)
497 mixed[pad_samples + i] += specbuf[i] * scale;
505 unsigned encoded = lsb_encode(mixed, output, out_len,
506 data, data_len, flags);
521 unsigned fft_len = 4096;
522 if (fft_len > signal_len)
523 fft_len = signal_len;
526 unsigned spec_len = fft_len / 2 + 1;
527 double *mag = malloc(spec_len *
sizeof(
double));
532 if (signal_len > fft_len)
533 offset = (signal_len - fft_len) / 2;
535 double *windowed = malloc(fft_len *
sizeof(
double));
536 double *win = malloc(fft_len *
sizeof(
double));
540 for (
unsigned i = 0; i < fft_len; i++)
541 windowed[i] = signal[offset + i] * win[i];
547 double bin_hz = sample_rate / (double)fft_len;
550 if (bin_hi >= spec_len)
551 bin_hi = spec_len - 1;
555 for (
unsigned k = bin_lo; k <= bin_hi; k++) {
556 energy += mag[k] * mag[k];
563 if (count == 0)
return 0.0;
564 return sqrt(energy / (
double)count);
580 return lsb_capacity(signal_len);
582 return spectext_capacity(signal_len, sample_rate);
584 return freq_capacity(signal_len, sample_rate);
589 unsigned signal_len,
double sample_rate,
590 const unsigned char *data,
unsigned data_len,
591 int method,
unsigned flags)
604 "frequency-band steganography requires sample_rate >= 40 kHz", 0);
607 return lsb_encode(host, output, signal_len, data, data_len, flags);
609 return spectext_encode(host, output, signal_len, sample_rate,
610 data, data_len, flags);
612 return freq_encode(host, output, signal_len, sample_rate,
613 data, data_len, flags);
617 unsigned signal_len,
double sample_rate,
618 const unsigned char *data,
unsigned data_len,
627 unsigned char *data_out,
unsigned max_len,
640 return lsb_decode(stego, signal_len, data_out, max_len);
642 return freq_decode(stego, signal_len, sample_rate,
647 unsigned signal_len,
double sample_rate,
648 const char *message,
int method)
652 (
const unsigned char *)message,
653 (
unsigned)strlen(message), method, 0);
658 char *message_out,
unsigned max_msg_len,
664 (
unsigned char *)message_out,
665 max_msg_len - 1, method);
666 message_out[decoded] =
'\0';
671 double sample_rate,
int *payload_type_out)
677 int found_method = -1;
678 unsigned found_header = 0;
681 if (sample_rate >= 40000.0) {
682 unsigned cs = chip_samples(sample_rate);
684 unsigned total_chips = signal_len / cs;
686 _bfsk_setup(sample_rate);
688 unsigned raw_header = 0;
689 double corr_sum = 0.0;
693 signal, signal_len, i, cs,
694 _bfsk_sin_lo, _bfsk_sin_hi, &corr);
695 raw_header |= (bit << i);
699 unsigned capacity = freq_capacity_cs(signal_len, cs);
701 double threshold = 0.25 *
TONE_AMP * (double)cs / 2.0;
703 if (msg_len > 0 && msg_len <= capacity &&
704 avg_corr >= threshold) {
706 found_header = raw_header;
713 if (found_method < 0 && signal_len >
HEADER_BITS) {
716 unsigned capacity = lsb_capacity(signal_len);
718 if (msg_len > 0 && msg_len <= capacity) {
723 if (ultra_rms > 1e-4) {
725 found_header = raw_header;
728 found_header = raw_header;
732 found_header = raw_header;
737 if (found_method >= 0 && payload_type_out != NULL)
A mini library of DSP (Digital Signal Processing) routines.
@ MD_ERR_INVALID_SIZE
A size or count argument is invalid (e.g.
@ MD_ERR_INVALID_RANGE
A range or bound is invalid (e.g.
@ MD_ERR_ALLOC_FAILED
A memory allocation failed.
@ MD_ERR_NULL_POINTER
A required pointer argument is NULL.
unsigned MD_resample_output_len(unsigned input_len, double in_rate, double out_rate)
Compute the output buffer size needed for resampling.
#define MD_STEG_SPECTEXT
Steganography method: hybrid LSB + spectrogram text art.
unsigned MD_spectrogram_text(double *output, unsigned max_len, const char *text, double freq_lo, double freq_hi, double duration_sec, double sample_rate)
Synthesise audio that displays readable text in a spectrogram.
#define MD_STEG_TYPE_TEXT
Payload type flag: text (null-terminated string).
void MD_Gen_Hann_Win(double *out, unsigned n)
Generate a Hanning (Hann) window of length n.
#define MD_STEG_TYPE_BINARY
Payload type flag: binary (raw byte buffer).
unsigned MD_resample(const double *input, unsigned input_len, double *output, unsigned max_output_len, double in_rate, double out_rate, unsigned num_zero_crossings, double kaiser_beta)
Resample a signal from one sample rate to another using polyphase sinc interpolation.
void MD_lowpass_brickwall(double *signal, unsigned len, double cutoff_hz, double sample_rate)
Apply a brickwall lowpass filter to a signal in-place.
#define MD_STEG_FREQ_BAND
Steganography method: near-ultrasonic frequency-band modulation (BFSK).
void MD_magnitude_spectrum(const double *signal, unsigned N, double *mag_out)
Compute the magnitude spectrum of a real-valued signal.
#define MD_STEG_LSB
Steganography method: least-significant-bit encoding.
Internal header for cross-file dependencies within the minidsp module.
#define MD_CHECK(cond, code, msg, retval)
Check a precondition in a function that returns a value.
#define MD_CHECK_VOID(cond, code, msg)
Check a precondition in a void function.
#define PAYLOAD_TYPE_BIT
Bit 31 of the header: payload type flag (0 = text, 1 = binary).
unsigned MD_steg_capacity(unsigned signal_len, double sample_rate, int method)
Compute the maximum message length (in bytes) that can be hidden.
unsigned MD_steg_encode_bytes(const double *host, double *output, unsigned signal_len, double sample_rate, const unsigned char *data, unsigned data_len, int method)
Encode arbitrary binary data into a host audio signal.
#define HEADER_BITS
Bits needed for the message-length header (32-bit unsigned).
static unsigned spectext_vis_capacity(double duration_sec)
Maximum number of visually displayable characters for a given duration.
unsigned MD_steg_decode_bytes(const double *stego, unsigned signal_len, double sample_rate, unsigned char *data_out, unsigned max_len, int method)
Decode binary data from a stego audio signal.
static int double_to_pcm16(double sample)
Convert a double sample in [-1, 1] to a signed 16-bit PCM value.
#define SPECTEXT_SEC_PER_CHAR
Seconds per character in the spectrogram visual.
static double pcm16_to_double(int pcm)
Convert a signed 16-bit PCM value back to double in [-1, 1].
unsigned MD_steg_decode(const double *stego, unsigned signal_len, double sample_rate, char *message_out, unsigned max_msg_len, int method)
Decode a secret message from a stego audio signal.
void md_steg_teardown(void)
Tear down the BFSK sine carrier cache.
#define SPECTEXT_NORM_PEAK
MD_spectrogram_text peak.
#define SPECTEXT_PAD_SEC
Silence before spectrogram text (s).
#define CHIP_MS
Duration of one bit chip (ms).
#define FREQ_LO
Carrier for bit 0 (Hz).
static unsigned spectext_output_len(unsigned signal_len, double sample_rate)
Compute the output signal length at 48 kHz for a given input.
#define FREQ_HI
Carrier for bit 1 (Hz).
static unsigned encode_common(const double *host, double *output, unsigned signal_len, double sample_rate, const unsigned char *data, unsigned data_len, int method, unsigned flags)
Shared encode logic for both text and binary public API functions.
#define SPECTEXT_AMP
Spectrogram art amplitude.
static double clamp(double x)
Clamp a double to [-1, 1].
unsigned MD_steg_encode(const double *host, double *output, unsigned signal_len, double sample_rate, const char *message, int method)
Encode a secret message into a host audio signal.
#define TONE_AMP
Additive tone amplitude.
int MD_steg_detect(const double *signal, unsigned signal_len, double sample_rate, int *payload_type_out)
Detect which steganography method (if any) was used to encode a signal.
#define SPECTEXT_FREQ_HI
Top of visual band (Hz, below Nyquist).
#define SPECTEXT_TARGET_SR
Output sample rate (Hz).
#define SPECTEXT_FREQ_LO
Bottom of visual band (Hz).
static unsigned decode_one_bit(const double *stego, unsigned signal_len, unsigned chip_idx, unsigned cs, const double *sin_lo, const double *sin_hi, double *corr_out)
Decode one bit by correlating a chip against precomputed BFSK carriers.
static void encode_one_bit(double *output, unsigned signal_len, unsigned chip_idx, unsigned cs, const double *carrier)
Encode one bit by adding a precomputed BFSK tone burst at the given chip.
#define LENGTH_MASK
Mask to extract the message length from the raw header (bits 0–30).
static double spectext_ultrasonic_rms(const double *signal, unsigned signal_len, double sample_rate)
Probe for ultrasonic energy in the 18-24 kHz band.
static unsigned lsb_read_header(const double *signal)
Read the raw 32-bit LSB header from the first HEADER_BITS samples.