From 4e8b6b11491585dc0ad57e2484395fac79ee66eb Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Fri, 10 Aug 2018 15:03:11 +0200 Subject: huawei,call: check for ^CVOICE support and enable audio streaming USB sticks only support voice if ^CVOICE returns 0. And to enable audio streaming on the "Application" port (whatever is returned by AT^DDSETEX=?) we need to send AT^DDSETEX= after starting the call. After that the serial port will send and accept signed 16-bit 8000hz PCM audio, or whatever format is returned by ^CVOICE?. This patch is a rework of the original implementation by: Dan Williams --- plugins/huawei/mm-broadband-modem-huawei.c | 95 ++++++++++++- plugins/huawei/mm-call-huawei.c | 208 +++++++++++++++++++++++++++-- plugins/huawei/mm-call-huawei.h | 7 +- plugins/huawei/mm-modem-helpers-huawei.c | 63 +++++++++ plugins/huawei/mm-modem-helpers-huawei.h | 8 ++ 5 files changed, 369 insertions(+), 12 deletions(-) diff --git a/plugins/huawei/mm-broadband-modem-huawei.c b/plugins/huawei/mm-broadband-modem-huawei.c index 8da1520b2..d4c0e830f 100644 --- a/plugins/huawei/mm-broadband-modem-huawei.c +++ b/plugins/huawei/mm-broadband-modem-huawei.c @@ -133,6 +133,7 @@ struct _MMBroadbandModemHuaweiPrivate { FeatureSupport prefmode_support; FeatureSupport time_support; FeatureSupport nwtime_support; + FeatureSupport cvoice_support; MMModemLocationSource enabled_sources; @@ -141,6 +142,10 @@ struct _MMBroadbandModemHuaweiPrivate { GArray *prefmode_supported_modes; DetailedSignal detailed_signal; + + /* Voice call audio related properties */ + guint audio_hz; + guint audio_bits; }; /*****************************************************************************/ @@ -2856,6 +2861,78 @@ get_detailed_registration_state (MMIfaceModemCdma *self, task); } +/*****************************************************************************/ +/* Check if Voice supported (Voice interface) */ + +static gboolean +modem_voice_check_support_finish (MMIfaceModemVoice *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +voice_parent_check_support_ready (MMIfaceModemVoice *self, + GAsyncResult *res, + GTask *task) +{ + gboolean parent_support; + + parent_support = iface_modem_voice_parent->check_support_finish (self, res, NULL); + g_task_return_boolean (task, parent_support); + g_object_unref (task); +} + +static void +cvoice_check_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response || + !mm_huawei_parse_cvoice_response (response, + &self->priv->audio_hz, + &self->priv->audio_bits, + &error)) { + self->priv->cvoice_support = FEATURE_NOT_SUPPORTED; + mm_dbg ("Huawei-specific CVOICE is unsupported: %s", error->message); + g_clear_error (&error); + + /* Now check generic support */ + iface_modem_voice_parent->check_support (MM_IFACE_MODEM_VOICE (self), + (GAsyncReadyCallback)voice_parent_check_support_ready, + task); + return; + } + + mm_dbg ("Huawei-specific CVOICE is supported"); + self->priv->cvoice_support = FEATURE_SUPPORTED; + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_voice_check_support (MMIfaceModemVoice *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + /* Check for Huawei-specific ^CVOICE support */ + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^CVOICE?", + 3, + TRUE, + (GAsyncReadyCallback)cvoice_check_ready, + task); +} + /*****************************************************************************/ /* Enabling unsolicited events (Voice interface) */ @@ -2987,12 +3064,21 @@ modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self, /* Create call (Voice interface) */ static MMBaseCall * -create_call (MMIfaceModemVoice *self, +create_call (MMIfaceModemVoice *_self, MMCallDirection direction, const gchar *number) { - /* New Huawei Call */ - return mm_call_huawei_new (MM_BASE_MODEM (self), direction, number); + MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self); + + /* If CVOICE is supported we must have audio settings */ + g_assert (self->priv->cvoice_support == FEATURE_NOT_SUPPORTED || + (self->priv->cvoice_support == FEATURE_SUPPORTED && self->priv->audio_hz && self->priv->audio_bits)); + + return mm_call_huawei_new (MM_BASE_MODEM (self), + direction, + number, + self->priv->audio_hz, + self->priv->audio_bits); } /*****************************************************************************/ @@ -4071,6 +4157,7 @@ mm_broadband_modem_huawei_init (MMBroadbandModemHuawei *self) self->priv->prefmode_support = FEATURE_SUPPORT_UNKNOWN; self->priv->nwtime_support = FEATURE_SUPPORT_UNKNOWN; self->priv->time_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->cvoice_support = FEATURE_SUPPORT_UNKNOWN; } static void @@ -4228,6 +4315,8 @@ iface_modem_voice_init (MMIfaceModemVoice *iface) { iface_modem_voice_parent = g_type_interface_peek_parent (iface); + iface->check_support = modem_voice_check_support; + iface->check_support_finish = modem_voice_check_support_finish; iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events; iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish; iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events; diff --git a/plugins/huawei/mm-call-huawei.c b/plugins/huawei/mm-call-huawei.c index bdd524a96..6c7a885d4 100644 --- a/plugins/huawei/mm-call-huawei.c +++ b/plugins/huawei/mm-call-huawei.c @@ -31,13 +31,139 @@ G_DEFINE_TYPE (MMCallHuawei, mm_call_huawei, MM_TYPE_BASE_CALL) +enum { + PROP_0, + PROP_AUDIO_HZ, + PROP_AUDIO_BITS, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + struct _MMCallHuaweiPrivate { GRegex *conf_regex; GRegex *conn_regex; GRegex *cend_regex; GRegex *ddtmf_regex; + guint audio_hz; + guint audio_bits; }; +/*****************************************************************************/ +/* Audio channel setup */ + +typedef struct { + MMBaseModem *modem; + MMPort *audio_port; + MMCallAudioFormat *audio_format; +} SetupAudioChannelContext; + +static void +setup_audio_channel_context_free (SetupAudioChannelContext *ctx) +{ + g_clear_object (&ctx->audio_port); + g_clear_object (&ctx->audio_format); + g_clear_object (&ctx->modem); + g_slice_free (SetupAudioChannelContext, ctx); +} + +static gboolean +setup_audio_channel_finish (MMBaseCall *self, + GAsyncResult *res, + MMPort **audio_port, + MMCallAudioFormat **audio_format, + GError **error) +{ + SetupAudioChannelContext *ctx; + + if (!g_task_propagate_boolean (G_TASK (res), error)) + return FALSE; + + ctx = g_task_get_task_data (G_TASK (res)); + + if (audio_port && ctx->audio_port) + *audio_port = g_object_ref (ctx->audio_port); + if (audio_format && ctx->audio_format) + *audio_format = g_object_ref (ctx->audio_format); + + return TRUE; +} + +static void +ddsetex_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMCallHuawei *self; + SetupAudioChannelContext *ctx; + GError *error = NULL; + const gchar *response = NULL; + gchar *resolution_str; + + response = mm_base_modem_at_command_finish (modem, res, &error); + if (!response) { + mm_dbg ("Error enabling audio streaming: '%s'", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* Setup audio format */ + g_assert (self->priv->audio_hz && self->priv->audio_bits); + resolution_str = g_strdup_printf ("s%ule", self->priv->audio_bits); + ctx->audio_format = mm_call_audio_format_new (); + mm_call_audio_format_set_encoding (ctx->audio_format, "pcm"); + mm_call_audio_format_set_resolution (ctx->audio_format, resolution_str); + mm_call_audio_format_set_rate (ctx->audio_format, self->priv->audio_hz); + + /* The QCDM port, if present, switches from QCDM to voice while + * a voice call is active. */ + ctx->audio_port = MM_PORT (mm_base_modem_get_port_qcdm (modem)); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +setup_audio_channel (MMBaseCall *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SetupAudioChannelContext *ctx; + MMCallHuawei *self; + GTask *task; + MMBaseModem *modem = NULL; + + self = MM_CALL_HUAWEI (_self); + + task = g_task_new (self, NULL, callback, user_data); + + /* If there is no CVOICE support, no custom audio setup required + * (i.e. audio path is externally managed) */ + if (!self->priv->audio_hz && !self->priv->audio_bits) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + ctx = g_slice_new0 (SetupAudioChannelContext); + g_object_get (self, + MM_BASE_CALL_MODEM, &ctx->modem, + NULL); + g_task_set_task_data (task, ctx, (GDestroyNotify) setup_audio_channel_context_free); + + /* Enable audio streaming on the audio port */ + mm_base_modem_at_command (modem, + "AT^DDSETEX=2", + 5, + FALSE, + (GAsyncReadyCallback)ddsetex_ready, + task); +} + /*****************************************************************************/ /* In-call unsolicited events */ @@ -203,12 +329,16 @@ cleanup_unsolicited_events (MMBaseCall *self, MMBaseCall * mm_call_huawei_new (MMBaseModem *modem, MMCallDirection direction, - const gchar *number) + const gchar *number, + guint audio_hz, + guint audio_bits) { return MM_BASE_CALL (g_object_new (MM_TYPE_CALL_HUAWEI, - MM_BASE_CALL_MODEM, modem, - "direction", direction, - "number", number, + MM_BASE_CALL_MODEM, modem, + "direction", direction, + "number", number, + MM_CALL_HUAWEI_AUDIO_HZ, audio_hz, + MM_CALL_HUAWEI_AUDIO_BITS, audio_bits, MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE, MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE, NULL)); @@ -221,6 +351,48 @@ mm_call_huawei_init (MMCallHuawei *self) self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CALL_HUAWEI, MMCallHuaweiPrivate); } +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMCallHuawei *self = MM_CALL_HUAWEI (object); + + switch (prop_id) { + case PROP_AUDIO_HZ: + self->priv->audio_hz = g_value_get_uint (value); + break; + case PROP_AUDIO_BITS: + self->priv->audio_bits = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MMCallHuawei *self = MM_CALL_HUAWEI (object); + + switch (prop_id) { + case PROP_AUDIO_HZ: + g_value_set_uint (value, self->priv->audio_hz); + break; + case PROP_AUDIO_BITS: + g_value_set_uint (value, self->priv->audio_bits); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + static void finalize (GObject *object) { @@ -246,8 +418,28 @@ mm_call_huawei_class_init (MMCallHuaweiClass *klass) g_type_class_add_private (object_class, sizeof (MMCallHuaweiPrivate)); - object_class->finalize = finalize; - - base_call_class->setup_unsolicited_events = setup_unsolicited_events; - base_call_class->cleanup_unsolicited_events = cleanup_unsolicited_events; + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + base_call_class->setup_unsolicited_events = setup_unsolicited_events; + base_call_class->cleanup_unsolicited_events = cleanup_unsolicited_events; + base_call_class->setup_audio_channel = setup_audio_channel; + base_call_class->setup_audio_channel_finish = setup_audio_channel_finish; + + properties[PROP_AUDIO_HZ] = + g_param_spec_uint (MM_CALL_HUAWEI_AUDIO_HZ, + "Audio Hz", + "Voice call audio hz if call audio is routed via the host", + 0, 24000, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_AUDIO_HZ, properties[PROP_AUDIO_HZ]); + + properties[PROP_AUDIO_BITS] = + g_param_spec_uint (MM_CALL_HUAWEI_AUDIO_BITS, + "Audio Bits", + "Voice call audio bits if call audio is routed via the host", + 0, 24, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_AUDIO_BITS, properties[PROP_AUDIO_BITS]); } diff --git a/plugins/huawei/mm-call-huawei.h b/plugins/huawei/mm-call-huawei.h index fc7331186..1b70e81f0 100644 --- a/plugins/huawei/mm-call-huawei.h +++ b/plugins/huawei/mm-call-huawei.h @@ -28,6 +28,9 @@ #define MM_IS_CALL_HUAWEI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CALL_HUAWEI)) #define MM_CALL_HUAWEI_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CALL_HUAWEI, MMCallHuaweiClass)) +#define MM_CALL_HUAWEI_AUDIO_HZ "call-huawei-audio-hz" +#define MM_CALL_HUAWEI_AUDIO_BITS "call-huawei-audio-bits" + typedef struct _MMCallHuawei MMCallHuawei; typedef struct _MMCallHuaweiClass MMCallHuaweiClass; typedef struct _MMCallHuaweiPrivate MMCallHuaweiPrivate; @@ -45,6 +48,8 @@ GType mm_call_huawei_get_type (void); MMBaseCall *mm_call_huawei_new (MMBaseModem *modem, MMCallDirection direction, - const gchar *number); + const gchar *number, + guint audio_hz, + guint audio_bits); #endif /* MM_CALL_HUAWEI_H */ diff --git a/plugins/huawei/mm-modem-helpers-huawei.c b/plugins/huawei/mm-modem-helpers-huawei.c index e92552edb..2aac3d609 100644 --- a/plugins/huawei/mm-modem-helpers-huawei.c +++ b/plugins/huawei/mm-modem-helpers-huawei.c @@ -1406,3 +1406,66 @@ done: return ret; } + +/*****************************************************************************/ +/* ^CVOICE response parser */ + +gboolean +mm_huawei_parse_cvoice_response (const gchar *response, + guint *out_hz, + guint *out_bits, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info = NULL; + GError *match_error = NULL; + guint supported = 0, hz = 0, bits = 0; + gboolean ret = FALSE; + + /* ^CVOICE: <0=supported,1=unsupported>,,, */ + r = g_regex_new ("\\^CVOICE:\\s*(\\d)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)$", 0, 0, NULL); + g_assert (r != NULL); + + if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) { + if (match_error) { + g_propagate_error (error, match_error); + g_prefix_error (error, "Could not parse ^CVOICE results: "); + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't match ^CVOICE reply"); + } + } else { + /* Remember that g_match_info_get_match_count() includes match #0 */ + g_assert (g_match_info_get_match_count (match_info) >= 5); + + if (mm_get_uint_from_match_info (match_info, 1, &supported) && + mm_get_uint_from_match_info (match_info, 2, &hz) && + mm_get_uint_from_match_info (match_info, 3, &bits)) { + if (supported == 0) { + if (out_hz) + *out_hz = hz; + if (out_bits) + *out_bits = bits; + ret = TRUE; + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "^CVOICE not supported by this device"); + } + } else { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse ^CVOICE reply"); + } + } + + if (match_info) + g_match_info_free (match_info); + g_regex_unref (r); + + return ret; +} diff --git a/plugins/huawei/mm-modem-helpers-huawei.h b/plugins/huawei/mm-modem-helpers-huawei.h index b4c7ac7a1..502f694f6 100644 --- a/plugins/huawei/mm-modem-helpers-huawei.h +++ b/plugins/huawei/mm-modem-helpers-huawei.h @@ -151,4 +151,12 @@ gboolean mm_huawei_parse_hcsq_response (const gchar *response, guint *out_value5, GError **error); +/*****************************************************************************/ +/* ^CVOICE response parser */ + +gboolean mm_huawei_parse_cvoice_response (const gchar *response, + guint *hz, + guint *bits, + GError **error); + #endif /* MM_MODEM_HELPERS_HUAWEI_H */ -- cgit v1.2.1