$ git clone http://tcclient.ion.nu/tc_client.git
commit ca3be817069c0a782732840f5db4d3ed666e7e17
Author: Alicia <...>
Date:   Tue Nov 22 20:11:04 2016 +0100

    tc_client-gtk: rewrote the audio mixer.

diff --git a/ChangeLog b/ChangeLog
index 79884c9..3df396d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -8,6 +8,7 @@ tc_client-gtk: mark outgoing video keyframes as keyframes.
 tc_client-gtk: added options for making links blue and how to wrap lines.
 tc_client-gtk: added volume indicators.
 tc_client-gtk: handle failure to connect to pulseaudio more gracefully.
+tc_client-gtk: rewrote the audio mixer.
 dist/appimage.sh: fix audio in appimages by building ffmpeg with support for nellymoser and speex, and depending on the system's libao and libpulse instead of including it in the appimage.
 libcamera(escapi): handle failure to open camera more gracefully.
 0.40:
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 39555dd..8a4b4c4 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -248,7 +248,7 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer x)
   #else
     int outlen=swr_convert(cam->swrctx, (void*)outdata, samplecount, (const uint8_t**)cam->frame->data, cam->frame->nb_samples);
   #endif
-    if(outlen>0){camera_playsnd(data->audiopipe, cam, outbuf, outlen);}
+    if(outlen>0){camera_playsnd(cam, outbuf, outlen);}
 #endif
     return 1;
   }
@@ -939,7 +939,7 @@ void captcha_done(GtkWidget* button, void* x)
   write(tc_client_in[1], "\n", 1);
 }
 
-void justwait(int x){wait(0);}
+void justwait(int x){waitpid(-1, 0, WNOHANG);}
 
 int main(int argc, char** argv)
 {
@@ -967,6 +967,9 @@ int main(int argc, char** argv)
   gui_init(frombuild);
   campreview.frame=av_frame_alloc();
   campreview.frame->data[0]=0;
+#ifdef HAVE_LIBAO
+  g_timeout_add(40, audiomixer, &audiopipe[1]);
+#endif
   gtk_main();
  
 #ifdef _WIN32
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index 85f2cc5..2e9b693 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -59,33 +59,45 @@ char pushtotalk_pushed=0;
 #endif
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
-// Experimental mixer, not sure if it really works
-void camera_playsnd(int audiopipe, struct camera* cam, short* samples, unsigned int samplecount)
+void camera_playsnd(struct camera* cam, int16_t* samples, unsigned int samplecount)
 {
-  if(cam->samples)
+  cam->samples=realloc(cam->samples, sizeof(int16_t)*(cam->samplecount+samplecount));
+  memcpy(&cam->samples[cam->samplecount], samples, samplecount*sizeof(short));
+  cam->samplecount+=samplecount;
+}
+
+gboolean audiomixer(void* p)
+{
+  int audiopipe=*(int*)p;
+  unsigned int i;
+  int sources=0;
+  for(i=0; i<camcount; ++i){sources+=!!cams[i].samplecount;}
+  if(!sources){return G_SOURCE_CONTINUE;}
+  unsigned int samplecount=SAMPLERATE_OUT/25; // Play one 25th of the samplerate's samples per iteration (which happens 25 times per second)
+  int16_t samples[samplecount];
+  memset(samples, 0, samplecount*sizeof(int16_t));
+  for(i=0; i<camcount; ++i)
   {
-// int sources=1;
-    unsigned int i;
-    for(i=0; i<camcount; ++i)
+    if(!cams[i].samplecount){continue;}
+    unsigned j;
+    for(j=0; j<samplecount && j<cams[i].samplecount; ++j)
     {
-      if(!cams[i].samples){continue;}
-      if(cam==&cams[i]){continue;}
-      unsigned j;
-      for(j=0; j<cam->samplecount && j<cams[i].samplecount; ++j)
-      {
-        cam->samples[j]+=cams[i].samples[j];
-      }
-      free(cams[i].samples);
-      cams[i].samples=0;
-// ++sources;
+      // Divide by number of sources to prevent integer overflow
+      samples[j]+=cams[i].samples[j]/sources;
+    }
+    if(cams[i].samplecount>samplecount)
+    {
+      cams[i].samplecount-=samplecount;
+      // Deal with drift and post-lag floods
+      if(cams[i].samplecount>SAMPLERATE_OUT/5){cams[i].samplecount=0; continue;}
+    }else{
+      cams[i].samplecount=0;
+      continue;
     }
-    write(audiopipe, cam->samples, cam->samplecount*sizeof(short));
-    free(cam->samples);
-// printf("Mixed sound from %i sources (cam: %p)\n", sources, cam);
+    memmove(cams[i].samples, &cams[i].samples[samplecount], sizeof(int16_t)*cams[i].samplecount);
   }
-  cam->samples=malloc(samplecount*sizeof(short));
-  memcpy(cam->samples, samples, samplecount*sizeof(short));
-  cam->samplecount=samplecount;
+  write(audiopipe, samples, samplecount*sizeof(int16_t));
+  return G_SOURCE_CONTINUE;
 }
 #endif
 
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index bb23e2d..bb0757c 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -67,7 +67,8 @@ 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);
+extern void camera_playsnd(struct camera* cam, int16_t* samples, unsigned int samplecount);
+extern gboolean audiomixer(void* p);
 #endif
 extern void camera_remove(const char* id, char isnick);
 extern struct camera* camera_find(const char* id);