1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
// SPDX-License-Identifier: MIT
#include <stdbool.h>
#include <stdio.h>
#include <dbus/dbus.h>
#include <string.h>
#include <alsa/asoundlib.h>
struct q6voiced {
char card[64];
snd_pcm_t *tx, *rx;
};
static void q6voiced_open(struct q6voiced *v)
{
if (v->tx)
return; /* Already active */
/*
* Opening the PCM devices starts the stream.
* This should be replaced by a codec2codec link probably.
*/
if (snd_pcm_open(&v->tx, v->card, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) < 0 ||
snd_pcm_set_params(v->tx, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 8000, 0, 500000) < 0 ||
snd_pcm_prepare(v->tx) < 0) {
perror("Failed to open tx");
goto error;
}
if (snd_pcm_open(&v->rx, v->card, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) < 0 ||
snd_pcm_set_params(v->rx, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 8000, 0, 500000) < 0 ||
snd_pcm_prepare(v->rx) < 0) {
perror("Failed to open rx");
goto error;
}
printf("PCM devices were opened.\n");
return;
error:
if (v->tx) {
snd_pcm_close(v->tx);
v->tx = NULL;
}
if (v->rx) {
snd_pcm_close(v->rx);
v->rx = NULL;
}
}
static void q6voiced_close(struct q6voiced *v)
{
if (!v->tx)
return; /* Not active */
snd_pcm_close(v->tx);
snd_pcm_close(v->rx);
v->rx = v->tx = NULL;
printf("PCM devices were closed.\n");
}
/* See ModemManager-enums.h */
enum MMCallState {
MM_CALL_STATE_DIALING = 1,
MM_CALL_STATE_RINGING_OUT = 2,
MM_CALL_STATE_ACTIVE = 4,
};
static bool mm_state_is_active(int state)
{
/*
* Some modems seem to be incapable of reporting DIALING -> ACTIVE.
* Therefore we also consider DIALING/RINGING_OUT as active.
*/
switch (state) {
case MM_CALL_STATE_DIALING:
case MM_CALL_STATE_RINGING_OUT:
case MM_CALL_STATE_ACTIVE:
return true;
default:
return false;
}
}
static void handle_signal(struct q6voiced *v, DBusMessage *msg, DBusError *err)
{
// Check if the message is a signal from the correct interface and with the correct name
// TODO: Should we also check the call state for oFono?
if (dbus_message_is_signal(msg, "org.ofono.VoiceCallManager", "CallAdded")) {
q6voiced_open(v);
} else if (dbus_message_is_signal(msg, "org.ofono.VoiceCallManager", "CallRemoved")) {
q6voiced_close(v);
} else if (dbus_message_is_signal(msg, "org.freedesktop.ModemManager1.Call", "StateChanged")) {
/*
* For ModemManager call objects are created in advance
* and not necessarily immediately started.
* Need to listen for call state changes.
*/
int old_state, new_state;
if (!dbus_message_get_args(msg, err,
DBUS_TYPE_INT32, &old_state,
DBUS_TYPE_INT32, &new_state,
DBUS_TYPE_INVALID))
return;
if (old_state == new_state)
return; /* No change */
if (mm_state_is_active(new_state))
q6voiced_open(v);
else if (mm_state_is_active(old_state) && !mm_state_is_active(new_state))
q6voiced_close(v);
}
}
int main(int argc, char **argv)
{
struct q6voiced v = {0};
DBusMessage *msg;
DBusConnection *conn;
DBusError err;
if (argc != 2) {
fprintf(stderr, "Usage: q6voiced hw:<card>,<device>\n");
return 1;
}
strlcpy(v.card, argv[1], sizeof(v.card) - 1);
// See: http://web.archive.org/web/20100309103206/http://dbus.freedesktop.org/doc/dbus/libdbus-tutorial.html
// "Receiving a Signal"
dbus_error_init(&err);
conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Connection error: %s\n", err.message);
dbus_error_free(&err);
return 1;
}
if (!conn)
return 1;
dbus_bus_add_match(conn, "type='signal',interface='org.ofono.VoiceCallManager'", &err);
dbus_bus_add_match(conn, "type='signal',interface='org.freedesktop.ModemManager1.Call'", &err);
dbus_connection_flush(conn);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Match error: %s\n", err.message);
dbus_error_free(&err);
return 1;
}
// Loop listening for signals being emmitted
while (dbus_connection_read_write(conn, -1)) {
// We need to process all received messages
while (msg = dbus_connection_pop_message(conn)) {
handle_signal(&v, msg, &err);
if (dbus_error_is_set(&err)) {
fprintf(stderr, "Failed to handle signal: %s\n", err.message);
dbus_error_free(&err);
}
dbus_message_unref(msg);
}
}
return 0;
}
|