From b3b51120e0afcb7670035c3bc6c7b8fb36c394d9 Mon Sep 17 00:00:00 2001 From: Derek Dai Date: Mon, 5 Dec 2016 08:34:03 +0800 Subject: [PATCH] source implement + sender talk via dbus --- res/CMakeLists.txt | 18 +- ...g.freedesktop.miracle.Sender.service.cmake | 3 + src/ctl/CMakeLists.txt | 32 +- src/ctl/ctl-src.c | 15 +- src/ctl/ctl.h | 1 + src/ctl/srcctl.c | 93 ++- src/stream/CMakeLists.txt | 37 +- src/stream/sender-iface.xml | 20 + src/stream/sender.c | 663 ++++++++++++++---- src/stream/sender.h | 25 + src/stream/sender.py | 66 ++ 11 files changed, 797 insertions(+), 176 deletions(-) create mode 100644 res/org.freedesktop.miracle.Sender.service.cmake create mode 100644 src/stream/sender-iface.xml create mode 100644 src/stream/sender.h create mode 100755 src/stream/sender.py diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt index 4cb3f19..5ca2b3f 100644 --- a/res/CMakeLists.txt +++ b/res/CMakeLists.txt @@ -1,12 +1,28 @@ ########### install files ############### +execute_process( + COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=session_bus_services_dir dbus-1 + OUTPUT_VARIABLE DBUS_SESSION_SERVICES_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +configure_file( + org.freedesktop.miracle.Sender.service.cmake + org.freedesktop.miracle.Sender.service + ) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/org.freedesktop.miracle.Sender.service + DESTINATION ${DBUS_SESSION_SERVICES_DIR}/ + ) + install( PROGRAMS miracle-gst gstplayer uibc-viewer DESTINATION bin ) -INSTALL( +install( FILES org.freedesktop.miracle.conf DESTINATION /etc/dbus-1/system.d ) diff --git a/res/org.freedesktop.miracle.Sender.service.cmake b/res/org.freedesktop.miracle.Sender.service.cmake new file mode 100644 index 0000000..99f29ae --- /dev/null +++ b/res/org.freedesktop.miracle.Sender.service.cmake @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.miracle.Sender +Exec=@CMAKE_INSTALL_PREFIX@/bin/miracle-sender diff --git a/src/ctl/CMakeLists.txt b/src/ctl/CMakeLists.txt index c8b291b..082c246 100644 --- a/src/ctl/CMakeLists.txt +++ b/src/ctl/CMakeLists.txt @@ -44,17 +44,23 @@ endif(READLINE_FOUND) target_link_libraries(miracle-sinkctl miracle-shared) -INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) +include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) ########### next target ############### +pkg_check_modules(GIO REQUIRED gio-2.0>=2.30) -set(miracle-srcctl_SRCS ctl.h - ctl-cli.c - ctl-src.h - ctl-src.c - ctl-wifi.c - srcctl.c - wfd.c) +set(miracle-srcctl_SRCS ctl.h + ctl-cli.c + ctl-src.h + ctl-src.c + ctl-wifi.c + srcctl.c + wfd.c) + +include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/shared + ${CMAKE_BINARY_DIR}/src/stream + ${GIO_INCLUDE_DIRS}) add_executable(miracle-srcctl ${miracle-srcctl_SRCS}) @@ -63,14 +69,14 @@ install(TARGETS miracle-srcctl DESTINATION bin) if(READLINE_FOUND) message(STATUS "Compiling with Readline support") set_property(TARGET miracle-srcctl - APPEND - PROPERTY COMPILE_DEFINITIONS HAVE_READLINE) + APPEND + PROPERTY COMPILE_DEFINITIONS HAVE_READLINE) target_link_libraries(miracle-srcctl ${READLINE_LIBRARY}) endif(READLINE_FOUND) -target_link_libraries(miracle-srcctl miracle-shared) - -INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/shared) +target_link_libraries(miracle-srcctl miracle-shared + sender-iface + ${GIO_LIBRARIES}) ########### install files ############### diff --git a/src/ctl/ctl-src.c b/src/ctl/ctl-src.c index 68087da..e3f7b2e 100644 --- a/src/ctl/ctl-src.c +++ b/src/ctl/ctl-src.c @@ -136,6 +136,8 @@ static void src_handle_setup(struct ctl_src *s, goto error; } + ctl_fn_src_setup(s); + return; error: @@ -389,7 +391,7 @@ static int src_send_set_parameter(struct ctl_src *s) _rtsp_message_unref_ struct rtsp_message *req; int r; const static char tmp[] = - "wfd_video_formats: 00 00 02 02 00000002 00000000 00000000 00 0000 0000 00 none none\n" + "wfd_video_formats: 38 00 02 10 00000080 00000000 00000000 00 0000 0000 00 none none\n" //"wfd_audio_codecs: AAC 00000001 00\n" //"wfd_uibc_capability: input_category_list=GENERIC\n;generic_cap_list=SingleTouch;hidc_cap_list=none;port=5100\n" //"wfd_uibc_setting: disable\n" @@ -690,7 +692,7 @@ static int src_io_fn(sd_event_source *source, static int src_listen(struct ctl_src *s) { - int fd, r; + int fd, r, enable = 1; if (!s) return cli_EINVAL(); @@ -705,8 +707,15 @@ static int src_listen(struct ctl_src *s) if (fd < 0) return cli_ERRNO(); + r = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)); + if(r < 0) { + r = -errno; + cli_vERR(r); + goto err_close; + } + r = bind(fd, (struct sockaddr*)&s->addr, s->addr_size); - if (fd < 0) { + if (r < 0) { r = -errno; cli_vERR(r); goto err_close; diff --git a/src/ctl/ctl.h b/src/ctl/ctl.h index a1abefd..9e246b3 100644 --- a/src/ctl/ctl.h +++ b/src/ctl/ctl.h @@ -253,6 +253,7 @@ void ctl_fn_peer_disconnected(struct ctl_peer *p); void ctl_fn_link_new(struct ctl_link *l); void ctl_fn_link_free(struct ctl_link *l); +void ctl_fn_src_setup(struct ctl_src *s); void ctl_fn_src_playing(struct ctl_src *s); void ctl_fn_src_connected(struct ctl_src *s); void ctl_fn_src_disconnected(struct ctl_src *s); diff --git a/src/ctl/srcctl.c b/src/ctl/srcctl.c index bfa05d7..1d6b0d1 100644 --- a/src/ctl/srcctl.c +++ b/src/ctl/srcctl.c @@ -36,12 +36,16 @@ #include #include #include +#include #include "ctl.h" #include "ctl-src.h" #include "wfd.h" #include "shl_macro.h" #include "shl_util.h" #include "config.h" +#include "sender-iface.h" + +void launch_sender(struct ctl_src *s); static sd_bus *bus; static struct ctl_wifi *wifi; @@ -57,19 +61,20 @@ static struct ctl_link *running_link; static struct ctl_peer *running_peer; static struct ctl_peer *pending_peer; -void launch_sender(struct ctl_src *s); // //char *gst_scale_res; -int gst_audio_en = 1; -static const int DEFAULT_RSTP_PORT = 1991; +static int gst_audio_en = 1; +static const int DEFAULT_RTSP_PORT = 1991; //bool uibc; -int rstp_port; +static int rtsp_port; //int uibc_port; // //unsigned int wfd_supported_res_cea = 0x0000001f; /* up to 720x576 */ //unsigned int wfd_supported_res_vesa = 0x00000003; /* up to 800x600 */ //unsigned int wfd_supported_res_hh = 0x00000000; /* not supported */ +static Sender *sender; + /* * cmd: select */ @@ -504,14 +509,14 @@ void launch_sender(struct ctl_src *s) { int i = 0; argv[i++] = "miracle-sender"; - //if (gst_audio_en) { - // argv[i++] = "--acodec"; - // argv[i++] = "aac"; - //} + if (gst_audio_en) { + argv[i++] = "--acodec"; + argv[i++] = "aac"; + } argv[i++] = "--host"; argv[i++] = inet_ntoa(((struct sockaddr_in *) &s->addr)->sin_addr); argv[i++] = "-p"; - sprintf(port, "%d", rstp_port); + sprintf(port, "%d", rtsp_port); argv[i++] = port; // if (s->hres && s->vres) { @@ -569,15 +574,80 @@ void ctl_fn_src_disconnected(struct ctl_src *s) } else { cli_notice("SRC disconnected"); src_connected = false; + + if(sender) { + GError *error = NULL; + if(sender_call_stop_sync(sender, NULL, &error)) { + cli_error("SRC failed to stop sender: %s", error->message); + g_error_free(error); + } + + g_object_unref(sender); + sender = NULL; + } } } +void ctl_fn_src_setup(struct ctl_src *s) +{ + GError *error = NULL; + + cli_printf("SRC got setup request\n"); + + if(!sender) { + sender = sender_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.miracle.Sender", + "/org/freedesktop/miracle/Sender/0", + NULL, + &error); + if(!sender) { + cli_error("SRC failed to connect to sender: %s", error->message); + g_error_free(error); + return; + } + } + + sender_call_prepare_sync(sender, + inet_ntoa(((struct sockaddr_in *) &s->addr)->sin_addr), + s->sink.rtp_ports.port0, + ":0", + 1920, + 1080, + 30, + FALSE, + NULL, + &error); + if(error) { + cli_error("SRC failed to setup: %s", error->message); + g_error_free(error); + return; + } + + g_info("SRC sender prepared"); +} + void ctl_fn_src_playing(struct ctl_src *s) { + GError *error = NULL; + cli_printf("SRC got play request\n"); // TODO src_connected must be true, why if() failed? //if (src_connected) - spawn_gst(s); + //spawn_gst(s); + + if(!sender) { + cli_error("SRC not setup yet"); + return; + } + + if(!sender_call_play_sync(sender, NULL, &error)) { + cli_error("SRC failed to play: %s", error->message); + g_error_free(error); + return; + } + + g_info("SRC sender playing"); } void ctl_fn_peer_new(struct ctl_peer *p) @@ -834,7 +904,7 @@ static int parse_argv(int argc, char *argv[]) }; int c; - rstp_port = DEFAULT_RSTP_PORT; + rtsp_port = DEFAULT_RTSP_PORT; while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { switch (c) { @@ -863,6 +933,7 @@ static int parse_argv(int argc, char *argv[]) int main(int argc, char **argv) { int r; + GError *error = NULL; setlocale(LC_ALL, ""); diff --git a/src/stream/CMakeLists.txt b/src/stream/CMakeLists.txt index 3f2fdd8..d87e785 100644 --- a/src/stream/CMakeLists.txt +++ b/src/stream/CMakeLists.txt @@ -1,19 +1,42 @@ -pkg_check_modules(GTK REQUIRED gtk+-3.0) +pkg_check_modules(GDK REQUIRED gdk-3.0>=3.20) +pkg_check_modules(GIO REQUIRED gio-2.0>=2.30) pkg_check_modules(GST REQUIRED gstreamer-1.0) pkg_check_modules(GST_PBUTILS REQUIRED gstreamer-pbutils-1.0) +find_program(GDBUS_CODEGEN_EXEC NAMES gdbus-codegen) + +if(NOT GDBUS_CODEGEN_EXEC) + message(ERROR gdbus-codegen not found) +endif() include_directories( - ${GTK_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} + ${GDK_INCLUDE_DIRS} ${GST_INCLUDE_DIRS} ${GST_PBUTILS_INCLUDE_DIRS} + ${GIO_INCLUDE_DIRS} ) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/sender-iface.c + ${CMAKE_CURRENT_BINARY_DIR}/sender-iface.h + COMMAND ${GDBUS_CODEGEN_EXEC} + ARGS --generate-c-code sender-iface + --annotate org.freedesktop.miracle.Sender org.gtk.GDBus.C.Name Sender + --interface-prefix org.freedesktop.Miracle + ${CMAKE_CURRENT_SOURCE_DIR}/sender-iface.xml + DEPENDS sender-iface.xml + COMMENT "generating sender interface" +) + +add_definitions(-DG_LOG_USE_STRUCTURED) + +add_library(sender-iface STATIC ${CMAKE_CURRENT_BINARY_DIR}/sender-iface.c) + add_executable(miracle-sender sender) -target_link_libraries( - miracle-sender - ${GTK_LIBRARIES} - ${GST_LIBRARIES} - ${GST_PBUTILS_LIBRARIES}) +target_link_libraries(miracle-sender sender-iface + ${GDK_LIBRARIES} + ${GIO_LIBRARIES} + ${GST_LIBRARIES} + ${GST_PBUTILS_LIBRARIES}) install(TARGETS miracle-sender DESTINATION bin) diff --git a/src/stream/sender-iface.xml b/src/stream/sender-iface.xml new file mode 100644 index 0000000..c4e14ec --- /dev/null +++ b/src/stream/sender-iface.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/stream/sender.c b/src/stream/sender.c index ac29af5..08a865c 100644 --- a/src/stream/sender.c +++ b/src/stream/sender.c @@ -16,6 +16,7 @@ */ #include #include +#include #include #include #include @@ -25,68 +26,24 @@ #include #include #include +#include "sender-iface.h" +#include "sender.h" -struct CmdChannel +struct SenderImpl { - gint in; - gint out; + Sender *skeleton; - guint in_source; - - gboolean controllee; -}; - -static gboolean on_req_in(gint fd, GIOCondition cond, struct CmdChannel *self) -{ - -} - -static struct CmdChannel * cmd_channel_new() -{ - int fds[2]; - GError *error = NULL; - struct CmdChannel *self = g_slice_new(struct CmdChannel); - if(!self) { - goto end; - } - - if(!g_unix_open_pipe(fds, 0, &error)) { - g_warning("%s", error->message); - goto free_self; - } - - self->in = fds[0]; - self->out = fds[1]; - - self->in_source = g_unix_fd_add(self->in, - G_IO_IN | G_IO_ERR | G_IO_HUP, - (GUnixFDSourceFunc) on_req_in, - self); - - goto end; - -free_self: - g_slice_free(struct CmdChannel, self); - self = NULL; -end: - return self; -} - -static void cmd_channel_free(struct CmdChannel *self) -{ - g_source_remove(self->in_source); - g_close(self->in, NULL); - g_close(self->out, NULL); - g_slice_free(struct CmdChannel, self); -} - -struct Sender -{ GMainLoop *loop; GstElement *pipeline; struct CmdChannel *channel; + + guint bus_owner_id; + + guint bus_obj_id; + + GDBusMethodInvocation *method_invoke; }; static gchar *arg_host = NULL; @@ -101,18 +58,78 @@ static gint arg_screen = -1; static gchar *arg_acodec = NULL; -static struct Sender *sender_new() -{ - struct Sender *self = g_slice_new(struct Sender); - if(!self) { - goto end; - } +static gboolean arg_audio_only = FALSE; + +static guint arg_refresh_rate = 30; + +static gboolean arg_interleave = FALSE; + +static char * arg_h264_profile = NULL; + +static const char *vpipeline_desc = + "ximagesrc name=vsrc use-damage=false show-pointer=false do-timestamp=true starty=%d startx=%d endy=%d endx=%d " + "capsfilter name=caps_framerate caps=\"video/x-raw, framerate=%d/1\" " + "videoscale name=vscale " + "capsfilter name=caps_scale caps=\"video/x-raw, width=%d, height=%d\" " + "autovideoconvert name=vconv " + "capsfilter name=caps_format caps=\"video/x-raw, format=I420\" " + "encodebin name=vencoder " + "queue name=vqueue max-size-buffers=0 max-size-bytes=0 " + "mpegtsmux name=muxer alignment=7 " + "capsfilter name=caps_muxer caps=\"video/mpegts, packetsize=188, systemstream=true\" " + "rtpmp2tpay name=rtppay " + "udpsink name=sink host=\"%s\" port=%d "; + +static const char *apipeline_desc = + //"pulsesrc name=asrc device=\"%s\" " + "audiotestsrc name=asrc " + "audioconvert name=aconv " + "audioresample name=aresample " + "encodebin name=aencoder " + "queue name=aqueue "; + +static gboolean sender_impl_prepare(struct SenderImpl *self, + GDBusMethodInvocation *invocation, + const gchar *host, + guint16 port, + const gchar *display, + guint16 width, + guint16 height, + guint16 refresh_rate, + gboolean interleave); + +static gboolean sender_impl_play(struct SenderImpl *sender, GDBusMethodInvocation *invocation); + +static gboolean sender_impl_pause(struct SenderImpl *sender, GDBusMethodInvocation *invocation); + +static gboolean sender_impl_stop(struct SenderImpl *sender, GDBusMethodInvocation *invocation); + +#define SENDER_IMPL_ERROR_DOMAIN_NAME "miracle-sender-impl" + +static const GDBusErrorEntry sender_dbus_error_entries[] = { + { MIRACLE_SENDER_ERROR_UNKNOWN, "org.freedesktop.miracle.Sender.Error.Unknown" }, + { MIRACLE_SENDER_ERROR_NOT_PREPARED, "org.freedesktop.miracle.Sender.Error.NoPrepared" }, +}; + +static GQuark sender_impl_error_quark() +{ + static gsize registered = 0; + g_dbus_error_register_error_domain("miracle-sender", + ®istered, + sender_dbus_error_entries, + G_N_ELEMENTS(sender_dbus_error_entries)); + + return g_quark_from_static_string("miracle-sender-impl"); +} + +static struct SenderImpl * sender_impl_new() +{ + struct SenderImpl *self = g_slice_new0(struct SenderImpl); -end: return self; } -static void sender_free(struct Sender *self) +static void sender_impl_free(struct SenderImpl *self) { if(self->pipeline) { gst_element_set_state(self->pipeline, GST_STATE_NULL); @@ -123,124 +140,461 @@ static void sender_free(struct Sender *self) g_main_loop_unref(self->loop); } - g_slice_free(struct Sender, self); + if(self->bus_owner_id) { + g_bus_unown_name(self->bus_owner_id); + } + + g_slice_free(struct SenderImpl, self); } -static int sender_prepare(struct Sender *self) +static void get_screen_dimension(gint *top, + gint *left, + gint *bottom, + gint *right) { - GError *error = NULL; - GString *desc = g_string_new("ximagesrc "); - GstElement *encoder; - GstElement *vconv; - gint screen_no; + GdkRectangle rect; - if(arg_screen == -1) { - GdkScreen *screen = gdk_screen_get_default(); - screen_no = gdk_screen_get_number(screen); +#if GDK_VERSION_MIN_REQUIRED > GDK_VERSION_3_20 + GdkDisplay *display = gdk_display_get_default(); + GdkMonitor *monitor; + + if(arg_screen < 0 || arg_screen >= gdk_display_get_n_monitors(display)) { + monitor = gdk_display_get_primary_monitor(display); } else { - screen_no = arg_screen; + monitor = gdk_display_get_monitor(display, arg_screen); } - g_string_append_printf(desc, - "screen-num=%d ! video/x-raw, framerate=30/1 ", - screen_no); + gdk_monitor_get_geometry(monitor, &rect); +#else + GdkScreen *screen = gdk_screen_get_default(); + gint monitor; + if(arg_screen < 0 || arg_screen >= gdk_screen_get_n_monitors(screen)) { + monitor = gdk_screen_get_primary_monitor(screen); + } + else { + monitor = arg_screen; + } + gdk_screen_get_monitor_geometry(screen, monitor, &rect); +#endif - if(arg_width || arg_height) { - g_string_append(desc, "! videoscale ! video/x-raw, "); + *top = rect.y; + *left = rect.x; + *bottom = rect.y + rect.height - 1; + *right = rect.x + rect.width - 1; +} + +static int link_elements(GstBin *bin, const char *name, ...) +{ + va_list argv; + const char *name1, *name2; + + va_start(argv, name); + name1 = name; + while((name2 = va_arg(argv, const char *))) { + GstElement *e1 = gst_bin_get_by_name(bin, name1); + GstElement *e2 = gst_bin_get_by_name(bin, name2); + gboolean linked = gst_element_link(e1, e2); + g_object_unref(G_OBJECT(e2)); + g_object_unref(G_OBJECT(e1)); + + if(!linked) { + g_warning("failed to link %s to %s", name1, name2); + errno = EINVAL; + return -1; + } + + name1 = name2; } - if(arg_width) { - g_string_append_printf(desc, "width=%d ", arg_width); - } - if(arg_height) { - g_string_append_printf(desc, "height=%d ", arg_height); + va_end(argv); + + return 0; +} + +void on_gst_message_state_changed(struct SenderImpl *self, + GstMessage *message) +{ + GstState old, curr, pending; + gst_message_parse_state_changed(message, &old, &curr, &pending); + const char *src = GST_MESSAGE_SRC_NAME(message); + if(strncmp("pipeline", src, 8) || !self->skeleton) { + return; } + g_info("%s(%s) state changed: %s => %s", + GST_MESSAGE_SRC_NAME(message), + g_type_name(G_OBJECT_TYPE(GST_MESSAGE_SRC(message))), + gst_element_state_get_name(old), + gst_element_state_get_name(curr)); + + switch(curr) { + case GST_STATE_PLAYING: + g_object_set(self->skeleton, "state", "playing", NULL); + if(self->method_invoke) { + sender_complete_play(SENDER(self->skeleton), + self->method_invoke); + self->method_invoke = NULL; + } + break; + case GST_STATE_PAUSED: + g_object_set(self->skeleton, "state", "paused", NULL); + if(self->method_invoke) { + sender_complete_pause(SENDER(self->skeleton), + self->method_invoke); + self->method_invoke = NULL; + } + break; + default: + break; + } +} + +void on_gst_message_error(struct SenderImpl *self, + GstMessage *message) +{ + GError *error = NULL; + char *dbg = NULL; + gst_message_parse_error(message, &error, &dbg); + if(self->method_invoke) { + g_dbus_method_invocation_return_gerror(self->method_invoke, error); + self->method_invoke = NULL; + } + g_warning("%s", dbg); + g_error_free(error); + g_free(dbg); +} + +void on_gst_message(struct SenderImpl *self, + GstMessage *message, + GstBus *bus) +{ + g_debug("Gstreamer message: %s", GST_MESSAGE_TYPE_NAME(message)); + switch(GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_ERROR: + on_gst_message_error(self, message); + break; + case GST_MESSAGE_STATE_CHANGED: + on_gst_message_state_changed(self, message); + break; + case GST_MESSAGE_LATENCY: { + GstClockTime latency = gst_pipeline_get_latency(GST_PIPELINE(self->pipeline)); + g_info("New latency is: %lu", latency); + } + break; + default: + break; + } +} + +static int prepare_pipeline(struct SenderImpl *self) +{ + GError *error = NULL; + gint result; + gint screen_top, screen_left, screen_bottom, screen_right; + GString * desc; + + desc = g_string_new(NULL); + if(!desc) { + result = -1; + goto end; + } + + get_screen_dimension(&screen_top, &screen_left, &screen_bottom, &screen_right); g_string_append_printf(desc, - "! videoconvert name=vconv " - "! video/x-raw, format=NV12 " - "! encodebin name=encoder " - "! rtpmp2tpay " - "! udpsink host=\"%s\" port=%d ", - arg_host, - arg_port); + vpipeline_desc, + screen_top, + screen_left, + screen_bottom, + screen_right, + arg_refresh_rate, + arg_width ? arg_width : screen_right - screen_left + 1, + arg_height ? arg_height : screen_bottom - screen_top + 1, + arg_host, + arg_port); if(arg_acodec) { - g_string_append_printf(desc, "pulsesrc device=\"%s\" ! encoder.", - "alsa_output.pci-0000_00_1b.0.analog-stereo"); + g_string_append_printf(desc, + apipeline_desc, + "alsa_output.pci-0000_00_1b.0.analog-stereo.monitor"); } - g_info("final pipeline: %s", desc->str); - self->pipeline = gst_parse_launch(desc->str, &error); - if(GST_PARSE_ERROR_LINK != error->code) { + if(error) { g_error("%s", error->message); + goto free_desc; + } + + gst_element_set_name(GST_ELEMENT(self->pipeline), "pipeline"); + + GstEncodingVideoProfile *vencode_profile = gst_encoding_video_profile_new( + gst_caps_from_string("video/x-h264, profile=high"), + NULL, + gst_caps_new_any(), + 0); + GstElement *vencoder = gst_bin_get_by_name(GST_BIN(self->pipeline), "vencoder"); + g_object_set(G_OBJECT(vencoder), "tune", 0x4, NULL); + g_object_set(G_OBJECT(vencoder), "profile", vencode_profile, NULL); + g_object_unref(G_OBJECT(vencoder)); + + if(arg_acodec) { + const char *format; + if(!strncmp("aac", arg_acodec, 3)) { + format = "audio/mpeg, framed=true, mpegversion=4, stream-format=adts"; + } + else if(!strncmp("ac3", arg_acodec, 3)) { + format = "audio/x-ac3, framed=true"; + } + else if(!strncmp("pcm", arg_acodec, 3)) { + format = "audio/x-lpcm"; + } + GstEncodingAudioProfile *aencode_profile = gst_encoding_audio_profile_new( + gst_caps_from_string(format), + NULL, + gst_caps_new_any(), + 0); + GstElement *aencoder = gst_bin_get_by_name(GST_BIN(self->pipeline), "aencoder"); + g_object_set(G_OBJECT(aencoder), "profile", aencode_profile, NULL); + g_object_unref(G_OBJECT(aencoder)); + } + + result = link_elements(GST_BIN(self->pipeline), + "vsrc", + "caps_framerate", + "vscale", + "caps_scale", + "vconv", + "caps_format", + "vencoder", + "vqueue", + "muxer", + "caps_muxer", + "rtppay", + "sink", + NULL); + if(result < 0) { goto error; } - GstCaps *caps = gst_caps_from_string("video/mpegts, systemstream=true, packetsize=188"); - GstEncodingContainerProfile *cprofile = gst_encoding_container_profile_new("mpeg-ts-profile", - NULL, - caps, - NULL); - gst_caps_unref(caps); - - caps = gst_caps_from_string("video/x-h264, format=YV12, width=1280, height=720"); - GstEncodingVideoProfile *vprofile = gst_encoding_video_profile_new(caps, NULL, NULL, 0); - gst_encoding_container_profile_add_profile(cprofile, GST_ENCODING_PROFILE(vprofile)); - //gst_caps_unref(caps); - /*g_object_unref(vprofile);*/ - if(arg_acodec) { - if(!strcmp("aac", arg_acodec)) { - caps = gst_caps_from_string("audio/aac"); - } - GstEncodingAudioProfile *aprofile = gst_encoding_audio_profile_new(caps, NULL, NULL, 0); - //gst_caps_unref(caps); - - gst_encoding_container_profile_add_profile(cprofile, GST_ENCODING_PROFILE(aprofile)); - /*g_object_unref(aprofile);*/ + link_elements(GST_BIN(self->pipeline), + "asrc", + "aconv", + "aresample", + "aencoder", + "aqueue", + "muxer", + NULL); } - encoder = gst_bin_get_by_name(GST_BIN(self->pipeline), "encoder"); - g_object_set(G_OBJECT(encoder), "profile", cprofile, NULL); - /*g_object_unref(cprofile);*/ + GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(self->pipeline)); + g_signal_connect_swapped(bus, "message", G_CALLBACK(on_gst_message), self); + gst_bus_add_signal_watch(bus); - vconv = gst_bin_get_by_name(GST_BIN(self->pipeline), "vconv"); - if(!gst_element_link(vconv, encoder)) { - printf("failed to link vconv to encoder\n"); - } + gst_element_set_state(self->pipeline, GST_STATE_PAUSED); - self->loop = g_main_loop_new(NULL, FALSE); - - goto end; + goto free_desc; error: if(self->pipeline) { g_object_unref(self->pipeline); self->pipeline = NULL; } -end: +free_desc: g_string_free(desc, TRUE); - - return 0; +end: + return result; } -static void sender_start(struct Sender *self) +static void sender_on_name_acquired(GDBusConnection *conn, + const char *name, + gpointer user_data) { + struct SenderImpl *self = user_data; + GError *error = NULL; + gboolean result; + + self->skeleton = sender_skeleton_new(); + g_signal_connect_swapped(self->skeleton, + "handle-prepare", + G_CALLBACK(sender_impl_prepare), + self); + g_signal_connect_swapped(self->skeleton, + "handle-play", + G_CALLBACK(sender_impl_play), + self); + g_signal_connect_swapped(self->skeleton, + "handle-pause", + G_CALLBACK(sender_impl_pause), + self); + g_signal_connect_swapped(self->skeleton, + "handle-stop", + G_CALLBACK(sender_impl_stop), + self); + g_object_set(self->skeleton, "state", "stop", NULL); + + result = g_dbus_interface_skeleton_export( + G_DBUS_INTERFACE_SKELETON(self->skeleton), + conn, + "/org/freedesktop/miracle/Sender/0", + &error); + if(!result) { + g_error("failed to expose object"); + } +} + +static void sender_on_name_lost(GDBusConnection *conn, + const char *name, + gpointer user_data) +{ + struct SenderImpl *self = user_data; + if(self->skeleton) { + g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(self->skeleton)); + g_signal_handlers_disconnect_by_data(self->skeleton, self); + g_object_unref(self->skeleton); + self->skeleton = NULL; + } +} + +static gint sender_impl_init(struct SenderImpl *self) +{ + gint result; + self->loop = g_main_loop_new(NULL, FALSE); + if(!self->loop) { + result = -1; + goto end; + } + + self->bus_owner_id = g_bus_own_name(G_BUS_TYPE_SESSION, + "org.freedesktop.miracle.Sender", + G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, + sender_on_name_acquired, + NULL, + self, + NULL); + +end: + return result; +} + +static gboolean sender_impl_prepare(struct SenderImpl *self, + GDBusMethodInvocation *invocation, + const gchar *host, + guint16 port, + const gchar *display, + guint16 width, + guint16 height, + guint16 refresh_rate, + gboolean interleave) +{ + if(self->pipeline) { + sender_complete_prepare(SENDER(self->skeleton), invocation); + return TRUE; + } + + if(self->method_invoke) { + g_dbus_method_invocation_return_error(invocation, + MIRACLE_SENDER_ERROR, + MIRACLE_SENDER_ERROR_AGAIN, + "request handling in progress"); + return TRUE; + } + + self->method_invoke = invocation; + + if(arg_host) { + g_free(arg_host); + } + arg_host = g_strdup(host); + arg_port = port; + g_setenv("DISPLAY", display ? display : ":0", TRUE); + arg_width = width; + arg_height = height; + + prepare_pipeline(self); + + return TRUE; +} + +static gboolean sender_impl_play(struct SenderImpl *self, + GDBusMethodInvocation *invocation) +{ + if(!self->pipeline) { + g_dbus_method_invocation_return_error(invocation, + MIRACLE_SENDER_ERROR, + MIRACLE_SENDER_ERROR_NOT_PREPARED, + "sender not prepared"); + return TRUE; + } + + if(self->method_invoke) { + g_dbus_method_invocation_return_error(invocation, + MIRACLE_SENDER_ERROR, + MIRACLE_SENDER_ERROR_AGAIN, + "request handling in progress"); + return TRUE; + } + + self->method_invoke = invocation; + gst_element_set_state(self->pipeline, GST_STATE_PLAYING); + + return TRUE; } -static void sender_pause(struct Sender *self) +static gboolean sender_impl_pause(struct SenderImpl *self, + GDBusMethodInvocation *invocation) { + if(!self->pipeline) { + g_dbus_method_invocation_return_error(invocation, + MIRACLE_SENDER_ERROR, + MIRACLE_SENDER_ERROR_NOT_PREPARED, + "sender not prepared"); + return TRUE; + } + + if(self->method_invoke) { + g_dbus_method_invocation_return_error(invocation, + MIRACLE_SENDER_ERROR, + MIRACLE_SENDER_ERROR_AGAIN, + "request handling in progress"); + return TRUE; + } + + self->method_invoke = invocation; + gst_element_set_state(self->pipeline, GST_STATE_PAUSED); + + return TRUE; } -static void sender_stop(struct Sender *self) +static gboolean sender_impl_stop(struct SenderImpl *self, + GDBusMethodInvocation *invocation) { + if(!self->pipeline) { + goto end; + } + + if(self->method_invoke) { + g_dbus_method_invocation_return_error(invocation, + MIRACLE_SENDER_ERROR, + MIRACLE_SENDER_ERROR_AGAIN, + "request handling in progress"); + return TRUE; + } + + g_object_set(self->skeleton, "state", "stop", NULL); + gst_element_set_state(self->pipeline, GST_STATE_NULL); + g_object_unref(self->pipeline); + self->pipeline = NULL; + +end: + sender_complete_stop(SENDER(self->skeleton), invocation); + + return TRUE; } -static void sender_run(struct Sender *self) +static gboolean sender_impl_run(struct SenderImpl *self) { g_main_loop_run(self->loop); } @@ -253,7 +607,9 @@ static GOptionEntry entries[] = { { "width", 'w', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &arg_width, "", "" }, { "height", 'h', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &arg_height, "", "" }, { "screen-num", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &arg_screen, "screen number to cast to", "" }, - { "acodec", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &arg_acodec, "codec to encode audio", "" }, + { "acodec", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &arg_acodec, "codec to encode audio", "" }, + { "audio-only", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &arg_audio_only, "no video, audio stream only", "" }, + { 0 } }; static void arg_parse(int *argc, char ***args) @@ -280,30 +636,55 @@ static void arg_parse(int *argc, char ***args) g_option_context_free(opt_context); } +static void gst_rerank(const char *name, ...) +{ + va_list names; + GstRegistry *reg = gst_registry_get(); + + va_start(names, name); + while(name) { + GstPluginFeature *plugin = gst_registry_lookup_feature(reg, name); + if(!plugin) { + goto next; + } + + g_info("raising rank of plugin %s from %u to %u", + name, + gst_plugin_feature_get_rank(plugin), + GST_RANK_PRIMARY + 1); + gst_plugin_feature_set_rank(plugin, GST_RANK_PRIMARY + 1); + gst_object_unref(plugin); + +next: + name = va_arg(names, const char *); + } + + va_end(names); +} + int main(int argc, char *args[]) { - struct Sender *sender; + struct SenderImpl *sender; arg_parse(&argc, &args); gdk_init(&argc, &args); gst_init(&argc, &args); gst_pb_utils_init(); + //gst_rerank("vaapih264enc", "vaapienc_h264", "glcolorconvert", NULL); - sender = sender_new(); + sender = sender_impl_new(); if(!sender) { g_error("%s", strerror(errno)); } - if(sender_prepare(sender) < 0) { + if(!sender_impl_init(sender)) { g_error("%s", strerror(errno)); } - sender_start(sender); + sender_impl_run(sender); - sender_run(sender); - - sender_free(sender); + g_object_unref(sender); return 0; } diff --git a/src/stream/sender.h b/src/stream/sender.h new file mode 100644 index 0000000..4269ef3 --- /dev/null +++ b/src/stream/sender.h @@ -0,0 +1,25 @@ +/* + * ===================================================================================== + * + * Filename: sender.h + * + * Description: + * + * Version: 1.0 + * Created: 2016年11月25日 10時42分20秒 + * Revision: none + * Compiler: gcc + * + * Author: YOUR NAME (), + * Organization: + * + * ===================================================================================== + */ + +#define MIRACLE_SENDER_ERROR sender_impl_error_quark() + +enum { + MIRACLE_SENDER_ERROR_UNKNOWN, + MIRACLE_SENDER_ERROR_NOT_PREPARED, + MIRACLE_SENDER_ERROR_AGAIN, +}; diff --git a/src/stream/sender.py b/src/stream/sender.py new file mode 100755 index 0000000..fa0252e --- /dev/null +++ b/src/stream/sender.py @@ -0,0 +1,66 @@ +#!/usr/bin/python3 + +import gi +gi.require_version('Gst', '1.0') +gi.require_version('GstBase', '1.0') +gi.require_version('GstPbutils', '1.0') +from gi.repository import GLib +from gi.repository import Gst, GstBase, GstPbutils + +Gst.init(None) +GstPbutils.pb_utils_init() + +pipeline = Gst.ElementFactory.make('pipeline') + +ximagesrc = Gst.ElementFactory.make('ximagesrc') +ximagesrc.set_property('use-damage', False) +ximagesrc.set_property('show-pointer', False) +ximagesrc.set_property('do-timestamp', True) +ximagesrc.set_property('startx', 0) +ximagesrc.set_property('starty', 0) +ximagesrc.set_property('endx', 1919) +ximagesrc.set_property('endy', 1079) +pipeline.add(ximagesrc) + +vscale = Gst.ElementFactory.make('videoscale') +pipeline.add(vscale) +ximagesrc.link(vscale) + +vconvert = Gst.ElementFactory.make('videoconvert') +pipeline.add(vconvert) +vscale.link(vconvert) + +filter = Gst.ElementFactory.make('capsfilter') +filter.set_property('caps', Gst.Caps.from_string('video/x-raw, format=I420, framerate=30/1, width=1920, height=1080')) +pipeline.add(filter) +vconvert.link(filter) + +encoder = Gst.ElementFactory.make('encodebin') +#cont_profile = GstPbutils.EncodingContainerProfile.new('mpegts', +# None, +# Gst.Caps.from_string('video/mpegts, packetsize=188, systemstream=true'), +# None) +vencode_profile = GstPbutils.EncodingVideoProfile.new(Gst.Caps.from_string('video/x-h264'), + None, + Gst.Caps.new_any(), + 1) +#cont_profile.add_profile(vencode_profile) +encoder.set_property('profile', vencode_profile) +pipeline.add(encoder) +filter.link(encoder) + +mpegtsmux = Gst.ElementFactory.make('mpegtsmux') +mpegtsmux.set_property('alignment', 7) +pipeline.add(mpegtsmux) +encoder.link(mpegtsmux) + +sink = Gst.ElementFactory.make('filesink') +sink.set_property('location', 'xxx') +pipeline.add(sink) +mpegtsmux.link(sink) + +pipeline.set_state(Gst.State.PLAYING) + +loop = GLib.MainLoop() +loop.run() +