$ git clone http://tcclient.ion.nu/tc_client.git
commit c4f4691cc6d935f1ad5c782a05d54123614bba3d
Author: Alicia <...>
Date:   Wed Oct 12 01:36:53 2016 +0200

    tc_client-gtk: added support for broadcasting audio.

diff --git a/ChangeLog b/ChangeLog
index b01aec9..b2690fc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -22,6 +22,7 @@ tc_client-gtk: fixed resampling of incoming audio.
 tc_client-gtk: added GTK+2 compatibility code related to the greenscreen camera color picker.
 tc_client-gtk: added compatibility code for windows' lack of pipe()
 tc_client-gtk: added workaround for libao not handling the "client_name" option on windows.
+tc_client-gtk: added support for broadcasting audio.
 libcamera: added support for a virtual X11 camera.
 tc_client-gtk and camviewer: updated to libavcodec's avcodec_{send,receive}_{frame,packet} API.
 camviewer: removed the old, buggy audio code.
diff --git a/Makefile b/Makefile
index ae86c88..9eee982 100644
--- a/Makefile
+++ b/Makefile
@@ -30,13 +30,36 @@ ifdef SWSCALE_LIBS
   INSTALLDEPS+=tc_client-gtk gtkgui.glade
   ifdef AO_LIBS
     ifdef AVRESAMPLE_LIBS
-      CONFINFO+=|  Will enable (incoming) mic support
+      CONFINFO+=|  Will enable incoming mic support
       CFLAGS+=-DHAVE_LIBAO=1 -DHAVE_AVRESAMPLE=1 $(AVRESAMPLE_CFLAGS) $(AO_CFLAGS)
     endif
     ifdef SWRESAMPLE_LIBS
-      CONFINFO+=|  Will enable (incoming) mic support
+      CONFINFO+=|  Will enable incoming mic support
       CFLAGS+=-DHAVE_LIBAO=1 -DHAVE_SWRESAMPLE=1 $(SWRESAMPLE_CFLAGS) $(AO_CFLAGS)
     endif
+    ifndef AVRESAMPLE_LIBS
+    ifndef SWRESAMPLE_LIBS
+      CONFINFO+=|  Incoming mic support will not be enabled
+    endif
+    endif
+  endif
+  ifdef PULSE_LIBS
+    CONFINFO+=|  Will enable outgoing mic support
+    CFLAGS+=-DHAVE_PULSEAUDIO=1 $(PULSE_CFLAGS)
+  else
+    CONFINFO+=|  Outgoing mic support will not be enabled
+  endif
+  ifdef LIBV4L2_LIBS
+    CONFINFO+=|  Will enable v4l2 camera support
+    CFLAGS+=-DHAVE_V4L2 $(LIBV4L2_CFLAGS)
+    LIBCAMERA_OBJ+=utilities/libcamera/camera_v4l2.o
+  else
+    CONFINFO+=|  v4l2 camera support will not be enabled
+  endif
+  ifdef LIBX11_LIBS
+    CONFINFO+=|  Will enable X11 virtual camera support
+    CFLAGS+=-DHAVE_X11 $(LIBX11_CFLAGS)
+    LIBCAMERA_OBJ+=utilities/libcamera/camera_x11.o
   endif
   ifneq ($(findstring MINGW,$(shell uname -s)),)
     ifeq ($(findstring mingw,$(shell $(CC) -v 2>&1 | grep Target)),)
@@ -65,18 +88,6 @@ ifdef SWSCALE_LIBS
  @echo
  @echo 'To build the gtk+ GUI, enter this directory from a MinGW shell and type make'
   endif
-  ifdef LIBV4L2_LIBS
-    CONFINFO+=|  Will enable v4l2 camera support
-    CFLAGS+=-DHAVE_V4L2 $(LIBV4L2_CFLAGS)
-    LIBCAMERA_OBJ+=utilities/libcamera/camera_v4l2.o
-  else
-    CONFINFO+=|  v4l2 camera support will not be enabled
-  endif
-  ifdef LIBX11_LIBS
-    CONFINFO+=|  Will enable X11 virtual camera support
-    CFLAGS+=-DHAVE_X11 $(LIBX11_CFLAGS)
-    LIBCAMERA_OBJ+=utilities/libcamera/camera_x11.o
-  endif
 endif
 endif
 endif
@@ -118,7 +129,7 @@ cursedchat: $(CURSEDCHAT_OBJ)
  $(CC) $(LDFLAGS) $^ $(LIBS) $(READLINE_LIBS) $(CURSES_LIBS) -o $@
 
 tc_client-gtk: $(TC_CLIENT_GTK_OBJ) camplaceholder.gif
- $(CC) $(LDFLAGS) $(TC_CLIENT_GTK_OBJ) $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(AVRESAMPLE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) $(LIBX11_LIBS) -o $@
+ $(CC) $(LDFLAGS) $(TC_CLIENT_GTK_OBJ) $(LIBS) $(GTK_LIBS) $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(SWSCALE_LIBS) $(AVRESAMPLE_LIBS) $(SWRESAMPLE_LIBS) $(AO_LIBS) $(LIBV4L2_LIBS) $(LIBX11_LIBS) $(PULSE_LIBS) -o $@
 
 camplaceholder.gif: utilities/gtk/gencamplaceholder.sh utilities/gtk/camplaceholder.xcf utilities/gtk/spinnerdot.xcf
  utilities/gtk/gencamplaceholder.sh
diff --git a/configure b/configure
index 8868c34..3300265 100755
--- a/configure
+++ b/configure
@@ -69,6 +69,23 @@ testbuild()
   fi
 }
 
+testpkgconfig()
+{
+  pkgname="$1"
+  varprefix="$2"
+  printf "Checking for ${pkgname}... "
+  testlibs="`pkg-config --libs "$pkgname" 2> /dev/null`"
+  if [ "x$testlibs" != "x" ]; then
+    echo "${varprefix}_LIBS=${testlibs}" >> config.mk
+    echo "${varprefix}_CFLAGS=`pkg-config --cflags "$pkgname"`" >> config.mk
+    echo yes
+    return 0
+  else
+    echo no
+    return 1
+  fi
+}
+
 if ! testbuild 'strndup' 'char* x=strndup("abc", 2);' 'string.h'; then
   echo '#define NO_STRNDUP 1' >> config.h
 fi
@@ -77,23 +94,8 @@ if ! testbuild 'dprintf' 'dprintf(1,"test");' 'stdio.h'; then
   echo '#define NO_DPRINTF 1' >> config.h
 fi
 
-printf 'Checking for gtk+-3.0... '
-gtklibs="`pkg-config --libs gtk+-3.0 2> /dev/null`"
-if [ "x$gtklibs" != "x" ]; then
-  echo "GTK_LIBS=${gtklibs}" >> config.mk
-  echo "GTK_CFLAGS=`pkg-config --cflags gtk+-3.0`" >> config.mk
-  echo yes
-else
-  echo no
-  printf 'Checking for gtk+-2.0... '
-  gtklibs="`pkg-config --libs gtk+-2.0 2> /dev/null`"
-  if [ "x$gtklibs" != "x" ]; then
-    echo "GTK_LIBS=${gtklibs}" >> config.mk
-    echo "GTK_CFLAGS=`pkg-config --cflags gtk+-2.0`" >> config.mk
-    echo yes
-  else
-    echo no
-  fi
+if ! testpkgconfig 'gtk+-3.0' GTK; then
+  testpkgconfig 'gtk+-2.0' GTK
 fi
 
 printf 'Checking for libavcodec... '
@@ -134,15 +136,7 @@ else
   echo no
 fi
 
-printf 'Checking for libswscale... '
-swscalelibs="`pkg-config --libs libswscale 2> /dev/null`"
-if [ "x$swscalelibs" != "x" ]; then
-  echo "SWSCALE_LIBS=${swscalelibs}" >> config.mk
-  echo "SWSCALE_CFLAGS=`pkg-config --cflags libswscale`" >> config.mk
-  echo yes
-else
-  echo no
-fi
+testpkgconfig libswscale SWSCALE
 
 printf 'Checking for libavutil... '
 avutillibs="`pkg-config --libs libavutil 2> /dev/null`"
@@ -211,15 +205,7 @@ else
 fi
 
 if "$haveresample"; then
-  printf 'Checking for libao... '
-  aolibs="`pkg-config --libs ao 2> /dev/null`"
-  if [ "x$aolibs" != "x" ]; then
-    echo "AO_LIBS=${aolibs}" >> config.mk
-    echo "AO_CFLAGS=`pkg-config --cflags ao`" >> config.mk
-    echo yes
-  else
-    echo no
-  fi
+  testpkgconfig ao AO
 fi
 
 printf 'Checking for ncurses... '
@@ -242,25 +228,9 @@ if testbuild 'readline' 'rl_initialize();return 0;' 'stdio.h readline/readline.h
   echo "READLINE_LIBS=-lreadline" >> config.mk
 fi
 
-printf 'Checking for libv4l2... '
-v4l2libs="`pkg-config --libs libv4l2 2> /dev/null`"
-if [ "x$v4l2libs" != "x" ]; then
-  echo "LIBV4L2_LIBS=${v4l2libs}" >> config.mk
-  echo "LIBV4L2_CFLAGS=`pkg-config --cflags libv4l2`" >> config.mk
-  echo yes
-else
-  echo no
-fi
-
-printf 'Checking for X11... '
-x11libs="`pkg-config --libs x11 2> /dev/null`"
-if [ "x$x11libs" != "x" ]; then
-  echo "LIBX11_LIBS=${x11libs}" >> config.mk
-  echo "LIBX11_CFLAGS=`pkg-config --cflags x11`" >> config.mk
-  echo yes
-else
-  echo no
-fi
+testpkgconfig libv4l2 LIBV4L2
+testpkgconfig x11 LIBX11
+testpkgconfig libpulse-simple PULSE
 
 # TODO: handle crosscompiling better
 printf 'Checking if endianness macros work... '
diff --git a/gtkgui.glade b/gtkgui.glade
index b481225..77a3cba 100644
--- a/gtkgui.glade
+++ b/gtkgui.glade
@@ -1083,6 +1083,22 @@
                         <property name="use_underline">True</property>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkCheckMenuItem" id="menuitem_broadcast_mic">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Microphone</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkMenuItem" id="menuitem_broadcast_stop">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Stop broadcasting</property>
+                        <property name="use_underline">True</property>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
@@ -1231,10 +1247,34 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkEntry" id="inputfield">
+                  <object class="GtkBox" id="box16">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="has_focus">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkButton" id="pushtotalk">
+                        <property name="label" translatable="yes">Push-to-talk</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="inputfield">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="has_focus">True</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>
@@ -1568,4 +1608,73 @@
       </object>
     </child>
   </object>
+  <object class="GtkDialog" id="pushtotalkdialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Broadcast</property>
+    <property name="modal">True</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <property name="transient_for">main</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox1">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="dialog-action_area1">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label" translatable="yes">Push-to-talk</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button2">
+                <property name="label" translatable="yes">Open mic</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</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">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label33">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="label" translatable="yes">Broadcast mic in push-to-talk mode (only broadcast while a button is pressed) or open mic?</property>
+            <property name="wrap">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="1">button1</action-widget>
+      <action-widget response="0">button2</action-widget>
+    </action-widgets>
+  </object>
 </interface>
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 8bdfaf4..019260f 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -145,6 +145,12 @@ void togglecam_cancel(void)
   gtk_check_menu_item_set_active(item, 0);
 }
 
+void stopbroadcasting(GtkMenuItem* x, void* y)
+{
+  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_camera")), 0);
+  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_mic")), 0);
+}
+
 unsigned int cameventsource=0;
 char buf[1024];
 gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer datap)
@@ -425,7 +431,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
       {
         if(config_get_bool("camdownonjoin"))
         {
-          gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_camera")), 0);
+          stopbroadcasting(0, 0);
         }
         space[0]=0;
         adduser(nick);
@@ -544,7 +550,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
   }
   if(!strcmp(buf, "Outgoing media stream was closed"))
   {
-    togglecam_cancel();
+    stopbroadcasting(0, 0);
     return 1;
   }
   if(!strncmp(buf, "Room topic: ", 12) ||
@@ -592,13 +598,15 @@ void* audiothread(void* fdp)
 }
 #endif
 
-void togglecam(GtkCheckMenuItem* item, struct viddata* data)
+void togglecam(GtkCheckMenuItem* item, void* x)
 {
   if(!gtk_check_menu_item_get_active(item))
   {
     if(!camout_cam){return;}
     cam_close(camout_cam);
     camout_cam=0;
+    if(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_mic")))){return;}
+    // TODO: if switching from cam+mic to just mic, send something to tell other clients to drop the last video frame and show a mic instead
     if(camera_find("out"))
     {
       dprintf(tc_client_in[1], "/camdown\n");
@@ -609,6 +617,62 @@ void togglecam(GtkCheckMenuItem* item, struct viddata* data)
   camselect_open(startcamout, togglecam_cancel);
 }
 
+#ifdef HAVE_PULSEAUDIO
+void togglemic(GtkCheckMenuItem* item, void* x)
+{
+  static int micpipe[2];
+  static int eventsource=0;
+  if(!gtk_check_menu_item_get_active(item)) // Stop mic broadcast
+  {
+    gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "pushtotalk")));
+    pushtotalk_enabled=0;
+    if(!eventsource){return;}
+    close(micpipe[0]);
+    close(micpipe[1]);
+    g_source_remove(eventsource);
+    eventsource=0;
+    if(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_camera")))){return;}
+    if(camera_find("out"))
+    {
+      dprintf(tc_client_in[1], "/camdown\n");
+      camera_remove("out", 0); // Close our local display
+    }
+    return;
+  }
+  // Choose between push-to-talk and open mic
+  int choice=gtk_dialog_run(GTK_DIALOG(gtk_builder_get_object(gui, "pushtotalkdialog")));
+  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "pushtotalkdialog")));
+  if(choice==GTK_RESPONSE_DELETE_EVENT)
+  {
+    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_mic")), 0);
+    return;
+  }
+  if(choice) // 1=push-to-talk, 0=open mic
+  {
+    gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(gui, "pushtotalk")));
+    pushtotalk_enabled=1;
+    pushtotalk_pushed=0;
+  }
+  // Start mic broadcast
+  if(!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_camera"))))
+  { // Only /camup if we're not already broadcasting video
+    dprintf(tc_client_in[1], "/camup\n");
+  }
+  pipe(micpipe);
+  g_thread_new("audio_in", audiothread_in, &micpipe[1]);
+  // Attach micpipe[0] to mic_encode()
+  GIOChannel* channel=g_io_channel_unix_new(micpipe[0]);
+  g_io_channel_set_encoding(channel, 0, 0);
+  eventsource=g_io_add_watch(channel, G_IO_IN, mic_encode, 0);
+}
+
+gboolean mic_pushtotalk(GtkWidget* button, GdkEvent* event, void* pushed)
+{
+  pushtotalk_pushed=!!pushed;
+  return 0;
+}
+#endif
+
 gboolean handleresize(GtkWidget* widget, GdkEventConfigure* event, struct viddata* data)
 {
   if(event->width!=gtk_widget_get_allocated_width(cambox))
@@ -821,6 +885,7 @@ void startsession(GtkButton* button, void* x)
   gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "channelconfig")));
   gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "channelpasswordwindow")));
   gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
+  gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "pushtotalk")));
   const char* nick=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_nick")));
   channel=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "cc_channel")));
   const char* chanpass=gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(gui, "channelpassword")));
@@ -895,7 +960,7 @@ void startsession(GtkButton* button, void* x)
 void captcha_done(GtkWidget* button, void* x)
 {
   gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(gui, "captcha")));
-  gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
+  gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(gui, "main")));
   write(tc_client_in[1], "\n", 1);
 }
 
@@ -931,7 +996,18 @@ int main(int argc, char** argv)
 
   unsigned int i;
   GtkWidget* item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast_camera"));
-  g_signal_connect(item, "toggled", G_CALLBACK(togglecam), data);
+  g_signal_connect(item, "toggled", G_CALLBACK(togglecam), 0);
+  item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast_mic"));
+  #ifdef HAVE_PULSEAUDIO
+  g_signal_connect(item, "toggled", G_CALLBACK(togglemic), 0);
+  item=GTK_WIDGET(gtk_builder_get_object(gui, "pushtotalk"));
+  g_signal_connect(item, "button-press-event", G_CALLBACK(mic_pushtotalk), (void*)1);
+  g_signal_connect(item, "button-release-event", G_CALLBACK(mic_pushtotalk), 0);
+  #else
+  gtk_widget_destroy(item);
+  #endif
+  item=GTK_WIDGET(gtk_builder_get_object(gui, "menuitem_broadcast_stop"));
+  g_signal_connect(item, "activate", G_CALLBACK(stopbroadcasting), 0);
   data->vencoder=avcodec_find_encoder(AV_CODEC_ID_FLV1);
   // Set up cam selection and preview
   campreview.cam=GTK_WIDGET(gtk_builder_get_object(gui, "camselect_preview"));
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index aaab747..d506190 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -24,6 +24,9 @@
 #else
   #include <libavcore/imgutils.h>
 #endif
+#ifdef HAVE_PULSEAUDIO
+  #include <pulse/simple.h>
+#endif
 #include "../libcamera/camera.h"
 #include "compat.h"
 #include "../compat.h"
@@ -49,6 +52,10 @@ unsigned int camrowcount=0;
 GdkPixbufAnimation* camplaceholder=0;
 GdkPixbufAnimationIter* camplaceholder_iter=0;
 CAM* camout_cam=0;
+#ifdef HAVE_PULSEAUDIO
+char pushtotalk_enabled=0;
+char pushtotalk_pushed=0;
+#endif
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 // Experimental mixer, not sure if it really works
@@ -226,7 +233,10 @@ void freebuffer(guchar* pixels, gpointer data){free(pixels);}
 unsigned int camout_delay;
 void startcamout(CAM* cam)
 {
-  dprintf(tc_client_in[1], "/camup\n");
+  if(!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtk_builder_get_object(gui, "menuitem_broadcast_mic"))))
+  { // Only /camup if we're not already broadcasting mic
+    dprintf(tc_client_in[1], "/camup\n");
+  }
   camout_cam=cam;
   camout_delay=500;
   camsize_out.width=320;
@@ -523,3 +533,82 @@ GdkPixbuf* scaled_gdk_pixbuf_from_cam(CAM* cam, unsigned int width, unsigned int
   }
   return gdkframe;
 }
+
+#ifdef HAVE_PULSEAUDIO
+void* audiothread_in(void* fdp)
+{
+  int fd=*(int*)fdp;
+  pa_simple* pulse;
+  pa_sample_spec pulsespec;
+  pulsespec.format=PA_SAMPLE_FLOAT32;
+  pulsespec.channels=1;
+  pulsespec.rate=44100;
+  pulse=pa_simple_new(0, "tc_client-gtk", PA_STREAM_RECORD, 0, "mic", &pulsespec, 0, 0, 0);
+  char buf[1024];
+  // Just read/listen and write to the main thread until either reading/listening or writing fails
+  while(1)
+  {
+    if(pa_simple_read(pulse, buf, 1024, 0)<0){break;}
+    if(write(fd, buf, 1024)<1024){break;}
+  }
+  pa_simple_free(pulse);
+  close(fd);
+  return 0;
+}
+
+gboolean mic_encode(GIOChannel* iochannel, GIOCondition condition, gpointer datap)
+{
+  static AVFrame* micframe=0;
+  static AVCodecContext* avctx=0;
+  if(!micframe)
+  {
+    micframe=av_frame_alloc();
+    micframe->format=AV_SAMPLE_FMT_FLT;
+    micframe->sample_rate=44100;
+    unsigned int i;
+    for(i=0; i<AV_NUM_DATA_POINTERS; ++i)
+    {
+      micframe->data[i]=0;
+      micframe->linesize[i]=0;
+    }
+    AVCodec* encoder=avcodec_find_encoder(AV_CODEC_ID_NELLYMOSER);
+    avctx=avcodec_alloc_context3(encoder);
+    avctx->sample_fmt=AV_SAMPLE_FMT_FLT;
+    avctx->sample_rate=44100;
+    avctx->channels=1;
+    avctx->time_base.num=1;
+    avctx->time_base.den=10;
+    avcodec_open2(avctx, encoder, 0);
+  }
+  // Read up to 1024 bytes (256 samples) and start encoding
+  unsigned char buf[1024];
+  gsize r;
+  int status=g_io_channel_read_chars(iochannel, (char*)buf, 1024, &r, 0);
+  if(status!=G_IO_STATUS_NORMAL){return 0;}
+  // If push-to-talk is enabled but not pushed, return as soon as we've consumed the incoming data
+  if(pushtotalk_enabled && !pushtotalk_pushed){return 1;}
+  micframe->nb_samples=r/4; // 32bit floats
+  micframe->data[0]=buf;
+  micframe->linesize[0]=r;
+  AVPacket packet={
+#ifdef AVPACKET_HAS_BUF
+    .buf=0,
+#endif
+    .data=0,
+    .size=0,
+    .dts=AV_NOPTS_VALUE,
+    .pts=AV_NOPTS_VALUE
+  };
+  av_init_packet(&packet);
+  avcodec_send_frame(avctx, micframe);
+  if(avcodec_receive_packet(avctx, &packet)){return 1;}
+  unsigned char frameinfo=0x6c; // 6=Nellymoser, 3<<2=44100 samplerate
+  // Send audio
+  dprintf(tc_client_in[1], "/audio %i\n", packet.size+1);
+  write(tc_client_in[1], &frameinfo, 1);
+  write(tc_client_in[1], packet.data, packet.size);
+
+  av_packet_unref(&packet);
+  return 1;
+}
+#endif
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index 44d15e3..3e06f78 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -61,6 +61,8 @@ extern GtkWidget* cambox;
 extern GdkPixbufAnimation* camplaceholder;
 extern GdkPixbufAnimationIter* camplaceholder_iter;
 extern CAM* camout_cam;
+extern char pushtotalk_enabled;
+extern char pushtotalk_pushed;
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 extern void camera_playsnd(int audiopipe, struct camera* cam, short* samples, unsigned int samplecount);
@@ -83,4 +85,6 @@ extern void camera_postproc(struct camera* cam, unsigned char* buf, unsigned int
 extern void updatescaling(unsigned int width, unsigned int height, char changedcams);
 extern gboolean camplaceholder_update(void* id);
 extern GdkPixbuf* scaled_gdk_pixbuf_from_cam(CAM* cam, unsigned int width, unsigned int height, unsigned int maxwidth, unsigned int maxheight);
+extern void* audiothread_in(void* fdp);
+extern gboolean mic_encode(GIOChannel* iochannel, GIOCondition condition, gpointer datap);
 #endif