aboutsummaryrefslogblamecommitdiffstats
path: root/src/send.c
blob: b841e1fe0c99e4b7fc2013b544e843413cadd8ec (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16















                                                                        

                   
                  
                   
                   


                   
                   

                   

                                                                 
 










                                                                      
 
 
   
                              
   
                                          
 

 


                      
                         



                    

                                      




                                         
















                                                          




                                                      


















                                                      








                                                            
 







                                                
 













                                                             





                                                       




                                        
 
                                                                                 
  
           

                   
  




                               

                               



                                                                  


                               
                                                                  
                              














                                         
  














                                                       
                                                       




                                                                                      
                
                


                                                                                   



           







                                         
 


                                                      

                                               


           
















                                                             
 
                     
                 
  














                           
                    
            

                  
                            

                           
      
                    
  
                   
                   
             

          
                     

                                                 








                                         


                                         
  
                 
                              
                           




                
 
   































































































                                                                      

                              

                                                   

                                             

                                
                       
                
  
                           
                              

          
                                         

                  
                                                                                    

                                                
                                                   


                        
  














                                                 




                                                   

                     
                     
                  

                                                                         
                                                                                

                           


                                                                                               
            
                                                                                      
          
  
                     
                           
     

                      
     



                    


                                                   
  


               
      

                  
         

                                
                                  




                                       
            

 
/**
 * 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;
}