From 68c4353c1b0513e2efaa42abc804309cb3ed3430 Mon Sep 17 00:00:00 2001 From: Trever Fischer Date: Tue, 1 Mar 2011 18:14:00 -0500 Subject: Rename project to 'anchorman', and prepare for public consumption. Inspired by the movie of the same name, anchorman now aims to be a decent tool for running your own broadcasting studio. --- src/anchorman.c | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 src/anchorman.c (limited to 'src/anchorman.c') diff --git a/src/anchorman.c b/src/anchorman.c new file mode 100644 index 0000000..6ed979b --- /dev/null +++ b/src/anchorman.c @@ -0,0 +1,238 @@ +// anchorman - A program to stream webcams. +// +// Copyright (C) 2011 Trever Fischer +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#define G_UDEV_API_IS_SUBJECT_TO_CHANGE + +#include +#include +#include + +// Original gst-launch command: +// gst-launch v4l2src device=/dev/video0 ! ffmpegcolorspace ! videorate ! videoscale ! video/x-raw-yuv,width=320,height=240 ! theoraenc bitrate=64 ! queue leaky=1 ! oggmux name=mux alsasrc device=hw:1 ! audioconvert ! vorbisenc ! queue leaky=1 ! mux. mux. ! queue ! shout2send ip=acm.cs.uakron.edu mount=lab.ogg + +GMainLoop *loop; + +static gchar *videoDevice = 0; +static gchar *audioDevice = 0; +static gchar *icecastServer = 0; +static gchar *icecastMount = 0; + +static GOptionEntry entries[] = +{ + { "video-device", 'v', 0, G_OPTION_ARG_STRING, &videoDevice, "Video device to use", "device" }, + { "audio-device", 'a', 0, G_OPTION_ARG_STRING, &audioDevice, "Audio device to use", "device" }, + { "server", 's', 0, G_OPTION_ARG_STRING, &icecastServer, "Icecast server to stream to", "server" }, + { "mount", 'm', 0, G_OPTION_ARG_STRING, &icecastMount, "Icecast mount to stream to on the server", "mount" }, + { NULL } +}; + +static const gchar *subsystems[] = +{ + "video4linux", + NULL +}; + +gboolean startPipeline(GstElement *pipe) { + GstStateChangeReturn stateChange; + stateChange = gst_element_set_state(pipe, GST_STATE_PLAYING); + switch(stateChange) { + case GST_STATE_CHANGE_SUCCESS: + case GST_STATE_CHANGE_ASYNC: + return TRUE; + default: + return FALSE; + } +} + +gboolean stopPipeline(GstElement *pipe) { + GstStateChangeReturn stateChange; + stateChange = gst_element_set_state(pipe, GST_STATE_NULL); + switch(stateChange) { + case GST_STATE_CHANGE_SUCCESS: + case GST_STATE_CHANGE_ASYNC: + return TRUE; + default: + return FALSE; + } +} + +void newDevice_cb(GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data) +{ + g_debug("Got %s event for %s", action, g_udev_device_get_name(device)); + if (strcmp(g_udev_device_get_device_file(device), videoDevice) == 0) { + if (strcmp(action, "add") == 0) { + g_print("Device added.\n"); + while(!startPipeline((GstElement*)data)); + } else if (strcmp(action, "remove") == 0) { + g_print("Device removed.\n"); + while(!stopPipeline((GstElement*)data)); + } + } +} + +static gboolean message_cb(GstBus *bus, GstMessage *msg, gpointer data) +{ + g_debug("Got message %s", GST_MESSAGE_TYPE_NAME(msg)); +} + +static gboolean state_cb(GstBus *bus, GstMessage *msg, gpointer data) +{ + GstState oldState; + GstState newState; + GstState pendingState; + gchar *name; + + gst_message_parse_state_changed(msg, &oldState, &newState, &pendingState); + name = gst_element_get_name(msg->src); + g_debug("Element %s changed state from %s to %s", name, gst_element_state_get_name(oldState), gst_element_state_get_name(newState)); + g_free(name); +} + +static gboolean error_cb(GstBus *bus, GstMessage *msg, gpointer data) +{ + GError *err; + gchar *debug; + gst_message_parse_error(msg, &err, &debug); + g_free(debug); + if (err->domain == GST_RESOURCE_ERROR) { + if (err->code == GST_RESOURCE_ERROR_OPEN_READ) { + g_debug("Could not open a device: %s", err->message); + goto out; + } + } else { + g_error("%d: %s", err->code, err->message); + } + g_main_loop_quit(loop); +out: + g_error_free(err); +} + +static gboolean warning_cb(GstBus *bus, GstMessage *msg, gpointer data) +{ + GError *err; + gchar *debug; + gst_message_parse_warning(msg, &err, &debug); + g_free(debug); + g_warning("%d %d: %s", err->domain, err->code, err->message); + if (err->domain == 992 && err->code == 13) + while(!stopPipeline((GstElement*)data)); + g_error_free(err); +} + +GstElement *buildPipeline(const gchar *videoDev, const gchar *audioDev, const gchar *server, const gchar *mount) +{ + GstElement *pipe = gst_pipeline_new(NULL); + + // Video pipeline + GstElement *v4lsrc = gst_element_factory_make("v4l2src", NULL); + GstElement *convert = gst_element_factory_make("ffmpegcolorspace", NULL); + GstElement *rate = gst_element_factory_make("videorate", NULL); + GstElement *scale = gst_element_factory_make("videoscale", NULL); + GstElement *encode = gst_element_factory_make("theoraenc", NULL); + GstElement *videoQueue = gst_element_factory_make("queue", NULL); + + gst_bin_add_many(GST_BIN(pipe), v4lsrc, convert, rate, scale, encode, videoQueue, NULL); + gst_element_link(encode, videoQueue); + gst_element_link_many(v4lsrc, convert, rate, scale); + GstCaps *caps = gst_caps_new_simple("video/x-raw-yuv", "width", G_TYPE_INT, 480, "height", G_TYPE_INT, 360, NULL); + gst_element_link_filtered(scale, encode, caps); + + // Audio pipeline + GstElement *alsasrc = gst_element_factory_make("alsasrc", NULL); + GstElement *audioConvert = gst_element_factory_make("audioconvert", NULL); + GstElement *vorbisenc = gst_element_factory_make("vorbisenc", NULL); + GstElement *audioQueue = gst_element_factory_make("queue", NULL); + + gst_bin_add_many(GST_BIN(pipe), alsasrc, audioConvert, vorbisenc, audioQueue, NULL); + gst_element_link_many(alsasrc, audioConvert, vorbisenc, audioQueue, NULL); + + + // Combine + GstElement *oggMux = gst_element_factory_make("oggmux", NULL); + gst_bin_add(GST_BIN(pipe), oggMux); + gst_element_link(videoQueue, oggMux); + gst_element_link(audioQueue, oggMux); + + // Broadcast! + GstElement *shout = gst_element_factory_make("shout2send", NULL); + + gst_bin_add(GST_BIN(pipe), shout); + gst_element_link(oggMux, shout); + + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipe)); + gst_bus_add_signal_watch(bus); + g_signal_connect(bus, "message::error", G_CALLBACK(error_cb), pipe); + g_signal_connect(bus, "message::warning", G_CALLBACK(warning_cb), pipe); + g_signal_connect(bus, "message::state-changed", G_CALLBACK(state_cb), pipe); + g_signal_connect(bus, "message", G_CALLBACK(message_cb), pipe); + + if (audioDevice) + g_object_set(G_OBJECT(alsasrc), "device", audioDev, NULL); + if (videoDevice) + g_object_set(G_OBJECT(v4lsrc), "device", videoDev, NULL); + if (icecastServer) + g_object_set(G_OBJECT(shout), "ip", server, NULL); + if (icecastMount) + g_object_set(G_OBJECT(shout), "mount", mount, NULL); + + g_object_set(G_OBJECT(encode), "bitrate", 64, NULL); + g_object_set(G_OBJECT(videoQueue), "leaky", 1, NULL); + g_object_set(G_OBJECT(audioQueue), "leaky", 1, NULL); + + return pipe; +} + +int main(int argc, char* argv[]) +{ + GError *error; + GOptionContext *context; + GstStateChangeReturn stateChange; + GUdevDevice *dev; + GUdevClient *udevClient; + GstElement *pipe; + + g_thread_init(NULL); + + context = g_option_context_new(" - Stream a webcam to an icecast server"); + g_option_context_add_main_entries(context, entries, NULL); + g_option_context_add_group(context, gst_init_get_option_group()); + + if (!g_option_context_parse(context, &argc, &argv, &error)) { + g_print("Bad arguments: %s\n", error->message); + exit(1); + } + + if (!gst_init_check(&argc, &argv, &error)) { + g_print("Could not initialize GStreamer: %s\n", error->message); + exit(1); + } + + if (!videoDevice) { + videoDevice = strdup("/dev/video0"); + } + + pipe = buildPipeline(videoDevice, audioDevice, icecastServer, icecastMount); + udevClient = g_udev_client_new(subsystems); + g_signal_connect(udevClient, "uevent", G_CALLBACK(newDevice_cb), pipe); + + startPipeline(pipe); + + loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(loop); +} -- cgit