| 1 |
/* |
| 2 |
* Copyright (C) 2010 Nokia Corporation. |
| 3 |
* |
| 4 |
* Contact: Maemo MMF Audio <mmf-audio@projects.maemo.org> |
| 5 |
* or Jyri Sarha <jyri.sarha@nokia.com> |
| 6 |
* |
| 7 |
* These PulseAudio Modules are free software; you can redistribute |
| 8 |
* it and/or modify it under the terms of the GNU Lesser General Public |
| 9 |
* License as published by the Free Software Foundation |
| 10 |
* version 2.1 of the License. |
| 11 |
* |
| 12 |
* This library is distributed in the hope that it will be useful, |
| 13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 15 |
* Lesser General Public License for more details. |
| 16 |
* |
| 17 |
* You should have received a copy of the GNU Lesser General Public |
| 18 |
* License along with this library; if not, write to the Free Software |
| 19 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 |
| 20 |
* USA. |
| 21 |
*/ |
| 22 |
#include "cmtspeech-connection.h" |
| 23 |
|
| 24 |
#include "cmtspeech-mainloop-handler.h" |
| 25 |
#include "module-voice-api.h" |
| 26 |
#include "cmtspeech-sink-input.h" |
| 27 |
#include <pulsecore/rtpoll.h> |
| 28 |
#include <pulsecore/core-rtclock.h> |
| 29 |
#include <pulse/rtclock.h> |
| 30 |
#include <pulse/timeval.h> |
| 31 |
#include <poll.h> |
| 32 |
#include <errno.h> |
| 33 |
|
| 34 |
#include <cmtspeech.h> |
| 35 |
|
| 36 |
/* TODO: Get rid of this and use asyncmsgq instead. */ |
| 37 |
enum cmt_speech_thread_state { |
| 38 |
CMT_UNINITIALIZED = 0, |
| 39 |
CMT_STARTING, |
| 40 |
CMT_RUNNING, |
| 41 |
CMT_ASK_QUIT, |
| 42 |
CMT_QUIT |
| 43 |
}; |
| 44 |
|
| 45 |
/* This should be only used for memblock free cb - cmtspeech_free_cb - below |
| 46 |
and it is initialized in cmtspeech_connection_init(). */ |
| 47 |
static struct userdata *userdata = NULL; |
| 48 |
|
| 49 |
static uint ul_frame_count = 0; |
| 50 |
|
| 51 |
#define CMTSPEECH_CLEANUP_TIMER_TIMEOUT ((pa_usec_t)(5 * PA_USEC_PER_SEC)) |
| 52 |
|
| 53 |
enum cmtspeech_cleanup_state_name { |
| 54 |
CMTSPEECH_CLEANUP_TIMER_INACTIVE = 0, |
| 55 |
CMTSPEECH_CLEANUP_TIMER_ACTIVE, |
| 56 |
CMTSPEECH_CLEANUP_IN_PROGRESS |
| 57 |
}; |
| 58 |
|
| 59 |
enum { |
| 60 |
CMTSPEECH_HANDLER_CLOSE_CONNECTION, |
| 61 |
}; |
| 62 |
|
| 63 |
typedef struct cmtspeech_handler { |
| 64 |
pa_msgobject parent; |
| 65 |
struct userdata *u; |
| 66 |
} cmtspeech_handler; |
| 67 |
|
| 68 |
PA_DEFINE_PRIVATE_CLASS(cmtspeech_handler, pa_msgobject); |
| 69 |
#define CMTSPEECH_HANDLER(o) cmtspeech_handler_cast(o) |
| 70 |
|
| 71 |
static void cmtspeech_handler_free(pa_object *o) { |
| 72 |
cmtspeech_handler *h = CMTSPEECH_HANDLER(o); |
| 73 |
|
| 74 |
pa_log_info("Free called"); |
| 75 |
pa_xfree(h); |
| 76 |
} |
| 77 |
|
| 78 |
static void close_cmtspeech_on_error(struct userdata *u); |
| 79 |
|
| 80 |
static int cmtspeech_handler_process_msg(pa_msgobject *o, int code, void *ud, int64_t offset, pa_memchunk *chunk) { |
| 81 |
cmtspeech_handler *h = CMTSPEECH_HANDLER(o); |
| 82 |
struct userdata *u; |
| 83 |
|
| 84 |
cmtspeech_handler_assert_ref(h); |
| 85 |
pa_assert_se(u = h->u); |
| 86 |
|
| 87 |
switch (code) { |
| 88 |
case CMTSPEECH_HANDLER_CLOSE_CONNECTION: |
| 89 |
pa_log_debug("CMTSPEECH_HANDLER_CLOSE_CONNECTION"); |
| 90 |
close_cmtspeech_on_error(u); |
| 91 |
return 0; |
| 92 |
default: |
| 93 |
pa_log_error("Unknown message code %d", code); |
| 94 |
return -1; |
| 95 |
} |
| 96 |
} |
| 97 |
|
| 98 |
static pa_msgobject *cmtspeech_handler_new(struct userdata *u) { |
| 99 |
cmtspeech_handler *h; |
| 100 |
|
| 101 |
pa_assert(u); |
| 102 |
pa_assert(u->core); |
| 103 |
pa_assert_se(h = pa_msgobject_new(cmtspeech_handler)); |
| 104 |
|
| 105 |
h->parent.parent.free = cmtspeech_handler_free; |
| 106 |
h->parent.process_msg = cmtspeech_handler_process_msg; |
| 107 |
h->u = u; |
| 108 |
|
| 109 |
return (pa_msgobject *)h; |
| 110 |
} |
| 111 |
|
| 112 |
/* This is usually called from sink IO-thread */ |
| 113 |
static void cmtspeech_free_cb(void *p) { |
| 114 |
cmtspeech_t *cmtspeech; |
| 115 |
|
| 116 |
if (!p) |
| 117 |
return; |
| 118 |
|
| 119 |
if (!userdata) { |
| 120 |
pa_log_error("userdata not set, cmtspeech buffer %p was not freed!", p); |
| 121 |
return; |
| 122 |
} |
| 123 |
|
| 124 |
pa_mutex_lock(userdata->cmt_connection.cmtspeech_mutex); |
| 125 |
cmtspeech = userdata->cmt_connection.cmtspeech; |
| 126 |
if (!cmtspeech) { |
| 127 |
pa_log_error("cmtspeech not open, cmtspeech buffer %p was not freed!", p); |
| 128 |
} else { |
| 129 |
int ret; |
| 130 |
cmtspeech_buffer_t *buf = cmtspeech_dl_buffer_find_with_data(cmtspeech, (uint8_t*)p); |
| 131 |
if (buf != NULL) { |
| 132 |
if ((ret = cmtspeech_dl_buffer_release(cmtspeech, buf))) { |
| 133 |
pa_log_error("cmtspeech_dl_buffer_release(%p) failed return value %d.", (void *)buf, ret); |
| 134 |
} |
| 135 |
} else { |
| 136 |
pa_log_error("cmtspeech_dl_buffer_find_with_data() returned NULL, releasing buffer failed."); |
| 137 |
} |
| 138 |
} |
| 139 |
pa_mutex_unlock(userdata->cmt_connection.cmtspeech_mutex); |
| 140 |
} |
| 141 |
|
| 142 |
/* Called from sink IO-thread */ |
| 143 |
/* NOTE: If you ever see a seqfault when accessing these libcmtspeechdata owned |
| 144 |
* memblocks, then just free the cmtframes here after coping them to regular |
| 145 |
* pa_memblocks. The performance penalty should not be too severe. */ |
| 146 |
int cmtspeech_buffer_to_memchunk(struct userdata *u, cmtspeech_buffer_t *buf, pa_memchunk *chunk) { |
| 147 |
pa_assert_fp(u); |
| 148 |
pa_assert_fp(chunk); |
| 149 |
pa_assert_fp(buf); |
| 150 |
|
| 151 |
if (!buf->data) { |
| 152 |
pa_log_warn("No data in cmtspeech_buffer"); |
| 153 |
if (cmtspeech_dl_buffer_release(u->cmt_connection.cmtspeech, buf)) |
| 154 |
pa_log_warn("cmtspeech_dl_buffer_release() failed"); |
| 155 |
return -1; |
| 156 |
} |
| 157 |
|
| 158 |
chunk->memblock = pa_memblock_new_user(u->core->mempool, buf->data, (size_t) buf->size, cmtspeech_free_cb, TRUE); |
| 159 |
chunk->index = CMTSPEECH_DATA_HEADER_LEN; |
| 160 |
chunk->length = buf->count - CMTSPEECH_DATA_HEADER_LEN; |
| 161 |
|
| 162 |
return 0; |
| 163 |
} |
| 164 |
|
| 165 |
/* cmtspeech thread */ |
| 166 |
static inline |
| 167 |
int push_cmtspeech_buffer_to_dl_queue(struct userdata *u, cmtspeech_dl_buf_t *buf) { |
| 168 |
pa_assert_fp(u); |
| 169 |
pa_assert_fp(buf); |
| 170 |
|
| 171 |
if (pa_asyncq_push(u->cmt_connection.dl_frame_queue, (void *)buf, FALSE)) { |
| 172 |
int ret; |
| 173 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 174 |
|
| 175 |
pa_log_error("Failed to push dl frame to asyncq"); |
| 176 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 177 |
if ((ret = cmtspeech_dl_buffer_release(u->cmt_connection.cmtspeech, buf))) |
| 178 |
pa_log_error("cmtspeech_dl_buffer_release(%p) failed return value %d.", (void *)buf, ret); |
| 179 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 180 |
return -1; |
| 181 |
} |
| 182 |
|
| 183 |
ONDEBUG_TOKENS(fprintf(stderr, "D")); |
| 184 |
return 0; |
| 185 |
} |
| 186 |
|
| 187 |
/* cmtspeech thread */ |
| 188 |
static void update_uplink_frame_timing(struct userdata *u, cmtspeech_event_t *cmtevent) { |
| 189 |
int deadline_us; |
| 190 |
int64_t usec; |
| 191 |
|
| 192 |
pa_log_debug("msec= %d usec=%d rtclock=%d.%09ld", |
| 193 |
(int)cmtevent->msg.timing_config_ntf.msec, |
| 194 |
(int)cmtevent->msg.timing_config_ntf.usec, |
| 195 |
(int)cmtevent->msg.timing_config_ntf.tstamp.tv_sec, |
| 196 |
cmtevent->msg.timing_config_ntf.tstamp.tv_nsec); |
| 197 |
|
| 198 |
deadline_us = (cmtevent->msg.timing_config_ntf.msec % 20) * 1000 + cmtevent->msg.timing_config_ntf.usec; |
| 199 |
|
| 200 |
usec = ((int64_t) cmtevent->msg.timing_config_ntf.tstamp.tv_sec * 1000000) + |
| 201 |
(cmtevent->msg.timing_config_ntf.tstamp.tv_nsec/1000) + deadline_us; |
| 202 |
|
| 203 |
pa_log_debug("deadline at %lld (%d usec from msg receival)", usec, deadline_us); |
| 204 |
|
| 205 |
if (u->source && PA_SOURCE_IS_LINKED(u->source->state)) |
| 206 |
pa_asyncmsgq_post(u->source->asyncmsgq, PA_MSGOBJECT(u->source), |
| 207 |
VOICE_SOURCE_SET_UL_DEADLINE, NULL, usec, NULL, NULL); |
| 208 |
else |
| 209 |
pa_log_error("No destination where to send timing info"); |
| 210 |
} |
| 211 |
|
| 212 |
static void reset_call_stream_states(struct userdata *u) { |
| 213 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 214 |
|
| 215 |
pa_assert(u); |
| 216 |
|
| 217 |
if (c->streams_created) { |
| 218 |
pa_log_warn("DL/UL streams existed at reset, closing"); |
| 219 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 220 |
CMTSPEECH_MAINLOOP_HANDLER_DELETE_STREAMS, NULL, 0, NULL, NULL); |
| 221 |
c->streams_created = FALSE; |
| 222 |
} |
| 223 |
if (c->playback_running) { |
| 224 |
pa_log_warn("DL stream was open, closing"); |
| 225 |
c->playback_running = FALSE; |
| 226 |
} |
| 227 |
if (c->record_running) { |
| 228 |
pa_log_warn("UL stream was open, closing"); |
| 229 |
c->record_running = FALSE; |
| 230 |
ul_frame_count = 0; |
| 231 |
} |
| 232 |
} |
| 233 |
|
| 234 |
/* cmtspeech thread */ |
| 235 |
static int mainloop_cmtspeech(struct userdata *u) { |
| 236 |
int retsockets = 0; |
| 237 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 238 |
struct pollfd *pollfd; |
| 239 |
|
| 240 |
pa_assert(u); |
| 241 |
|
| 242 |
if (!c->cmt_poll_item) |
| 243 |
return 0; |
| 244 |
|
| 245 |
if (pa_atomic_load(&u->cmtspeech_server_status)) |
| 246 |
pa_rtpoll_set_timer_absolute(c->rtpoll, pa_rtclock_now() + CMTSPEECH_CLEANUP_TIMER_TIMEOUT); |
| 247 |
|
| 248 |
pollfd = pa_rtpoll_item_get_pollfd(c->cmt_poll_item, NULL); |
| 249 |
if (pollfd->revents & POLLIN) { |
| 250 |
cmtspeech_t *cmtspeech; |
| 251 |
int flags = 0, i = CMTSPEECH_CTRL_LEN; |
| 252 |
int res; |
| 253 |
|
| 254 |
/* locking note: hot path lock */ |
| 255 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 256 |
|
| 257 |
cmtspeech = c->cmtspeech; |
| 258 |
|
| 259 |
res = cmtspeech_check_pending(cmtspeech, &flags); |
| 260 |
if (res >= 0) |
| 261 |
retsockets = 1; |
| 262 |
|
| 263 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 264 |
|
| 265 |
if (res > 0) { |
| 266 |
if (flags & CMTSPEECH_EVENT_CONTROL) { |
| 267 |
cmtspeech_event_t cmtevent; |
| 268 |
|
| 269 |
/* locking note: this path is taken only very rarely */ |
| 270 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 271 |
|
| 272 |
i = cmtspeech_read_event(cmtspeech, &cmtevent); |
| 273 |
|
| 274 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 275 |
|
| 276 |
pa_log_debug("read cmtspeech event: state %d -> %d (type %d, ret %d).", |
| 277 |
cmtevent.prev_state, cmtevent.state, cmtevent.msg_type, i); |
| 278 |
|
| 279 |
if (i != 0) { |
| 280 |
pa_log_error("ERROR: unable to read event."); |
| 281 |
|
| 282 |
} else if (cmtevent.prev_state == CMTSPEECH_STATE_DISCONNECTED && |
| 283 |
cmtevent.state == CMTSPEECH_STATE_CONNECTED) { |
| 284 |
pa_log_debug("call starting."); |
| 285 |
reset_call_stream_states(u); |
| 286 |
|
| 287 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 288 |
CMTSPEECH_MAINLOOP_HANDLER_CREATE_STREAMS, NULL, 0, NULL, NULL); |
| 289 |
|
| 290 |
c->streams_created = TRUE; |
| 291 |
} else if (cmtevent.prev_state == CMTSPEECH_STATE_CONNECTED && |
| 292 |
cmtevent.state == CMTSPEECH_STATE_ACTIVE_DL && |
| 293 |
cmtevent.msg_type == CMTSPEECH_SPEECH_CONFIG_REQ) { |
| 294 |
pa_log_notice("speech start: srate=%u, format=%u, stream=%u", |
| 295 |
cmtevent.msg.speech_config_req.sample_rate, |
| 296 |
cmtevent.msg.speech_config_req.data_format, |
| 297 |
cmtevent.msg.speech_config_req.speech_data_stream); |
| 298 |
|
| 299 |
/* Ul is turned on when timing information is received */ |
| 300 |
|
| 301 |
pa_log_debug("enabling DL"); |
| 302 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 303 |
CMTSPEECH_MAINLOOP_HANDLER_CMT_DL_CONNECT, NULL, 0, NULL, NULL); |
| 304 |
c->playback_running = true; |
| 305 |
|
| 306 |
// start waiting for first dl frame |
| 307 |
c->first_dl_frame_received = false; |
| 308 |
} else if (cmtevent.prev_state == CMTSPEECH_STATE_ACTIVE_DLUL && |
| 309 |
cmtevent.state == CMTSPEECH_STATE_ACTIVE_DL && |
| 310 |
cmtevent.msg_type == CMTSPEECH_SPEECH_CONFIG_REQ) { |
| 311 |
|
| 312 |
pa_log_notice("speech update: srate=%u, format=%u, stream=%u", |
| 313 |
cmtevent.msg.speech_config_req.sample_rate, |
| 314 |
cmtevent.msg.speech_config_req.data_format, |
| 315 |
cmtevent.msg.speech_config_req.speech_data_stream); |
| 316 |
|
| 317 |
} else if (cmtevent.prev_state == CMTSPEECH_STATE_ACTIVE_DL && |
| 318 |
cmtevent.state == CMTSPEECH_STATE_ACTIVE_DLUL) { |
| 319 |
pa_log_debug("enabling UL"); |
| 320 |
|
| 321 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 322 |
CMTSPEECH_MAINLOOP_HANDLER_CMT_UL_CONNECT, NULL, 0, NULL, NULL); |
| 323 |
c->record_running = true; |
| 324 |
|
| 325 |
} else if (cmtevent.state == CMTSPEECH_STATE_ACTIVE_DLUL && |
| 326 |
cmtevent.msg_type == CMTSPEECH_TIMING_CONFIG_NTF) { |
| 327 |
update_uplink_frame_timing(u, &cmtevent); |
| 328 |
pa_log_debug("updated UL timing params"); |
| 329 |
|
| 330 |
} else if ((cmtevent.prev_state == CMTSPEECH_STATE_ACTIVE_DL || |
| 331 |
cmtevent.prev_state == CMTSPEECH_STATE_ACTIVE_DLUL) && |
| 332 |
cmtevent.state == CMTSPEECH_STATE_CONNECTED) { |
| 333 |
pa_log_notice("speech stop: stream=%u", |
| 334 |
cmtevent.msg.speech_config_req.speech_data_stream); |
| 335 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 336 |
CMTSPEECH_MAINLOOP_HANDLER_CMT_DL_DISCONNECT, NULL, 0, NULL, NULL); |
| 337 |
c->playback_running = FALSE; |
| 338 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 339 |
CMTSPEECH_MAINLOOP_HANDLER_CMT_UL_DISCONNECT, NULL, 0, NULL, NULL); |
| 340 |
c->record_running = FALSE; |
| 341 |
ul_frame_count = 0; |
| 342 |
|
| 343 |
} else if (cmtevent.prev_state == CMTSPEECH_STATE_CONNECTED && |
| 344 |
cmtevent.state == CMTSPEECH_STATE_DISCONNECTED) { |
| 345 |
pa_log_debug("call terminated."); |
| 346 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 347 |
CMTSPEECH_MAINLOOP_HANDLER_DELETE_STREAMS, NULL, 0, NULL, NULL); |
| 348 |
c->streams_created = FALSE; |
| 349 |
reset_call_stream_states(u); |
| 350 |
|
| 351 |
} else if (cmtevent.msg_type == CMTSPEECH_EVENT_RESET) { |
| 352 |
pa_log_warn("modem reset detected"); |
| 353 |
close_cmtspeech_on_error(u); |
| 354 |
/* cmtspeech handle now null so return immediately */ |
| 355 |
return retsockets; |
| 356 |
|
| 357 |
} else { |
| 358 |
pa_log_error("Unrecognized cmtspeech event: state %d -> %d (type %d, ret %d).", |
| 359 |
cmtevent.prev_state, cmtevent.state, cmtevent.msg_type, i); |
| 360 |
if (cmtevent.state == CMTSPEECH_STATE_DISCONNECTED) |
| 361 |
reset_call_stream_states(u); |
| 362 |
} |
| 363 |
} |
| 364 |
|
| 365 |
/* step: check for SSI data events */ |
| 366 |
if (flags & CMTSPEECH_EVENT_DL_DATA) { |
| 367 |
cmtspeech_buffer_t *buf; |
| 368 |
static int counter = 0; |
| 369 |
bool cmtspeech_active = false; |
| 370 |
|
| 371 |
counter++; |
| 372 |
if (counter < 10) |
| 373 |
pa_log_debug("SSI: DL frame available, read %d bytes.", i); |
| 374 |
|
| 375 |
/* locking note: another hot path lock */ |
| 376 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 377 |
cmtspeech_active = cmtspeech_is_active(c->cmtspeech); |
| 378 |
i = cmtspeech_dl_buffer_acquire(cmtspeech, &buf); |
| 379 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 380 |
|
| 381 |
if (i < 0) { |
| 382 |
pa_log_error("Invalid DL frame received, cmtspeech_dl_buffer_acquire returned %d", i); |
| 383 |
} else { |
| 384 |
if (counter < 10 ) |
| 385 |
pa_log_debug("DL (audio len %d) frame's first bytes %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", |
| 386 |
buf->count - CMTSPEECH_DATA_HEADER_LEN, |
| 387 |
buf->data[0], buf->data[1], buf->data[1], buf->data[3], |
| 388 |
buf->data[4], buf->data[5], buf->data[6], buf->data[7]); |
| 389 |
|
| 390 |
if (c->playback_running) { |
| 391 |
if (c->first_dl_frame_received != true) { |
| 392 |
c->first_dl_frame_received = true; |
| 393 |
pa_log_debug("DL frame received, turn DL routing on..."); |
| 394 |
} |
| 395 |
(void)push_cmtspeech_buffer_to_dl_queue(u, buf); |
| 396 |
|
| 397 |
} else if (cmtspeech_active != true) { |
| 398 |
pa_log_debug("DL frame received before ACTIVE_DL state, dropping..."); |
| 399 |
} |
| 400 |
} |
| 401 |
} |
| 402 |
} |
| 403 |
} else { |
| 404 |
/* pollfd timer expired and no events. */ |
| 405 |
if (pa_atomic_cmpxchg(&u->cmtspeech_cleanup_state, CMTSPEECH_CLEANUP_TIMER_ACTIVE, CMTSPEECH_CLEANUP_IN_PROGRESS)) { |
| 406 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 407 |
if (!pa_atomic_load(&u->cmtspeech_server_status) && c->cmtspeech) { |
| 408 |
if (u->server_inactive_timeout <= pa_rtclock_now()) { |
| 409 |
pa_log_debug("cmtspeech cleanup timer checking server status."); |
| 410 |
if (cmtspeech_is_active(c->cmtspeech)) { |
| 411 |
pa_log_debug("cmtspeech still active, forcing cleanup"); |
| 412 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 413 |
CMTSPEECH_MAINLOOP_HANDLER_CMT_DL_DISCONNECT, NULL, 0, NULL, NULL); |
| 414 |
pa_asyncmsgq_post(pa_thread_mq_get()->outq, u->mainloop_handler, |
| 415 |
CMTSPEECH_MAINLOOP_HANDLER_CMT_UL_DISCONNECT, NULL, 0, NULL, NULL); |
| 416 |
cmtspeech_state_change_error(c->cmtspeech); |
| 417 |
} |
| 418 |
pa_rtpoll_set_timer_disabled(c->rtpoll); |
| 419 |
pa_atomic_store(&u->cmtspeech_cleanup_state, CMTSPEECH_CLEANUP_TIMER_INACTIVE); |
| 420 |
pa_log_debug("cmtspeech cleanup timer inactive in cmtspeech mainloop."); |
| 421 |
} else { |
| 422 |
pa_rtpoll_set_timer_relative(c->rtpoll, CMTSPEECH_CLEANUP_TIMER_TIMEOUT); |
| 423 |
pa_atomic_store(&u->cmtspeech_cleanup_state, CMTSPEECH_CLEANUP_TIMER_ACTIVE); |
| 424 |
pa_log_debug("cmtspeech cleanup timer timeout updated in cmtspeech mainloop."); |
| 425 |
} |
| 426 |
} else { |
| 427 |
pa_rtpoll_set_timer_disabled(c->rtpoll); |
| 428 |
pa_atomic_store(&u->cmtspeech_cleanup_state, CMTSPEECH_CLEANUP_TIMER_INACTIVE); |
| 429 |
pa_log_debug("cmtspeech cleanup timer inactive in cmtspeech mainloop (call active or cmtspeech closed)."); |
| 430 |
} |
| 431 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 432 |
} else if (!pa_atomic_load(&u->cmtspeech_server_status) && c->cmtspeech == NULL) { |
| 433 |
pa_log_debug("cmtspeech cleanup timer inactive in cmtspeech mainloop (2)."); |
| 434 |
pa_rtpoll_set_timer_disabled(c->rtpoll); |
| 435 |
} |
| 436 |
} |
| 437 |
|
| 438 |
return retsockets; |
| 439 |
} |
| 440 |
|
| 441 |
/* cmtspeech thread */ |
| 442 |
static int check_cmtspeech_connection(struct cmtspeech_connection *c) { |
| 443 |
static uint counter = 0; |
| 444 |
|
| 445 |
if (c->cmtspeech) |
| 446 |
return 0; |
| 447 |
|
| 448 |
/* locking note: not on the hot path */ |
| 449 |
|
| 450 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 451 |
|
| 452 |
c->cmtspeech = cmtspeech_open(); |
| 453 |
|
| 454 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 455 |
|
| 456 |
if (!c->cmtspeech) { |
| 457 |
if (counter++ < 5) |
| 458 |
pa_log_error("cmtspeech_open() failed"); |
| 459 |
return -1; |
| 460 |
} else if (counter > 0) { |
| 461 |
pa_log_debug("cmtspeech_open() OK"); |
| 462 |
counter = 0; |
| 463 |
} |
| 464 |
return 0; |
| 465 |
} |
| 466 |
|
| 467 |
/* cmtspeech thread */ |
| 468 |
static void pollfd_update(struct cmtspeech_connection *c) { |
| 469 |
if (c->cmt_poll_item) { |
| 470 |
pa_rtpoll_item_free(c->cmt_poll_item); |
| 471 |
c->cmt_poll_item = NULL; |
| 472 |
} |
| 473 |
if (c->cmtspeech) { |
| 474 |
pa_rtpoll_item *i = pa_rtpoll_item_new(c->rtpoll, PA_RTPOLL_NEVER, 1); |
| 475 |
struct pollfd *pollfd = pa_rtpoll_item_get_pollfd(i, NULL); |
| 476 |
/* locking note: a hot path lock */ |
| 477 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 478 |
pollfd->fd = cmtspeech_descriptor(c->cmtspeech); |
| 479 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 480 |
pollfd->events = POLLIN; |
| 481 |
pollfd->revents = 0; |
| 482 |
|
| 483 |
c->cmt_poll_item = i; |
| 484 |
|
| 485 |
} else { |
| 486 |
pa_log_debug("No cmtspeech connection"); |
| 487 |
} |
| 488 |
|
| 489 |
if (c->thread_state_poll_item) { |
| 490 |
pa_rtpoll_item_free(c->thread_state_poll_item); |
| 491 |
c->thread_state_poll_item = NULL; |
| 492 |
} |
| 493 |
|
| 494 |
c->thread_state_poll_item = pa_rtpoll_item_new_fdsem(c->rtpoll, PA_RTPOLL_NORMAL, c->thread_state_change); |
| 495 |
} |
| 496 |
|
| 497 |
/** |
| 498 |
* Closes the cmtspeech instance after an unrecoverable |
| 499 |
* error has been detected. |
| 500 |
* |
| 501 |
* In most cases, the connection to the modem has been lost and its |
| 502 |
* state is unknown. As a recovery mechanism, we close the library |
| 503 |
* instance and restart from a known state. |
| 504 |
*/ |
| 505 |
/* cmtspeech thread */ |
| 506 |
static void close_cmtspeech_on_error(struct userdata *u) |
| 507 |
{ |
| 508 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 509 |
pa_bool_t was_active = c->streams_created; |
| 510 |
|
| 511 |
pa_assert(u); |
| 512 |
|
| 513 |
pa_log_debug("closing the modem instance"); |
| 514 |
|
| 515 |
reset_call_stream_states(u); |
| 516 |
|
| 517 |
if (u->sink_input && PA_SINK_INPUT_IS_LINKED(u->sink_input->state) && |
| 518 |
u->sink_input->sink && u->sink_input->sink->asyncmsgq) { |
| 519 |
pa_assert_se(pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), |
| 520 |
PA_SINK_INPUT_MESSAGE_FLUSH_DL, NULL, 0, NULL) == 0); |
| 521 |
} else { |
| 522 |
cmtspeech_buffer_t *buf; |
| 523 |
pa_log_debug("DL stream not connected. Flushing the queue locally"); |
| 524 |
while((buf = pa_asyncq_pop(c->dl_frame_queue, 0))) { |
| 525 |
if (cmtspeech_dl_buffer_release(c->cmtspeech, buf)) { |
| 526 |
pa_log_error("Freeing cmtspeech buffer failed!"); |
| 527 |
} |
| 528 |
} |
| 529 |
} |
| 530 |
|
| 531 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 532 |
if (was_active == TRUE) |
| 533 |
pa_log_error("closing modem instance when interface still active"); |
| 534 |
if (cmtspeech_close(c->cmtspeech)) |
| 535 |
pa_log_error("cmtspeech_close() failed"); |
| 536 |
c->cmtspeech = NULL; |
| 537 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 538 |
} |
| 539 |
|
| 540 |
/* cmtspeech thread */ |
| 541 |
static void thread_func(void *udata) { |
| 542 |
struct userdata *u = udata; |
| 543 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 544 |
|
| 545 |
pa_assert(u); |
| 546 |
|
| 547 |
pa_log_debug("cmtspeech thread starting up"); |
| 548 |
|
| 549 |
if (u->core->realtime_scheduling) |
| 550 |
pa_make_realtime(u->core->realtime_priority - 1); |
| 551 |
|
| 552 |
pa_thread_mq_install(&c->thread_mq); |
| 553 |
|
| 554 |
c->cmtspeech = cmtspeech_open(); |
| 555 |
|
| 556 |
pa_assert_se(pa_atomic_cmpxchg(&c->thread_state, CMT_STARTING, CMT_RUNNING)); |
| 557 |
|
| 558 |
while(1) { |
| 559 |
int ret; |
| 560 |
|
| 561 |
if (check_cmtspeech_connection(c)) { |
| 562 |
pa_rtpoll_set_timer_relative(c->rtpoll, 500000); |
| 563 |
} |
| 564 |
|
| 565 |
pollfd_update(c); |
| 566 |
|
| 567 |
if (0 > (ret = pa_rtpoll_run(c->rtpoll, TRUE))) { |
| 568 |
pa_log_error("running rtpoll failed (%d) (fd %d)", ret, cmtspeech_descriptor(c->cmtspeech)); |
| 569 |
close_cmtspeech_on_error(u); |
| 570 |
} |
| 571 |
|
| 572 |
if (pa_atomic_load(&c->thread_state) == CMT_ASK_QUIT) { |
| 573 |
pa_log_debug("cmtspeech thread quiting"); |
| 574 |
goto finish; |
| 575 |
} |
| 576 |
|
| 577 |
/* note: cmtspeech can be closed in DBus thread */ |
| 578 |
if (c->cmtspeech == NULL) { |
| 579 |
pa_log_notice("closing and reopening cmtspeech device"); |
| 580 |
continue; |
| 581 |
} |
| 582 |
|
| 583 |
if (0 > mainloop_cmtspeech(u)) { |
| 584 |
goto fail; |
| 585 |
} |
| 586 |
|
| 587 |
} |
| 588 |
|
| 589 |
|
| 590 |
/**/ |
| 591 |
fail: |
| 592 |
pa_log_error("Trying to unload myself"); |
| 593 |
pa_asyncmsgq_post(c->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); |
| 594 |
|
| 595 |
pa_log_debug("Waiting for quit command..."); |
| 596 |
pa_fdsem_wait(c->thread_state_change); |
| 597 |
pa_assert(pa_atomic_load(&c->thread_state) == CMT_ASK_QUIT); |
| 598 |
|
| 599 |
finish: |
| 600 |
close_cmtspeech_on_error(u); |
| 601 |
|
| 602 |
pa_assert_se(pa_atomic_cmpxchg(&c->thread_state, CMT_ASK_QUIT, CMT_QUIT)); |
| 603 |
|
| 604 |
pa_log_debug("cmtspeech thread ended"); |
| 605 |
} |
| 606 |
|
| 607 |
static int priv_cmtspeech_to_pa_prio(int cmtspprio) |
| 608 |
{ |
| 609 |
if (cmtspprio == CMTSPEECH_TRACE_ERROR) |
| 610 |
return PA_LOG_ERROR; |
| 611 |
|
| 612 |
if (cmtspprio == CMTSPEECH_TRACE_INFO) |
| 613 |
return PA_LOG_INFO; |
| 614 |
|
| 615 |
return PA_LOG_DEBUG; |
| 616 |
} |
| 617 |
|
| 618 |
static void priv_cmtspeech_trace_handler_f(int priority, const char *message, va_list args) |
| 619 |
{ |
| 620 |
pa_log_levelv_meta(priv_cmtspeech_to_pa_prio(priority), |
| 621 |
"libcmtspeechdata", |
| 622 |
0, |
| 623 |
NULL, |
| 624 |
message, |
| 625 |
args); |
| 626 |
} |
| 627 |
|
| 628 |
/* Main thread */ |
| 629 |
int cmtspeech_connection_init(struct userdata *u) |
| 630 |
{ |
| 631 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 632 |
|
| 633 |
pa_assert(u); |
| 634 |
pa_assert(!userdata); /* To make sure we are the only instance running. */ |
| 635 |
|
| 636 |
/* Initialized static pointer for memblock free function */ |
| 637 |
userdata = u; |
| 638 |
|
| 639 |
c->cmt_handler = cmtspeech_handler_new(u); |
| 640 |
pa_atomic_store(&c->thread_state, CMT_STARTING); |
| 641 |
c->thread_state_change = pa_fdsem_new(); |
| 642 |
c->rtpoll = pa_rtpoll_new(); |
| 643 |
c->cmt_poll_item = NULL; |
| 644 |
pa_thread_mq_init(&c->thread_mq, u->core->mainloop, c->rtpoll); |
| 645 |
c->dl_frame_queue = pa_asyncq_new(4); |
| 646 |
|
| 647 |
c->cmtspeech = NULL; |
| 648 |
c->cmtspeech_mutex = pa_mutex_new(FALSE, FALSE); |
| 649 |
|
| 650 |
cmtspeech_init(); |
| 651 |
cmtspeech_trace_toggle(CMTSPEECH_TRACE_ERROR, true); |
| 652 |
cmtspeech_trace_toggle(CMTSPEECH_TRACE_INFO, true); |
| 653 |
cmtspeech_trace_toggle(CMTSPEECH_TRACE_STATE_CHANGE, true); |
| 654 |
cmtspeech_trace_toggle(CMTSPEECH_TRACE_IO, true); |
| 655 |
cmtspeech_trace_toggle(CMTSPEECH_TRACE_DEBUG, true); |
| 656 |
cmtspeech_set_trace_handler(priv_cmtspeech_trace_handler_f); |
| 657 |
|
| 658 |
c->call_ul = FALSE; |
| 659 |
c->call_dl = FALSE; |
| 660 |
c->call_emergency = FALSE; |
| 661 |
|
| 662 |
c->first_dl_frame_received = FALSE; |
| 663 |
c->record_running = FALSE; |
| 664 |
c->playback_running = FALSE; |
| 665 |
c->streams_created = FALSE; |
| 666 |
|
| 667 |
if (!(c->thread = pa_thread_new("cmtspeech", thread_func, u))) { |
| 668 |
pa_log_error("Failed to create thread."); |
| 669 |
pa_atomic_store(&c->thread_state, CMT_QUIT); |
| 670 |
cmtspeech_connection_unload(u); |
| 671 |
return -1; |
| 672 |
} |
| 673 |
|
| 674 |
return 0; |
| 675 |
} |
| 676 |
|
| 677 |
/* Main thread */ |
| 678 |
void cmtspeech_connection_unload(struct userdata *u) |
| 679 |
{ |
| 680 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 681 |
|
| 682 |
pa_assert(u); |
| 683 |
|
| 684 |
switch (pa_atomic_load(&c->thread_state)) { |
| 685 |
default: |
| 686 |
pa_log_error("Undefined thread_state value: %d", pa_atomic_load(&c->thread_state)); |
| 687 |
// fall trough |
| 688 |
case CMT_UNINITIALIZED: |
| 689 |
pa_log_debug("No CMT connection to unload"); |
| 690 |
return; |
| 691 |
case CMT_STARTING: |
| 692 |
while (pa_atomic_load(&c->thread_state) == CMT_STARTING) { |
| 693 |
pa_log_debug("CMT connection not up yet, waiting..."); |
| 694 |
usleep(200000); |
| 695 |
} |
| 696 |
// fall trough |
| 697 |
case CMT_RUNNING: |
| 698 |
pa_assert_se(pa_atomic_cmpxchg(&c->thread_state, CMT_RUNNING, CMT_ASK_QUIT)); |
| 699 |
pa_fdsem_post(c->thread_state_change); |
| 700 |
// fall trough |
| 701 |
case CMT_ASK_QUIT: |
| 702 |
while (pa_atomic_load(&c->thread_state) == CMT_ASK_QUIT) { |
| 703 |
pa_log_debug("Waiting for CMT connection thread to quit..."); |
| 704 |
usleep(200000); |
| 705 |
} |
| 706 |
pa_log_debug("cmtspeech thread has ended"); |
| 707 |
// fall trough |
| 708 |
case CMT_QUIT: |
| 709 |
break; |
| 710 |
} |
| 711 |
|
| 712 |
pa_atomic_store(&c->thread_state, CMT_UNINITIALIZED); |
| 713 |
if (c->cmt_handler) { |
| 714 |
c->cmt_handler->parent.free((pa_object *)c->cmt_handler); |
| 715 |
c->cmt_handler = NULL; |
| 716 |
} |
| 717 |
if (c->thread_state_change) { |
| 718 |
pa_fdsem_free(c->thread_state_change); |
| 719 |
c->thread_state_change = NULL; |
| 720 |
} |
| 721 |
pa_rtpoll_free(c->rtpoll); |
| 722 |
c->rtpoll = NULL; |
| 723 |
pa_thread_mq_done(&c->thread_mq); |
| 724 |
|
| 725 |
if (c->cmtspeech) { |
| 726 |
pa_log_error("CMT speech connection up when shutting down"); |
| 727 |
} |
| 728 |
pa_asyncq_free(c->dl_frame_queue, NULL); |
| 729 |
pa_mutex_free(c->cmtspeech_mutex); |
| 730 |
userdata = NULL; |
| 731 |
pa_log_debug("CMT connection unloaded"); |
| 732 |
} |
| 733 |
|
| 734 |
/** |
| 735 |
* Sends an UL frame using SSI audio interface 'sal'. |
| 736 |
* |
| 737 |
* Return zero on success, -1 on error. |
| 738 |
*/ |
| 739 |
/* Source IO-thread */ |
| 740 |
int cmtspeech_send_ul_frame(struct userdata *u, uint8_t *buf, size_t bytes) |
| 741 |
{ |
| 742 |
cmtspeech_buffer_t *salbuf; |
| 743 |
int res = -1; |
| 744 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 745 |
|
| 746 |
pa_assert(u); |
| 747 |
|
| 748 |
/* locking note: hot path lock */ |
| 749 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 750 |
|
| 751 |
if (!c->cmtspeech) { |
| 752 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 753 |
return -EIO; |
| 754 |
} |
| 755 |
|
| 756 |
if (cmtspeech_is_active(c->cmtspeech) == true) |
| 757 |
res = cmtspeech_ul_buffer_acquire(c->cmtspeech, &salbuf); |
| 758 |
|
| 759 |
if (res == 0) { |
| 760 |
if (ul_frame_count++ < 10) |
| 761 |
pa_log_debug("Sending ul frame # %d", ul_frame_count); |
| 762 |
|
| 763 |
/* note: 'bytes' must match the fixed size of frames */ |
| 764 |
pa_assert(bytes == (size_t)salbuf->pcount); |
| 765 |
memcpy(salbuf->payload, buf, bytes); |
| 766 |
res = cmtspeech_ul_buffer_release(c->cmtspeech, salbuf); |
| 767 |
if (res < 0) { |
| 768 |
pa_log_error("cmtspeech_ul_buffer_release(%p) failed return value %d.", (void *)salbuf, res); |
| 769 |
if (res == -EIO) { |
| 770 |
/* note: a severe error has occured, close the modem |
| 771 |
* instance */ |
| 772 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 773 |
pa_log_error("A severe error has occured, close the modem instance."); |
| 774 |
close_cmtspeech_on_error(u); |
| 775 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 776 |
} |
| 777 |
} |
| 778 |
ONDEBUG_TOKENS(fprintf(stderr, "U")); |
| 779 |
} else { |
| 780 |
static uint count = 0; |
| 781 |
if (count++ < 10) |
| 782 |
pa_log_error("cmtspeech_ul_buffer_acquire failed %d", res); |
| 783 |
} |
| 784 |
|
| 785 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 786 |
|
| 787 |
return res; |
| 788 |
} |
| 789 |
|
| 790 |
/* This is called form pulseaudio main thread. */ |
| 791 |
DBusHandlerResult cmtspeech_dbus_filter(DBusConnection *conn, DBusMessage *msg, void *arg) |
| 792 |
{ |
| 793 |
DBusMessageIter args; |
| 794 |
int type; |
| 795 |
struct userdata *u = arg; |
| 796 |
struct cmtspeech_connection *c = &u->cmt_connection; |
| 797 |
|
| 798 |
pa_assert(u); |
| 799 |
|
| 800 |
DBusError dbus_error; |
| 801 |
dbus_error_init(&dbus_error); |
| 802 |
|
| 803 |
if (dbus_message_is_signal(msg, CMTSPEECH_DBUS_CSCALL_CONNECT_IF, CMTSPEECH_DBUS_CSCALL_CONNECT_SIG)) { |
| 804 |
dbus_bool_t ulflag, dlflag, emergencyflag; |
| 805 |
|
| 806 |
dbus_message_get_args(msg, &dbus_error, |
| 807 |
DBUS_TYPE_BOOLEAN, &ulflag, |
| 808 |
DBUS_TYPE_BOOLEAN, &dlflag, |
| 809 |
DBUS_TYPE_BOOLEAN, &emergencyflag, |
| 810 |
DBUS_TYPE_INVALID); |
| 811 |
|
| 812 |
if (dbus_error_is_set(&dbus_error) != TRUE) { |
| 813 |
pa_log_debug("received AudioConnect with params %d, %d, %d", ulflag, dlflag, emergencyflag); |
| 814 |
|
| 815 |
c->call_ul = (ulflag == TRUE ? TRUE : FALSE); |
| 816 |
c->call_dl = (dlflag == TRUE ? TRUE : FALSE); |
| 817 |
c->call_emergency = (emergencyflag == TRUE ? TRUE : FALSE); |
| 818 |
|
| 819 |
/* note: very rarely taken code path */ |
| 820 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 821 |
if (c->cmtspeech) |
| 822 |
cmtspeech_state_change_call_connect(c->cmtspeech, dlflag == TRUE); |
| 823 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 824 |
|
| 825 |
} else |
| 826 |
pa_log_error("received %s with invalid parameters", CMTSPEECH_DBUS_CSCALL_CONNECT_SIG); |
| 827 |
|
| 828 |
return DBUS_HANDLER_RESULT_HANDLED; |
| 829 |
|
| 830 |
} else if (dbus_message_is_signal(msg, CMTSPEECH_DBUS_CSCALL_STATUS_IF, CMTSPEECH_DBUS_CSCALL_STATUS_SIG)) { |
| 831 |
pa_log_debug("Received ServerStatus"); |
| 832 |
|
| 833 |
if (dbus_message_iter_init(msg, &args) == TRUE) { |
| 834 |
type = dbus_message_iter_get_arg_type(&args); |
| 835 |
dbus_bool_t val; |
| 836 |
if (type == DBUS_TYPE_BOOLEAN) { |
| 837 |
dbus_message_iter_get_basic(&args, &val); |
| 838 |
|
| 839 |
pa_log_debug("Set ServerStatus to %d.", val == TRUE); |
| 840 |
|
| 841 |
/* note: very rarely taken code path */ |
| 842 |
pa_mutex_lock(c->cmtspeech_mutex); |
| 843 |
if (c->cmtspeech) { |
| 844 |
cmtspeech_state_change_call_status(c->cmtspeech, val == TRUE); |
| 845 |
if (val) { |
| 846 |
/* Call in progress, pause cleanup timer. */ |
| 847 |
pa_atomic_store(&u->cmtspeech_server_status, 1); |
| 848 |
if (pa_atomic_cmpxchg(&u->cmtspeech_cleanup_state, CMTSPEECH_CLEANUP_TIMER_ACTIVE, |
| 849 |
CMTSPEECH_CLEANUP_TIMER_INACTIVE)) { |
| 850 |
pa_log_warn("cmtspeech cleanup timer changed to inactive in DBus thread."); |
| 851 |
} |
| 852 |
} else { |
| 853 |
/* Call ended, set cleanup timer timeout. */ |
| 854 |
u->server_inactive_timeout = pa_rtclock_now() + CMTSPEECH_CLEANUP_TIMER_TIMEOUT; |
| 855 |
if (pa_atomic_cmpxchg(&u->cmtspeech_cleanup_state, CMTSPEECH_CLEANUP_TIMER_INACTIVE, |
| 856 |
CMTSPEECH_CLEANUP_TIMER_ACTIVE)) { |
| 857 |
pa_log_debug("cmtspeech cleanup timer timeout set in DBus thread."); |
| 858 |
} else { |
| 859 |
pa_log_debug("cmtspeech cleanup timer is already active or cleanup in progress."); |
| 860 |
} |
| 861 |
pa_atomic_store(&u->cmtspeech_server_status, 0); |
| 862 |
} |
| 863 |
} |
| 864 |
pa_mutex_unlock(c->cmtspeech_mutex); |
| 865 |
} else |
| 866 |
pa_log_warn("received %s with invalid arguments.", CMTSPEECH_DBUS_CSCALL_STATUS_SIG); |
| 867 |
} else |
| 868 |
pa_log_error("received %s with invalid parameters", CMTSPEECH_DBUS_CSCALL_STATUS_SIG); |
| 869 |
|
| 870 |
return DBUS_HANDLER_RESULT_HANDLED; |
| 871 |
|
| 872 |
} else if (dbus_message_is_signal(msg, CMTSPEECH_DBUS_PHONE_SSC_STATE_IF, CMTSPEECH_DBUS_PHONE_SSC_STATE_SIG)) { |
| 873 |
const char* modemstate = NULL; |
| 874 |
|
| 875 |
dbus_message_get_args(msg, &dbus_error, |
| 876 |
DBUS_TYPE_STRING, &modemstate, |
| 877 |
DBUS_TYPE_INVALID); |
| 878 |
|
| 879 |
if (dbus_error_is_set(&dbus_error) != TRUE) { |
| 880 |
pa_log_debug("modem state change: %s", modemstate); |
| 881 |
} |
| 882 |
} |
| 883 |
|
| 884 |
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| 885 |
} |