$ 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);