main.c 7.32 KB
Newer Older
Bob Ham's avatar
Bob Ham committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * Copyright (C) 2018 Purism SPC
 *
 * This file is part of Hægtesse.
 *
 * Hægtesse 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.
 *
 * Hægtesse 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 Hægtesse.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Bob Ham <bob.ham@puri.sm>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

Bob Ham's avatar
Bob Ham committed
25
#include "util.h"
purism's avatar
purism committed
26 27 28
#include "haeg-port.h"
#include "haeg-audio.h"
#include "haeg-port-monitor.h"
Bob Ham's avatar
Bob Ham committed
29 30 31 32 33 34 35 36 37 38 39 40 41

#include <glib.h>
#include <glib/gi18n.h>

#include <stdio.h>


#define TTY_CHUNK_SIZE   320
#define SAMPLE_LEN       2


struct haegtesse_data
{
purism's avatar
purism committed
42 43 44 45
  HaegPortMonitor     *monitor;
  HaegPort            *port;
  HaegAudio           *audio;
  guint                timeout_id;
Bob Ham's avatar
Bob Ham committed
46 47 48
};

static void
purism's avatar
purism committed
49
clear_port (struct haegtesse_data *data)
Bob Ham's avatar
Bob Ham committed
50
{
purism's avatar
purism committed
51 52 53
  haeg_audio_cork (data->audio);
  g_clear_object (&data->port);
  haeg_port_monitor_request_port (data->monitor);
Bob Ham's avatar
Bob Ham committed
54 55 56 57 58 59 60 61 62 63 64 65
}

static gboolean
timeout_cb (struct haegtesse_data *data)
{
  g_debug ("Timeout");

  /* Reopen port */
  /* Without this, the audio on repeated calls can get corrupted,
     probably because the sample stream loses a byte and so every
     successive (2-byte) sample is actually byte 1 of one sample and
     byte 0 of the next.  Re-opening the port seems to reset this. */
purism's avatar
purism committed
66
  clear_port (data);
Bob Ham's avatar
Bob Ham committed
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98

  data->timeout_id = 0;
  return FALSE;
}


static void
stop_timeout (struct haegtesse_data *data)
{
  if (!data->timeout_id)
    {
      return;
    }

  g_source_remove (data->timeout_id);
  data->timeout_id = 0;
}


static void
start_timeout (struct haegtesse_data *data)
{
  if (data->timeout_id)
    {
      g_source_remove (data->timeout_id);
    }

  data->timeout_id =
    g_timeout_add (1000, (GSourceFunc)timeout_cb, data);
}


purism's avatar
purism committed
99 100 101 102 103 104 105 106 107 108 109
static void
reopen_port (struct haegtesse_data *data)
{
  stop_timeout (data);
  clear_port (data);
}


static gboolean
transfer_spk (struct haegtesse_data *data,
              gsize *written)
Bob Ham's avatar
Bob Ham committed
110 111 112 113
{
  void *buf = NULL;
  size_t buf_size = 320;
  gsize bytes_read;
purism's avatar
purism committed
114
  GError *error = NULL;
Bob Ham's avatar
Bob Ham committed
115 116

  /* Get the write buffer */
purism's avatar
purism committed
117
  haeg_audio_begin_write (data->audio, &buf, &buf_size);
Bob Ham's avatar
Bob Ham committed
118 119

  /* Read audio data from the TTY */
purism's avatar
purism committed
120 121
  bytes_read = haeg_port_read (data->port, buf, buf_size, &error);
  if (error || bytes_read == 0)
Bob Ham's avatar
Bob Ham committed
122
    {
purism's avatar
purism committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136
      if (error)
        {
          g_warning ("%s", error->message);
          g_error_free (error);
        }
      else
        {
          g_warning ("EOF from TTY port");
        }

      haeg_audio_cancel_write (data->audio);
      reopen_port (data);

      return FALSE;
Bob Ham's avatar
Bob Ham committed
137
    }
purism's avatar
purism committed
138 139 140 141

  /* Write audio data to speaker stream */
  haeg_audio_write (data->audio, buf, bytes_read);

Bob Ham's avatar
Bob Ham committed
142 143
  g_debug ("Wrote %zi bytes to PA speaker stream", (size_t)bytes_read);

purism's avatar
purism committed
144 145 146 147 148
  if (written)
    {
      *written = bytes_read;
    }
  return TRUE;
Bob Ham's avatar
Bob Ham committed
149 150 151
}


purism's avatar
purism committed
152
static gboolean
Bob Ham's avatar
Bob Ham committed
153 154
transfer_mic (struct haegtesse_data *data)
{
purism's avatar
purism committed
155 156 157 158 159 160 161
  void *buf = NULL;
  size_t write_len = 0;
  gsize written;
  gboolean not_ready = FALSE;
  GError *error = NULL;

  haeg_audio_get_mic_buffer (data->audio, &buf, &write_len);
Bob Ham's avatar
Bob Ham committed
162 163

  // Check we actually have data
purism's avatar
purism committed
164
  if (write_len == 0)
Bob Ham's avatar
Bob Ham committed
165 166
    {
      g_debug ("Empty mic buffer");
purism's avatar
purism committed
167
      return TRUE;
Bob Ham's avatar
Bob Ham committed
168 169
    }

purism's avatar
purism committed
170 171 172 173 174
  // Write audio data to the TTY
  written = haeg_port_write (data->port,
                             buf, write_len,
                             &not_ready, &error);
  if (error)
Bob Ham's avatar
Bob Ham committed
175
    {
purism's avatar
purism committed
176 177 178 179
      g_warning ("%s", error->message);
      g_error_free (error);
      reopen_port (data);
      return FALSE;
Bob Ham's avatar
Bob Ham committed
180
    }
purism's avatar
purism committed
181
  if (not_ready)
Bob Ham's avatar
Bob Ham committed
182
    {
purism's avatar
purism committed
183
      return TRUE;
Bob Ham's avatar
Bob Ham committed
184
    }
185
  g_debug ("Wrote %" G_GSIZE_FORMAT" bytes (of %zi) to TTY port from mic buffer",
purism's avatar
purism committed
186
           written, write_len);
Bob Ham's avatar
Bob Ham committed
187 188

  // Update buffer
purism's avatar
purism committed
189 190 191
  haeg_audio_update_mic_buffer (data->audio, written);

  return TRUE;
Bob Ham's avatar
Bob Ham committed
192 193 194
}


purism's avatar
purism committed
195 196 197
static void
port_error_cb (struct haegtesse_data *data,
               const gchar *message)
Bob Ham's avatar
Bob Ham committed
198
{
purism's avatar
purism committed
199 200 201 202 203 204 205 206 207
  g_warning ("Error on TTY port: %s", message);
  reopen_port (data);
}


static void
port_read_ready_cb (struct haegtesse_data *data)
{
  gboolean ok;
Bob Ham's avatar
Bob Ham committed
208 209 210 211 212

  /* Cancel any running timeout */
  stop_timeout (data);

  /* "Uncork" the streams if this is the first chunk */
purism's avatar
purism committed
213
  if (haeg_audio_is_corked (data->audio))
Bob Ham's avatar
Bob Ham committed
214 215
    {
      /* Uncork */
purism's avatar
purism committed
216
      haeg_audio_uncork (data->audio);
Bob Ham's avatar
Bob Ham committed
217 218 219
    }

  /* Move data from modem to speaker stream */
purism's avatar
purism committed
220 221 222 223 224
  ok = transfer_spk (data, NULL);
  if (!ok)
    {
      return;
    }
Bob Ham's avatar
Bob Ham committed
225 226 227 228 229 230 231 232

  /* Move data from microphone stream to modem */
  /* We have to do this here because for unknown reasons, having a
     write watch on the GIOChannel doesn't give us callbacks quickly
     enough.  We basically use the read watch to provide appropriate
     timing for the writing.  If there is mic data from PulseAudio in
     the buffer and we can write some of it without blocking, great.
     If not, so be it.  This works. */
purism's avatar
purism committed
233 234
  ok = transfer_mic (data);
  if (!ok)
Bob Ham's avatar
Bob Ham committed
235
    {
purism's avatar
purism committed
236
      return;
Bob Ham's avatar
Bob Ham committed
237 238
    }

purism's avatar
purism committed
239 240
  /* Start the timeout running */
  start_timeout (data);
Bob Ham's avatar
Bob Ham committed
241 242 243 244
}


static void
purism's avatar
purism committed
245 246
port_cb (struct haegtesse_data *data,
         const gchar *port_file_name)
Bob Ham's avatar
Bob Ham committed
247
{
purism's avatar
purism committed
248
  GError *error;
Bob Ham's avatar
Bob Ham committed
249

purism's avatar
purism committed
250
  if (data->port)
Bob Ham's avatar
Bob Ham committed
251
    {
purism's avatar
purism committed
252 253
      g_warning ("Received port signal while port object exists");
      return;
Bob Ham's avatar
Bob Ham committed
254 255
    }

purism's avatar
purism committed
256
  g_debug ("Creating new TTY port `%s'", port_file_name);
Bob Ham's avatar
Bob Ham committed
257

purism's avatar
purism committed
258 259 260 261 262
  error = NULL;
  data->port = haeg_port_new (port_file_name,
                              SAMPLE_LEN,
                              &error);
  if (error)
Bob Ham's avatar
Bob Ham committed
263
    {
purism's avatar
purism committed
264 265 266
      g_warning ("%s", error->message);
      g_error_free (error);
      return;
Bob Ham's avatar
Bob Ham committed
267 268
    }

purism's avatar
purism committed
269 270 271 272
  g_signal_connect_swapped (data->port, "error",
                            G_CALLBACK (port_error_cb), data);
  g_signal_connect_swapped (data->port, "read-ready",
                            G_CALLBACK (port_read_ready_cb), data);
Bob Ham's avatar
Bob Ham committed
273

purism's avatar
purism committed
274
  haeg_port_start (data->port);
Bob Ham's avatar
Bob Ham committed
275 276 277 278 279 280
}


static void
set_up (struct haegtesse_data *data)
{
purism's avatar
purism committed
281
  data->audio = haeg_audio_new (TTY_CHUNK_SIZE * 2);
Bob Ham's avatar
Bob Ham committed
282

purism's avatar
purism committed
283 284 285 286
  data->monitor = haeg_port_monitor_new ();
  g_signal_connect_swapped (data->monitor, "port",
                            G_CALLBACK (port_cb), data);
  haeg_port_monitor_request_port (data->monitor);
Bob Ham's avatar
Bob Ham committed
287 288 289 290 291 292
}


static void
tear_down (struct haegtesse_data *data)
{
purism's avatar
purism committed
293 294 295 296 297 298 299 300
  if (data->port)
    {
      haeg_port_stop (data->port);
      stop_timeout (data);
    }
  g_clear_object (&data->port);
  g_clear_object (&data->monitor);
  g_clear_object (&data->audio);
Bob Ham's avatar
Bob Ham committed
301 302 303 304
}


static void
purism's avatar
purism committed
305
run ()
Bob Ham's avatar
Bob Ham committed
306 307 308 309 310 311 312 313
{
  struct haegtesse_data data;
  GMainLoop * loop;

  memset (&data, 0, sizeof (struct haegtesse_data));
  set_up (&data);

  loop = g_main_loop_new (NULL, FALSE);
purism's avatar
purism committed
314
  printf (APPLICATION_NAME " started\n");
Bob Ham's avatar
Bob Ham committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
  g_main_loop_run (loop);
  g_main_loop_unref (loop);

  tear_down (&data);
}


int
main (int argc, char **argv)
{
  GError *error = NULL;
  GOptionContext *context;
  gboolean ok;

  setlocale(LC_ALL, "");

  GOptionEntry options[] =
    {
      { NULL }
    };

  context = g_option_context_new ("- transfer audio data between modem and PulseAudio");
  g_option_context_add_main_entries (context, options, NULL);
  ok = g_option_context_parse (context, &argc, &argv, &error);
  if (!ok)
    {
      g_print ("Error parsing options: %s\n", error->message);
    }

purism's avatar
purism committed
344
  run ();
Bob Ham's avatar
Bob Ham committed
345 346 347

  return 0;
}