$ git clone http://tcclient.ion.nu/tc_client.git
commit 4d8d5aeae656f49d026bfd4826202f9d3d26c9ec
Author: Alicia <...>
Date:   Mon May 2 20:54:55 2016 +0200

    tc_client-gtk: moved encoding out of the cam thread, allowing postprocessing to be applied to the broadcasted frame.

diff --git a/ChangeLog b/ChangeLog
index 29a2096..b563d52 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,7 @@
 modbot: only use youtube-dl's 'ytsearch:' for --get-id, and only if it doesn't appear to be an ID already.
 tc_client-gtk: handle only one sendmessage event at a time, and don't send empty lines.
 tc_client-gtk: added postprocessing options for cams upon right-click, starting with brightness adjustment.
+tc_client-gtk: moved encoding out of the cam thread, allowing postprocessing to be applied to the broadcasted frame.
 tc_client-gtk and camviewer: fixed compatibility with newer libavutil.
 0.38:
 Handle multi-line messages.
diff --git a/utilities/gtk/camviewer.c b/utilities/gtk/camviewer.c
index 173827d..175dbc9 100644
--- a/utilities/gtk/camviewer.c
+++ b/utilities/gtk/camviewer.c
@@ -1,6 +1,6 @@
 /*
     tc_client-gtk, a graphical user interface for tc_client
-    Copyright (C) 2015  alicia@ion.nu
+    Copyright (C) 2015-2016  alicia@ion.nu
     Copyright (C) 2015  Pamela Hiatt
 
     This program is free software: you can redistribute it and/or modify
@@ -62,8 +62,6 @@ struct viddata
   AVCodec* vdecoder;
   AVCodec* vencoder;
   AVCodec* adecoder;
-  int scalewidth;
-  int scaleheight;
 #ifdef HAVE_AVRESAMPLE
   int audiopipe;
   AVAudioResampleContext* resamplectx;
@@ -93,9 +91,9 @@ void updatescaling(struct viddata* data, unsigned int width, unsigned int height
   if(!camcount){return;}
   if(!width){width=gtk_widget_get_allocated_width(data->box);}
   if(!height){height=gtk_widget_get_allocated_height(data->box);}
-  data->scalewidth=width/camcount;
+  camsize_scale.width=width/camcount;
   // 3/4 ratio
-  data->scaleheight=data->scalewidth*3/4;
+  camsize_scale.height=camsize_scale.width*3/4;
   unsigned int i;
   unsigned int labelsize=0;
   for(i=0; i<camcount; ++i)
@@ -104,13 +102,13 @@ void updatescaling(struct viddata* data, unsigned int width, unsigned int height
       labelsize=gtk_widget_get_allocated_height(cams[i].label);
   }
   // Fit by height
-  if(height<data->scaleheight+labelsize)
+  if(height<camsize_scale.height+labelsize)
   {
-    data->scaleheight=height-labelsize;
-    data->scalewidth=data->scaleheight*4/3;
+    camsize_scale.height=height-labelsize;
+    camsize_scale.width=camsize_scale.height*4/3;
   }
-  if(data->scalewidth<8){data->scalewidth=8;}
-  if(data->scaleheight<1){data->scaleheight=1;}
+  if(camsize_scale.width<8){camsize_scale.width=8;}
+  if(camsize_scale.height<1){camsize_scale.height=1;}
   // TODO: wrapping and stuff
   // Rescale current images to fit
   for(i=0; i<camcount; ++i)
@@ -118,7 +116,7 @@ void updatescaling(struct viddata* data, unsigned int width, unsigned int height
     GdkPixbuf* pixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cams[i].cam));
     if(!pixbuf){continue;}
     GdkPixbuf* old=pixbuf;
-    pixbuf=gdk_pixbuf_scale_simple(pixbuf, data->scalewidth, data->scaleheight, GDK_INTERP_BILINEAR);
+    pixbuf=gdk_pixbuf_scale_simple(pixbuf, camsize_scale.width, camsize_scale.height, GDK_INTERP_BILINEAR);
     gtk_image_set_from_pixbuf(GTK_IMAGE(cams[i].cam), pixbuf);
     g_object_unref(old);
   }
@@ -199,8 +197,6 @@ void printchat_color(const char* text, const char* color, unsigned int offset, c
   if(bottom){autoscroll_after(scroll);}
 }
 
-void freebuffer(guchar* pixels, gpointer data){free(pixels);}
-
 char buf[1024];
 gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer datap)
 {
@@ -482,8 +478,16 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
   if(!strcmp(buf, "Starting outgoing media stream"))
   {
     struct camera* cam=camera_new(nickname, "out");
-    cam->vctx=avcodec_alloc_context3(data->vdecoder);
-    avcodec_open2(cam->vctx, data->vdecoder, 0);
+    cam->vctx=avcodec_alloc_context3(data->vencoder);
+    cam->vctx->pix_fmt=AV_PIX_FMT_YUV420P;
+    cam->vctx->time_base.num=1;
+    cam->vctx->time_base.den=10;
+    cam->vctx->width=camsize_out.width;
+    cam->vctx->height=camsize_out.height;
+    avcodec_open2(cam->vctx, data->vencoder, 0);
+    cam->frame->data[0]=0;
+    cam->dstframe->data[0]=0;
+
     cam->actx=0;
     gtk_box_pack_start(GTK_BOX(data->box), cam->box, 0, 0, 0);
     gtk_widget_show_all(cam->box);
@@ -559,21 +563,6 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
     g_io_channel_read_chars(iochannel, (gchar*)pkt.data+pos, size-pos, &r, 0);
     pos+=r;
   }
-  unsigned int scalewidth=data->scalewidth;
-  unsigned int scaleheight=data->scaleheight;
-  if(!strcmp(&buf[7], "out"))
-  {
-    if(cam)
-    {
-      dprintf(tc_client_in[1], "/video %i\n", size+1);
-      write(tc_client_in[1], &frameinfo, 1);
-      write(tc_client_in[1], pkt.data, size);
-    }else{
-      cam=&campreview;
-      scalewidth=320;
-      scaleheight=240;
-    }
-  }
   if((frameinfo&0xf)!=2){return 1;} // Not FLV1, get data but discard it
   if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); return 1;}
   pkt.size=size;
@@ -582,17 +571,17 @@ gboolean handledata(GIOChannel* iochannel, GIOCondition condition, gpointer data
   if(!gotframe){return 1;}
 
   // Scale and convert to RGB24 format
-  unsigned int bufsize=avpicture_get_size(AV_PIX_FMT_RGB24, scalewidth, scaleheight);
+  unsigned int bufsize=av_image_get_buffer_size(AV_PIX_FMT_RGB24, camsize_scale.width, camsize_scale.height, 1);
   unsigned char* buf=malloc(bufsize);
   cam->dstframe->data[0]=buf;
-  cam->dstframe->linesize[0]=scalewidth*3;
-  struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, scalewidth, scaleheight, AV_PIX_FMT_RGB24, 0, 0, 0, 0);
+  cam->dstframe->linesize[0]=camsize_scale.width*3;
+  struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, camsize_scale.width, camsize_scale.height, AV_PIX_FMT_RGB24, 0, 0, 0, 0);
   sws_scale(swsctx, (const uint8_t*const*)cam->frame->data, cam->frame->linesize, 0, cam->frame->height, cam->dstframe->data, cam->dstframe->linesize);
   sws_freeContext(swsctx);
-  camera_postproc(cam, buf, scalewidth*scaleheight);
+  camera_postproc(cam, cam->dstframe->data[0], camsize_scale.width*camsize_scale.height);
 
   GdkPixbuf* oldpixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cam->cam));
-  GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, scalewidth, scaleheight, cam->dstframe->linesize[0], freebuffer, 0);
+  GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, camsize_scale.width, camsize_scale.height, cam->dstframe->linesize[0], freebuffer, 0);
   gtk_image_set_from_pixbuf(GTK_IMAGE(cam->cam), gdkframe);
   g_object_unref(oldpixbuf);
 
@@ -642,7 +631,7 @@ void togglecam(GtkCheckMenuItem* item, struct viddata* data)
   // Start a cam thread for the selected cam
   GtkComboBox* combo=GTK_COMBO_BOX(gtk_builder_get_object(gui, "camselect_combo"));
   GIOChannel* channel=camthread(gtk_combo_box_get_active_id(combo), data->vencoder, 100000);
-  cameventsource=g_io_add_watch(channel, G_IO_IN, handledata, 0);
+  cameventsource=g_io_add_watch(channel, G_IO_IN, cam_encode, 0);
 }
 
 gboolean handleresize(GtkWidget* widget, GdkEventConfigure* event, struct viddata* data)
@@ -957,9 +946,7 @@ int main(int argc, char** argv)
   // Set up cam selection and preview
   campreview.cam=GTK_WIDGET(gtk_builder_get_object(gui, "camselect_preview"));
   campreview.frame=av_frame_alloc();
-  campreview.dstframe=av_frame_alloc();
-  campreview.vctx=avcodec_alloc_context3(data->vdecoder);
-  avcodec_open2(campreview.vctx, data->vdecoder, 0);
+  campreview.frame->data[0]=0;
   GtkComboBox* combo=GTK_COMBO_BOX(gtk_builder_get_object(gui, "camselect_combo"));
   g_signal_connect(combo, "changed", G_CALLBACK(camselect_change), data->vencoder);
   // Signals for cancelling
diff --git a/utilities/gtk/media.c b/utilities/gtk/media.c
index 59db50a..4aa9766 100644
--- a/utilities/gtk/media.c
+++ b/utilities/gtk/media.c
@@ -1,6 +1,6 @@
 /*
     tc_client-gtk, a graphical user interface for tc_client
-    Copyright (C) 2015  alicia@ion.nu
+    Copyright (C) 2015-2016  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -32,6 +32,7 @@
 #include "../compat.h"
 #include "gui.h"
 #include "media.h"
+extern int tc_client_in[2];
 struct camera campreview={
   .postproc.min_brightness=0,
   .postproc.max_brightness=255,
@@ -44,6 +45,8 @@ unsigned int camcount=0;
 #else
   pid_t camproc=0;
 #endif
+struct size camsize_out={.width=320, .height=240};
+struct size camsize_scale={.width=320, .height=240};
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 // Experimental mixer, not sure if it really works
@@ -187,6 +190,72 @@ void camera_cleanup(void)
   free(cams);
 }
 
+void freebuffer(guchar* pixels, gpointer data){free(pixels);}
+
+gboolean cam_encode(GIOChannel* iochannel, GIOCondition condition, gpointer datap)
+{
+  struct camera* cam=camera_find("out");
+  char preview=0;
+  if(!cam)
+  {
+    cam=&campreview;
+    preview=1;
+  }else{
+    cam->vctx->width=camsize_out.width;
+    cam->vctx->height=camsize_out.height;
+    if(!cam->dstframe->data[0])
+    {
+      cam->dstframe->format=AV_PIX_FMT_YUV420P;
+      cam->dstframe->width=camsize_out.width;
+      cam->dstframe->height=camsize_out.height;
+      av_image_alloc(cam->dstframe->data, cam->dstframe->linesize, camsize_out.width, camsize_out.height, cam->dstframe->format, 1);
+    }
+  }
+  if(!cam->frame->data[0])
+  {
+    cam->frame->format=AV_PIX_FMT_RGB24;
+    cam->frame->width=camsize_out.width;
+    cam->frame->height=camsize_out.height;
+    cam->frame->linesize[0]=cam->frame->width*3;
+    av_image_alloc(cam->frame->data, cam->frame->linesize, camsize_out.width, camsize_out.height, cam->frame->format, 1);
+  }
+  g_io_channel_read_chars(iochannel, (void*)cam->frame->data[0], camsize_out.width*camsize_out.height*3, 0, 0);
+  camera_postproc(cam, cam->frame->data[0], cam->frame->width*cam->frame->height);
+  // Update our local display
+  GdkPixbuf* oldpixbuf=gtk_image_get_pixbuf(GTK_IMAGE(cam->cam));
+  GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->frame->data[0], GDK_COLORSPACE_RGB, 0, 8, cam->frame->width, cam->frame->height, cam->frame->linesize[0], 0, 0);
+  if(!preview) // Scale, unless we're previewing
+  {
+    gdkframe=gdk_pixbuf_scale_simple(gdkframe, camsize_scale.width, camsize_scale.height, GDK_INTERP_BILINEAR);
+  }
+  gtk_image_set_from_pixbuf(GTK_IMAGE(cam->cam), gdkframe);
+  g_object_unref(oldpixbuf);
+  if(preview){return 1;}
+  // Encode
+  struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, cam->frame->width, cam->frame->height, AV_PIX_FMT_YUV420P, 0, 0, 0, 0);
+  sws_scale(swsctx, (const uint8_t*const*)cam->frame->data, cam->frame->linesize, 0, cam->frame->height, cam->dstframe->data, cam->dstframe->linesize);
+  sws_freeContext(swsctx);
+  int gotpacket;
+  AVPacket packet={
+    .buf=0,
+    .data=0,
+    .size=0,
+    .dts=AV_NOPTS_VALUE,
+    .pts=AV_NOPTS_VALUE
+  };
+  av_init_packet(&packet);
+  avcodec_encode_video2(cam->vctx, &packet, cam->dstframe, &gotpacket);
+  unsigned char frameinfo=0x22; // Note: differentiating between keyframes and non-keyframes seems to break stuff, so let's just go with all being interframes (1=keyframe, 2=interframe, 3=disposable interframe)
+  // Send video
+  dprintf(tc_client_in[1], "/video %i\n", packet.size+1);
+  write(tc_client_in[1], &frameinfo, 1);
+  ssize_t w=write(tc_client_in[1], packet.data, packet.size);
+if(w!=packet.size){printf("Error: wrote %li of %i bytes\n", w, packet.size);}
+
+  av_packet_unref(&packet);
+  return 1;
+}
+
 unsigned int* camthread_delay;
 void camthread_resetdelay(int x)
 {
@@ -201,10 +270,11 @@ GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
     kill(camproc, SIGINT);
     usleep(200000); // Give the previous process some time to shut down
   }
-  // Set up a second pipe to be handled by handledata() to avoid overlap with tc_client's output
+  // Set up a pipe to be handled by cam_encode()
   int campipe[2];
 #ifndef _WIN32
   CAM* cam=cam_open(name); // Opening here in case of GUI callbacks
+  cam_resolution(cam, &camsize_out.width, &camsize_out.height);
   pipe(campipe);
   camproc=fork();
   if(!camproc)
@@ -216,56 +286,14 @@ GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
     camthread_delay=&delay;
     signal(SIGUSR1, camthread_resetdelay);
 #endif
-    // Set up camera
-    AVCodecContext* ctx=avcodec_alloc_context3(vencoder);
-    ctx->width=320;
-    ctx->height=240;
-    cam_resolution(cam, (unsigned int*)&ctx->width, (unsigned int*)&ctx->height);
-    ctx->pix_fmt=AV_PIX_FMT_YUV420P;
-    ctx->time_base.num=1;
-    ctx->time_base.den=10;
-    avcodec_open2(ctx, vencoder, 0);
-    AVFrame* frame=av_frame_alloc();
-    frame->format=AV_PIX_FMT_RGB24;
-    frame->width=ctx->width;
-    frame->height=ctx->height;
-    av_image_alloc(frame->data, frame->linesize, ctx->width, ctx->height, frame->format, 1);
-    AVPacket packet;
-    packet.buf=0;
-    packet.data=0;
-    packet.size=0;
-    packet.dts=AV_NOPTS_VALUE;
-    packet.pts=AV_NOPTS_VALUE;
-
-    // Set up frame for conversion from the camera's format to a format the encoder can use
-    AVFrame* dstframe=av_frame_alloc();
-    dstframe->format=ctx->pix_fmt;
-    dstframe->width=ctx->width;
-    dstframe->height=ctx->height;
-    av_image_alloc(dstframe->data, dstframe->linesize, ctx->width, ctx->height, ctx->pix_fmt, 1);
-
-    struct SwsContext* swsctx=sws_getContext(frame->width, frame->height, AV_PIX_FMT_RGB24, frame->width, frame->height, AV_PIX_FMT_YUV420P, 0, 0, 0, 0);
-
+    unsigned char img[camsize_out.width*camsize_out.height*3];
     while(1)
     {
       usleep(delay);
       if(delay>100000){delay-=50000;}
-      cam_getframe(cam, frame->data[0]);
-      int gotpacket;
-      sws_scale(swsctx, (const uint8_t*const*)frame->data, frame->linesize, 0, frame->height, dstframe->data, dstframe->linesize);
-      av_init_packet(&packet);
-      packet.data=0;
-      packet.size=0;
-      avcodec_encode_video2(ctx, &packet, dstframe, &gotpacket);
-      unsigned char frameinfo=0x22; // Note: differentiating between keyframes and non-keyframes seems to break stuff, so let's just go with all being interframes (1=keyframe, 2=interframe, 3=disposable interframe)
-      // Send the packet to our main thread so we can see ourselves (the main thread also sends it to the server)
-      dprintf(campipe[1], "Video: out %i\n", packet.size+1);
-      write(campipe[1], &frameinfo, 1);
-      write(campipe[1], packet.data, packet.size);
-
-      av_free_packet(&packet);
+      cam_getframe(cam, img);
+      write(campipe[1], img, camsize_out.width*camsize_out.height*3);
     }
-    sws_freeContext(swsctx);
     _exit(0);
   }
   if(cam){cam_close(cam);} // Leave the cam to the child process
@@ -280,12 +308,11 @@ GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay)
   return channel;
 }
 
-extern GIOFunc handledata;
 void camselect_change(GtkComboBox* combo, AVCodec* vencoder)
 {
   if(!camproc){return;} // If there isn't a camthread already, it will be started elsewhere
   GIOChannel* channel=camthread(gtk_combo_box_get_active_id(combo), vencoder, 100000);
-  cameventsource=g_io_add_watch(channel, G_IO_IN, (void*)&handledata, 0);
+  cameventsource=g_io_add_watch(channel, G_IO_IN, cam_encode, 0);
 }
 
 gboolean camselect_cancel(GtkWidget* widget, void* x1, void* x2)
@@ -310,7 +337,7 @@ void camselect_accept(GtkWidget* widget, AVCodec* vencoder)
   // For platforms without proper signals, resort to restarting the camthread with the new delay
   GtkComboBox* combo=GTK_COMBO_BOX(gtk_builder_get_object(gui, "camselect_combo"));
   GIOChannel* channel=camthread(gtk_combo_box_get_active_id(combo), vencoder, 500000);
-  cameventsource=g_io_add_watch(channel, G_IO_IN, (void*)&handledata, 0);
+  cameventsource=g_io_add_watch(channel, G_IO_IN, cam_encode, 0);
 #endif
   dprintf(tc_client_in[1], "/camup\n");
 }
diff --git a/utilities/gtk/media.h b/utilities/gtk/media.h
index 21ab1dd..6426b89 100644
--- a/utilities/gtk/media.h
+++ b/utilities/gtk/media.h
@@ -1,6 +1,6 @@
 /*
     tc_client-gtk, a graphical user interface for tc_client
-    Copyright (C) 2015  alicia@ion.nu
+    Copyright (C) 2015-2016  alicia@ion.nu
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
@@ -35,6 +35,11 @@ struct camera
     char autoadjust;
   } postproc;
 };
+struct size
+{
+  unsigned int width;
+  unsigned int height;
+};
 extern struct camera campreview;
 extern struct camera* cams;
 extern unsigned int camcount;
@@ -44,6 +49,8 @@ extern unsigned int camcount;
 #else
   extern pid_t camproc;
 #endif
+extern struct size camsize_out;
+extern struct size camsize_scale;
 
 #if defined(HAVE_AVRESAMPLE) || defined(HAVE_SWRESAMPLE)
 extern void camera_playsnd(int audiopipe, struct camera* cam, short* samples, unsigned int samplecount);
@@ -54,6 +61,8 @@ extern struct camera* camera_find(const char* id);
 extern struct camera* camera_findbynick(const char* nick);
 extern struct camera* camera_new(const char* nick, const char* id);
 extern void camera_cleanup(void);
+extern void freebuffer(guchar* pixels, gpointer data);
+extern gboolean cam_encode(GIOChannel* iochannel, GIOCondition condition, gpointer datap);
 extern GIOChannel* camthread(const char* name, AVCodec* vencoder, unsigned int delay);
 extern void camselect_change(GtkComboBox* combo, AVCodec* vencoder);
 extern gboolean camselect_cancel(GtkWidget* widget, void* x1, void* x2);