aboutsummaryrefslogtreecommitdiffstats
path: root/src/fodtmf-send.c
diff options
context:
space:
mode:
authorMattias Andrée <maandree@member.fsf.org>2015-12-19 22:03:01 +0100
committerMattias Andrée <maandree@member.fsf.org>2015-12-19 22:03:01 +0100
commit6944594bfabe9fd3eee7e53381048b491acf6dbb (patch)
tree6c776e0db51fa9d88d74524daf87916a5e6c117b /src/fodtmf-send.c
parenta nice readme (diff)
downloadfodtmf-6944594bfabe9fd3eee7e53381048b491acf6dbb.tar.gz
fodtmf-6944594bfabe9fd3eee7e53381048b491acf6dbb.tar.bz2
fodtmf-6944594bfabe9fd3eee7e53381048b491acf6dbb.tar.xz
m
Signed-off-by: Mattias Andrée <maandree@member.fsf.org>
Diffstat (limited to 'src/fodtmf-send.c')
-rw-r--r--src/fodtmf-send.c503
1 files changed, 503 insertions, 0 deletions
diff --git a/src/fodtmf-send.c b/src/fodtmf-send.c
new file mode 100644
index 0000000..b841e1f
--- /dev/null
+++ b/src/fodtmf-send.c
@@ -0,0 +1,503 @@
+/**
+ * Copyright © 2015 Mattias Andrée (maandree@member.fsf.org)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "common.h"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <alloca.h>
+#include <getopt.h>
+#include <math.h> /* -lm */
+#include <alsa/asoundlib.h> /* dep: alsa-lib (pkg-config alsa) */
+
+
+
+#define UTYPE uint32_t
+#define SMIN INT32_MIN
+#define SMAX INT32_MAX
+#define FORMAT SND_PCM_FORMAT_U32
+
+#define SAMPLE_RATE 52000 /* Hz */
+#define DURATION 100 /* ms */ /* 100 ms → 10 Bd → 5 B/s */
+#define LATENCY 100000 /* µs */
+
+
+
+/**
+ * Number of samples per tone.
+ */
+#define N (SAMPLE_RATE / 1000 * DURATION)
+
+
+
+/**
+ * The process's name.
+ */
+static const char* argv0;
+
+/**
+ * Audio descriptor.
+ */
+static snd_pcm_t* sound_handle = NULL;
+
+/**
+ * Audio playback buffer for each nibble.
+ */
+static UTYPE buffers[16][N];
+
+/**
+ * The number of parity tones in the hamming code.
+ */
+static int parity = 3;
+
+/**
+ * Whether to add an additional parity tone (of all
+ * data tones) in addition to those numerated by `parity`.
+ */
+static int use_extra_parity = 0;
+
+/**
+ * Whether to add redundancy frequencies in the tones.
+ */
+static int use_redundant_freq = 0;
+
+/**
+ * Frequencies multiple for the redundant frequencies.
+ */
+static double redundant_freq_mul = 0;
+
+/**
+ * Data buffer used for error correcting code support.
+ */
+static int* data;
+
+/**
+ * Code buffer used for error correcting code support.
+ */
+static int* code;
+
+/**
+ * The number elements that can be stored in `data`.
+ */
+static int data_n;
+
+/**
+ * The number elements that can be stored in `code`.
+ */
+static int code_n;
+
+/**
+ * File descriptor to write the audio to (with any metadata)
+ * rather than sending it to the audio subsystem.
+ *
+ * This is available for developers of the receiver.
+ * It is not intend to be used for anything else.
+ */
+static int output_fd = -1;
+
+
+
+/**
+ * Use to accept signals, without doing anything
+ * about them, just be interrupted.
+ *
+ * @param signo The received signal.
+ */
+static void signoop(int signo)
+{
+ (void) signo;
+}
+
+
+/**
+ * Get the tones used to transmit a nibble.
+ *
+ * @param nibble The nibble.
+ * @param low Output parameter for the lower frequency.
+ * @param high Output parameter for the higher frequency.
+ */
+static void get_freq(int nibble, int* low, int* high)
+{
+ static const int LOW[] = { 697, 770, 852, 941 };
+ static const int HIGH[] = { 1209, 1336, 1477, 1663 };
+
+ *low = LOW[(nibble >> 0) & 0x03];
+ *high = HIGH[(nibble >> 2) & 0x03];
+}
+
+
+/**
+ * Initialise to before for each nibble.
+ */
+static void init_buffers(void)
+{
+#define GENERATE_TONE(tone) sin(2 * M_PI * ((double)i / (SAMPLE_RATE / (tone))))
+
+ size_t i;
+ UTYPE* buffer;
+ int j, low, high;
+
+ for (j = 0; j < 16; j++)
+ {
+ buffer = buffers[j];
+ get_freq(j, &low, &high);
+
+ if (use_redundant_freq)
+ for (i = 0; i < N; i++)
+ buffer[i] = (GENERATE_TONE(low) +
+ GENERATE_TONE(high) +
+ GENERATE_TONE(low * redundant_freq_mul) +
+ GENERATE_TONE(high * redundant_freq_mul)) *
+ (SMAX / 4) - SMIN;
+ else
+ for (i = 0; i < N; i++)
+ buffer[i] = (GENERATE_TONE(low) + GENERATE_TONE(high)) *
+ (SMAX / 2) - SMIN;
+ }
+}
+
+
+/**
+ * Transmit a nibble.
+ *
+ * @param n The nibble.
+ * @return 0 on success, -1 on error.
+ */
+static int send_nibble(int n)
+{
+ UTYPE* buffer = buffers[n];
+ snd_pcm_sframes_t frames;
+ int r;
+
+ if (output_fd >= 0)
+ {
+ char* buf = (char*)buffer;
+ size_t ptr = 0;
+ ssize_t wrote;
+ while (ptr < N)
+ {
+ wrote = write(output_fd, buf + ptr, N - ptr);
+ if (wrote < 0)
+ return -1;
+ ptr += (size_t)write;
+ }
+ return 0;
+ }
+
+ r = frames = snd_pcm_writei(sound_handle, buffer, N);
+ if (frames < 0)
+ r = frames = snd_pcm_recover(sound_handle, r, 0 /* do not print error reason? */);
+ if (r < 0)
+ {
+ fprintf(stderr, "%s: snd_pcm_writei: %s\n", argv0, snd_strerror(r));
+ errno = 0;
+ return -1;
+ }
+ if ((r > 0) && ((size_t)r < N))
+ printf("%s: short write: expected %zu, wrote %zu\n", argv0, N, (size_t)frames);
+
+ return 0;
+}
+
+
+/**
+ * Transmit a byte.
+ *
+ * @param c The byte.
+ * @return 0 on success, -1 on error.
+ */
+static int send_byte(int c)
+{
+#ifdef DEBUG
+ fprintf(stderr, "%s: sending byte: %i\n", argv0, c);
+#endif
+ if (send_nibble((c >> 0) & 0x0F)) return -1;
+ if (send_nibble((c >> 4) & 0x0F)) return -1;
+ return 0;
+}
+
+
+/**
+ * Transmit a byte with error correcting code.
+ *
+ * This function uses a buffer so that it can
+ * create an error correcting code. The bytes
+ * are not transmitted until this buffer has
+ * been filled.
+ *
+ * @param c The byte, the negative of that byte (intended
+ * only for `CHAR_END` and `CHAR_CANCEL`) to fill
+ * the remained of the buffer with the byte.
+ * Note that if a negative value is used, it is
+ * no necessary that anything will happen.
+ * @return 0 on success, -1 on error.
+ */
+static int send_byte_with_ecc(int c)
+{
+ static int ptr = 0;
+ int i, j, d, p;
+
+ if (parity < 2)
+ {
+ if (c < 0)
+ return 0;
+ if (send_byte(c))
+ return -1;
+ if (parity)
+ if (send_byte(c))
+ return -1;
+ if (use_extra_parity)
+ if (send_byte(c))
+ return -1;
+ return 0;
+ }
+
+ /* Fill buffer. */
+ if (c < 0)
+ {
+ if (ptr > 0)
+ while (ptr < data_n)
+ data[ptr++] = -c;
+ }
+ else
+ data[ptr++] = c;
+
+ /* Is it full? */
+ if (ptr < data_n)
+ return 0;
+ ptr = 0;
+
+ /* Hamming code. */
+ memset(code, 0, code_n * sizeof(*code));
+ for (i = 1, j = 0; i <= (1 << parity) - 1; i++)
+ {
+ if ((i & -i) == i)
+ continue;
+ for (d = i, p = 0; d; d >>= 1, p++)
+ if (d & 1)
+ code[(1 << p) - 1] ^= data[j];
+ code[i - 1] = data[j];
+ j++;
+ }
+ if (use_extra_parity)
+ for (i = 0; i < data_n; i++)
+ code[(1 << parity) - 1] ^= data[i];
+
+ /* Transmit. */
+ for (i = 0; i < code_n; i++)
+ if (send_byte(code[i]))
+ return -1;
+
+ return 0;
+}
+
+
+/**
+ * Transmit a chunk of bytes.
+ *
+ * @param buf The chunk to transmit.
+ * @param n The number of bytes in the chunk.
+ * @return 0 on success, -1 on failure.
+ */
+static int send_chunk(char* buf, size_t n)
+{
+ size_t i;
+ int c;
+
+ for (i = 0; i < n; i++)
+ {
+ c = buf[i];
+ if ((c == CHAR_ESCAPE) || (c == CHAR_CANCEL) || (c == CHAR_END))
+ if (send_byte_with_ecc(CHAR_ESCAPE))
+ goto qfile;
+ if (send_byte_with_ecc(c))
+ goto qfile;
+ }
+
+ return 0;
+ qfile:
+ errno = 0;
+ fail:
+ return -1;
+}
+
+
+/**
+ * Read all input from stdin and transmit it as it is being read.
+ *
+ * @return 0 on success, -1 on failure.
+ */
+static int send_file(void)
+{
+ char buf[1024];
+ ssize_t n;
+
+ for (;;)
+ {
+ n = read(STDIN_FILENO, buf, sizeof(buf));
+ if (n < 0) goto fail;
+ if (n == 0) break;
+ if (send_chunk(buf, (size_t)n))
+ goto fail;
+ }
+
+ return 0;
+ fail:
+ return -1;
+}
+
+
+/**
+ * Read all input from stdin, and then transmit it.
+ *
+ * @return 0 on success, -1 on failure.
+ */
+static int send_term(void)
+{
+ char* buf = NULL;
+ size_t size = 0;
+ size_t ptr = 0;
+ ssize_t n;
+ void* new;
+ int saved_errno;
+
+ for (;;)
+ {
+ if (ptr == size)
+ {
+ size = size ? (size << 1) : 128;
+ new = realloc(buf, size);
+ if (new == NULL)
+ goto fail;
+ buf = new;
+ }
+ n = read(STDIN_FILENO, buf + ptr, size - ptr);
+ if (n < 0) goto fail;
+ if (n == 0) break;
+ ptr += (size_t)n;
+ }
+ if (send_chunk(buf, ptr))
+ goto fail;
+
+ return 0;
+ fail:
+ saved_errno = errno;
+ free(buf);
+ errno = saved_errno;
+ return -1;
+}
+
+
+/**
+ * Transmit a file over audio.
+ *
+ * @param argc The number of elements in `argv`.
+ * @param argv Command line arguments.
+ * @return 0 on success, 1 on failure.
+ */
+int main(int argc, char* argv[])
+{
+ struct sigaction act;
+ int r, rc = 1;
+
+ /* Parse command line. */
+ argv0 = argc ? argv[0] : "";
+ for (;;)
+ {
+ r = getopt (argc, argv, "f:pr:o:");
+ if (r == -1)
+ break;
+ else if (r == 'f') use_redundant_freq = 1, redundant_freq_mul = atof(optarg);
+ else if (r == 'p') use_extra_parity = 1;
+ else if (r == 'r') parity = atoi(optarg);
+ else if (r == 'o') output_fd = atoi(optarg);
+ else if (r != '?')
+ abort();
+ }
+
+ /* Set up signal handling. */
+ siginterrupt(SIGTERM, 1);
+ siginterrupt(SIGQUIT, 1);
+ siginterrupt(SIGINT, 1);
+ siginterrupt(SIGHUP, 1);
+ sigemptyset(&(act.sa_mask));
+ act.sa_handler = signoop;
+ act.sa_flags = 0;
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGQUIT, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+ sigprocmask(SIG_SETMASK, &(act.sa_mask), NULL);
+
+ /* Generate the tones to play. */
+ init_buffers();
+ /* Generate buffers for error correcting code. */
+ data_n = (1 << parity) - parity - 1;
+ code_n = (1 << parity) - 1 + use_extra_parity;
+ data = alloca(data_n * sizeof(*data));
+ code = alloca(code_n * sizeof(*code));
+
+ /* Set up audio. */
+ if (output_fd >= 0)
+ goto no_audio;
+ r = snd_pcm_open(&sound_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
+ if (r < 0)
+ return fprintf(stderr, "%s: snd_pcm_open: %s\n", *argv, snd_strerror(r)), 1;
+ if (sound_handle == NULL)
+ perror("snd_pcm_open");
+ /* Configure audio. */
+ r = snd_pcm_set_params(sound_handle, FORMAT, SND_PCM_ACCESS_RW_INTERLEAVED, 1 /* channels */,
+ SAMPLE_RATE, 1 /* allow resampling? */, LATENCY);
+ if (r < 0)
+ return fprintf(stderr, "%s: snd_pcm_set_params: %s\n", *argv, snd_strerror(r)), 1;
+ no_audio:
+
+ /* Send message. */
+ if (isatty(STDIN_FILENO))
+ {
+ if (send_term())
+ goto fail;
+ }
+ else
+ if (send_file())
+ goto fail;
+
+ /* Mark end of transmission. */
+ if (send_byte_with_ecc(CHAR_END)) goto cleanup;
+ if (send_byte_with_ecc(-CHAR_END)) goto cleanup;
+
+ /* Done! */
+ rc = 0;
+ goto cleanup;
+ fail:
+ if (errno)
+ perror(argv0);
+ cleanup:
+ if (output_fd >= 0)
+ snd_pcm_close(sound_handle);
+ /* Mark aborted transmission. */
+ if (rc)
+ {
+ send_byte_with_ecc(CHAR_CANCEL);
+ send_byte_with_ecc(-CHAR_CANCEL);
+ }
+ return rc;
+}
+