// 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); }