[pulseaudio-commits] [Git][pulseaudio/pavucontrol][master] card: implement bluetooth profile codec selection

PulseAudio Marge Bot gitlab at gitlab.freedesktop.org
Fri Feb 26 11:25:04 UTC 2021



PulseAudio Marge Bot pushed to branch master at PulseAudio / pavucontrol


Commits:
d66137f9 by Igor V. Kovalenko at 2021-02-26T14:03:20+03:00
card: implement bluetooth profile codec selection

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pavucontrol/-/merge_requests/54>

- - - - -


9 changed files:

- configure.ac
- src/cardwidget.cc
- src/cardwidget.h
- src/devicewidget.h
- src/mainwindow.cc
- src/mainwindow.h
- src/pavucontrol.cc
- src/pavucontrol.glade
- src/pavucontrol.h


Changes:

=====================================
configure.ac
=====================================
@@ -56,6 +56,9 @@ fi
 AC_SUBST(PULSE_LIBS)
 AC_SUBST(PULSE_CFLAGS)
 
+AC_CHECK_LIB(pulse, pa_context_send_message_to_object, [HAVE_PULSE_MESSAGING_API=yes], [HAVE_PULSE_MESSAGING_API=no])
+AS_IF([test "x$HAVE_PULSE_MESSAGING_API" = "xyes"], AC_DEFINE([HAVE_PULSE_MESSAGING_API], 1, [Have messaging API.]))
+
 # If using GCC specifiy some additional parameters
 if test "x$GCC" = "xyes" ; then
    CFLAGS="$CFLAGS -pipe -Wall -W -Wno-unused-parameter"


=====================================
src/cardwidget.cc
=====================================
@@ -33,12 +33,22 @@ CardWidget::CardWidget(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>
     x->get_widget("cardNameLabel", nameLabel);
     x->get_widget("profileList", profileList);
     x->get_widget("cardIconImage", iconImage);
+    x->get_widget("codecBox", codecBox);
+    x->get_widget("codecList", codecList);
 
-    treeModel = Gtk::ListStore::create(profileModel);
-    profileList->set_model(treeModel);
+    profileListStore = Gtk::ListStore::create(profileModel);
+    profileList->set_model(profileListStore);
     profileList->pack_start(profileModel.desc);
 
     profileList->signal_changed().connect( sigc::mem_fun(*this, &CardWidget::onProfileChange));
+
+    codecBox->hide();
+
+    codecListStore = Gtk::ListStore::create(codecModel);
+    codecList->set_model(codecListStore);
+    codecList->pack_start(codecModel.desc);
+
+    codecList->signal_changed().connect( sigc::mem_fun(*this, &CardWidget::onCodecChange));
 }
 
 CardWidget* CardWidget::create() {
@@ -51,22 +61,41 @@ CardWidget* CardWidget::create() {
 
 
 void CardWidget::prepareMenu() {
-    int idx = 0;
-    int active_idx = -1;
+    int active_idx;
 
-    treeModel->clear();
+    profileListStore->clear();
+    active_idx = -1;
     /* Fill the ComboBox's Tree Model */
     for (uint32_t i = 0; i < profiles.size(); ++i) {
-        Gtk::TreeModel::Row row = *(treeModel->append());
+        Gtk::TreeModel::Row row = *(profileListStore->append());
         row[profileModel.name] = profiles[i].first;
         row[profileModel.desc] = profiles[i].second;
         if (profiles[i].first == activeProfile)
-          active_idx = idx;
-        idx++;
+          active_idx = i;
     }
 
     if (active_idx >= 0)
         profileList->set_active(active_idx);
+
+    codecListStore->clear();
+    active_idx = -1;
+    /* Fill the ComboBox's Tree Model */
+    for (uint32_t i = 0; i < codecs.size(); ++i) {
+        Gtk::TreeModel::Row row = *(codecListStore->append());
+        row[codecModel.name] = codecs[i].first;
+        row[codecModel.desc] = codecs[i].second;
+        if (codecs[i].first == activeCodec)
+          active_idx = i;
+    }
+
+    if (active_idx >= 0)
+        codecList->set_active(active_idx);
+
+    /* unhide codec box */
+    if (codecs.size())
+        codecBox->show();
+    else
+        codecBox->hide();
 }
 
 void CardWidget::onProfileChange() {
@@ -93,3 +122,31 @@ void CardWidget::onProfileChange() {
         }
     }
 }
+
+void CardWidget::onCodecChange() {
+    if (updating)
+        return;
+
+#ifdef HAVE_PULSE_MESSAGING_API
+    Gtk::TreeModel::iterator iter = codecList->get_active();
+    if (iter)
+    {
+        Gtk::TreeModel::Row row = *iter;
+        if (row)
+        {
+          pa_operation* o;
+          Glib::ustring codec_id = row[codecModel.name];
+
+          std::string codec_message = "{" + std::string(codec_id) + "}";
+
+          if (!(o = pa_context_send_message_to_object(get_context(), card_bluez_message_handler_path(pulse_card_name).c_str(),
+              "switch-codec", codec_message.c_str(), NULL, NULL))) {
+              show_error(_("pa_context_set_card_profile_by_index() failed"));
+              return;
+          }
+
+          pa_operation_unref(o);
+        }
+    }
+#endif
+}


=====================================
src/cardwidget.h
=====================================
@@ -43,19 +43,27 @@ public:
     Gtk::Menu menu;
     Gtk::Image *iconImage;
     Glib::ustring name;
+    std::string pulse_card_name;
+    Gtk::Box *codecBox;
     uint32_t index;
     bool updating;
 
-    std::vector< std::pair<Glib::ustring,Glib::ustring> > profiles;
+    // each entry in profiles is a pair of profile name and profile description
+    std::vector<std::pair<Glib::ustring, Glib::ustring>> profiles;
     std::map<Glib::ustring, PortInfo> ports;
     Glib::ustring activeProfile;
     bool hasSinks;
     bool hasSources;
 
+    // each entry in codecs is a pair of codec name and codec description
+    std::vector<std::pair<Glib::ustring, Glib::ustring>> codecs;
+    Glib::ustring activeCodec;
+
     void prepareMenu();
 
 protected:
   virtual void onProfileChange();
+  virtual void onCodecChange();
 
   /* Tree model columns */
   class ModelColumns : public Gtk::TreeModel::ColumnRecord
@@ -72,7 +80,12 @@ protected:
   ModelColumns profileModel;
 
   Gtk::ComboBox *profileList;
-  Glib::RefPtr<Gtk::ListStore> treeModel;
+  Glib::RefPtr<Gtk::ListStore> profileListStore;
+
+  ModelColumns codecModel;
+
+  Gtk::ComboBox *codecList;
+  Glib::RefPtr<Gtk::ListStore> codecListStore;
 };
 
 #endif


=====================================
src/devicewidget.h
=====================================
@@ -68,7 +68,7 @@ public:
     virtual void executeVolumeUpdate();
     virtual void setBaseVolume(pa_volume_t v);
 
-    std::vector< std::pair<Glib::ustring,Glib::ustring> > ports;
+    std::vector<std::pair<Glib::ustring, Glib::ustring>> ports;
     Glib::ustring activePort;
 
     void prepareMenu();


=====================================
src/mainwindow.cc
=====================================
@@ -35,6 +35,10 @@
 
 #include "i18n.h"
 
+#if defined(HAVE_PULSE_MESSAGING_API)
+#include <pulse/message-params.h>
+#endif
+
 /* Used for profile sorting */
 struct profile_prio_compare {
     bool operator() (const pa_card_profile_info2& lhs, const pa_card_profile_info2& rhs) const {
@@ -373,6 +377,8 @@ void MainWindow::updateCard(const pa_card_info &info) {
 
     w->updating = true;
 
+    w->pulse_card_name = info.name;
+
     description = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_DESCRIPTION);
     w->name = description ? description : info.name;
     gchar *txt;
@@ -431,7 +437,7 @@ void MainWindow::updateCard(const pa_card_info &info) {
         if (!profileIt->available)
             desc += _(" (unavailable)");
 
-        w->profiles.push_back(std::pair<Glib::ustring,Glib::ustring>(profileIt->name, desc));
+        w->profiles.push_back(std::pair<Glib::ustring, Glib::ustring>(profileIt->name, desc));
     }
 
     w->activeProfile = info.active_profile ? info.active_profile->name : "";
@@ -475,6 +481,51 @@ void MainWindow::updateCard(const pa_card_info &info) {
     w->updating = false;
 }
 
+void MainWindow::updateCardCodecs(const std::string& card_name, const std::unordered_map<std::string, std::string>& codecs) {
+    CardWidget *w = NULL;
+
+    for (auto c : cardWidgets) {
+        if (card_name.compare(c.second->pulse_card_name) == 0)
+            w = c.second;
+    }
+
+    if (!w)
+        return;
+
+    w->updating = true;
+
+    w->codecs.clear();
+
+    /* insert profiles */
+    for (auto e : codecs) {
+        w->codecs.push_back(std::pair<Glib::ustring, Glib::ustring>(e.first, e.second));
+    }
+
+    w->prepareMenu();
+
+    w->updating = false;
+}
+
+void MainWindow::setActiveCodec(const std::string& card_name, const std::string& codec) {
+    CardWidget *w = NULL;
+
+    for (auto c : cardWidgets) {
+        if (card_name.compare(c.second->pulse_card_name) == 0)
+            w = c.second;
+    }
+
+    if (!w)
+        return;
+
+    w->updating = true;
+
+    w->activeCodec = codec;
+
+    w->prepareMenu();
+
+    w->updating = false;
+}
+
 bool MainWindow::updateSink(const pa_sink_info &info) {
     SinkWidget *w;
     bool is_new = false;
@@ -525,7 +576,7 @@ bool MainWindow::updateSink(const pa_sink_info &info) {
 
     w->ports.clear();
     for (std::set<pa_sink_port_info>::iterator i = port_priorities.begin(); i != port_priorities.end(); ++i)
-        w->ports.push_back(std::pair<Glib::ustring,Glib::ustring>(i->name, i->description));
+        w->ports.push_back(std::pair<Glib::ustring, Glib::ustring>(i->name, i->description));
 
     w->activePort = info.active_port ? info.active_port->name : "";
 
@@ -692,7 +743,7 @@ void MainWindow::updateSource(const pa_source_info &info) {
 
     w->ports.clear();
     for (std::set<pa_source_port_info>::iterator i = port_priorities.begin(); i != port_priorities.end(); ++i)
-        w->ports.push_back(std::pair<Glib::ustring,Glib::ustring>(i->name, i->description));
+        w->ports.push_back(std::pair<Glib::ustring, Glib::ustring>(i->name, i->description));
 
     w->activePort = info.active_port ? info.active_port->name : "";
 


=====================================
src/mainwindow.h
=====================================
@@ -29,6 +29,8 @@ class MainWindow;
 #  include <pulse/ext-device-restore.h>
 #endif
 
+#include <unordered_map>
+
 class CardWidget;
 class SinkWidget;
 class SourceWidget;
@@ -54,6 +56,8 @@ public:
 #if HAVE_EXT_DEVICE_RESTORE_API
     void updateDeviceInfo(const pa_ext_device_restore_info &info);
 #endif
+    void updateCardCodecs(const std::string& card_name, const std::unordered_map<std::string, std::string>& codecs);
+    void setActiveCodec(const std::string& card_name, const std::string& codec);
 
     void removeCard(uint32_t index);
     void removeSink(uint32_t index);


=====================================
src/pavucontrol.cc
=====================================
@@ -25,6 +25,9 @@
 #include <pulse/pulseaudio.h>
 #include <pulse/ext-stream-restore.h>
 #include <pulse/ext-device-manager.h>
+#ifdef HAVE_PULSE_MESSAGING_API
+#include <pulse/message-params.h>
+#endif
 
 #include <canberra-gtk.h>
 
@@ -42,6 +45,12 @@
 #include "mainwindow.h"
 #include "pavuapplication.h"
 
+#include <unordered_map>
+#include <utility>
+#include <memory>
+
+using WindowAndCardName = std::pair<MainWindow*, std::string>;
+
 static pa_context* context = NULL;
 static pa_mainloop_api* api = NULL;
 static int n_outstanding = 0;
@@ -70,7 +79,145 @@ static void dec_outstanding(MainWindow *w) {
     }
 }
 
-void card_cb(pa_context *, const pa_card_info *i, int eol, void *userdata) {
+#ifdef HAVE_PULSE_MESSAGING_API
+
+std::string card_bluez_message_handler_path(const std::string& name) {
+    return "/card/" + name + "/bluez";
+}
+
+static void context_bluetooth_card_codec_list_cb(pa_context *c, int success, char *response, void *userdata) {
+    auto u = std::unique_ptr<WindowAndCardName>(reinterpret_cast<WindowAndCardName*>(userdata));
+
+    if (!success)
+        return;
+
+    void *state = NULL;
+    char *codec_list;
+    char *handler_struct;
+    int err;
+
+    if (pa_message_params_read_raw(response, &codec_list, &state) <= 0) {
+        show_error(_("list-codecs message response could not be parsed correctly"));
+        return;
+    }
+
+    std::unordered_map<std::string, std::string> codecs;
+
+    state = NULL;
+    while ((err = pa_message_params_read_raw(codec_list, &handler_struct, &state)) > 0) {
+        void *state2 = NULL;
+        const char *path;
+        const char *description;
+
+        if (pa_message_params_read_string(handler_struct, &path, &state2) <= 0) {
+            err = -1;
+            break;
+        }
+        if (pa_message_params_read_string(handler_struct, &description, &state2) <= 0) {
+            err = -1;
+            break;
+        }
+
+        codecs[path] = description;
+    }
+
+    if (err < 0) {
+        show_error(_("list-codecs message response could not be parsed correctly"));
+        codecs.clear();
+        return;
+    }
+
+    u->first->updateCardCodecs(u->second, codecs);
+}
+
+static void context_bluetooth_card_active_codec_cb(pa_context *c, int success, char *response, void *userdata) {
+    auto u = std::unique_ptr<WindowAndCardName>(reinterpret_cast<WindowAndCardName*>(userdata));
+
+    if (!success)
+        return;
+
+    void *state = NULL;
+    const char *name;
+
+    if (pa_message_params_read_string(response, &name, &state) <= 0) {
+        show_error(_("get-codec message response could not be parsed correctly"));
+        return;
+    }
+
+    u->first->setActiveCodec(u->second, name);
+}
+
+template<typename U> void send_message(pa_context *c, const char *target, const char *request, pa_context_string_cb_t cb, const U& u)
+{
+    auto send_message_userdata = new U(u);
+
+    pa_operation *o = pa_context_send_message_to_object(c, target, request, NULL, cb, send_message_userdata);
+
+    if (!o) {
+        delete send_message_userdata;
+        show_error(_("pa_context_send_message_to_object() failed"));
+        return;
+    }
+    pa_operation_unref(o);
+}
+
+static void context_message_handlers_cb(pa_context *c, int success, char *response, void *userdata) {
+    auto u = std::unique_ptr<WindowAndCardName>(reinterpret_cast<WindowAndCardName*>(userdata));
+
+    if (!success)
+        return;
+
+    void *state = NULL;
+    char *handler_list;
+    char *handler_struct;
+    int err;
+
+    if (pa_message_params_read_raw(response, &handler_list, &state) <= 0) {
+        show_error(_("list-handlers message response could not be parsed correctly"));
+        return;
+    }
+
+    std::unordered_map<std::string, std::string> message_handler_map;
+
+    state = NULL;
+    while ((err = pa_message_params_read_raw(handler_list, &handler_struct, &state)) > 0) {
+        void *state2 = NULL;
+        const char *path;
+        const char *description;
+
+        if (pa_message_params_read_string(handler_struct, &path, &state2) <= 0) {
+            err = -1;
+            break;
+        }
+        if (pa_message_params_read_string(handler_struct, &description, &state2) <= 0) {
+            err = -1;
+            break;
+        }
+
+        message_handler_map[path] = description;
+    }
+
+    if (err < 0) {
+        show_error(_("list-handlers message response could not be parsed correctly"));
+        message_handler_map.clear();
+        return;
+    }
+
+    /* only send requests if card bluez message handler is registered */
+    auto e = message_handler_map.find(card_bluez_message_handler_path(u->second));
+
+    if (e != message_handler_map.end()) {
+
+        /* get-codec: retrieve active codec name */
+        send_message(c, e->first.c_str(), "get-codec", context_bluetooth_card_active_codec_cb, *u);
+
+        /* list-codecs: retrieve list of codecs */
+        send_message(c, e->first.c_str(), "list-codecs", context_bluetooth_card_codec_list_cb, *u);
+    }
+}
+#endif
+
+void card_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) {
     MainWindow *w = static_cast<MainWindow*>(userdata);
 
     if (eol < 0) {
@@ -87,6 +234,11 @@ void card_cb(pa_context *, const pa_card_info *i, int eol, void *userdata) {
     }
 
     w->updateCard(*i);
+
+#ifdef HAVE_PULSE_MESSAGING_API
+    /* initiate requesting bluetooth codec list */
+    send_message(c, "/core", "list-handlers", context_message_handlers_cb, WindowAndCardName(w, i->name));
+#endif
 }
 
 #if HAVE_EXT_DEVICE_RESTORE_API


=====================================
src/pavucontrol.glade
=====================================
@@ -171,6 +171,43 @@
                         <property name="position">1</property>
                       </packing>
                     </child>
+                    <child>
+                      <object class="GtkHBox" id="codecBox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="label51">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="label" translatable="yes"><b>Codec:</b></property>
+                            <property name="use_markup">True</property>
+                            <property name="xalign">0</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkComboBox" id="codecList">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>


=====================================
src/pavucontrol.h
=====================================
@@ -21,6 +21,8 @@
 #ifndef pavucontrol_h
 #define pavucontrol_h
 
+#include <string>
+
 #include <signal.h>
 #include <string.h>
 
@@ -75,4 +77,9 @@ pa_context* get_context(void);
 void show_error(const char *txt);
 
 MainWindow* pavucontrol_get_window(pa_glib_mainloop *m, bool maximize, bool retry, int tab_number);
+
+#ifdef HAVE_PULSE_MESSAGING_API
+std::string card_bluez_message_handler_path(const std::string& name);
+#endif
+
 #endif



View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pavucontrol/-/commit/d66137f9a8751ac8598c9a014a27cd72072756f2

-- 
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pavucontrol/-/commit/d66137f9a8751ac8598c9a014a27cd72072756f2
You're receiving this email because of your account on gitlab.freedesktop.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/pulseaudio-commits/attachments/20210226/f3ccdc98/attachment-0001.htm>


More information about the pulseaudio-commits mailing list