mirror of
https://github.com/albfan/miraclecast.git
synced 2025-02-13 10:22:04 +00:00
source implement + sender talk via dbus
This commit is contained in:
parent
fb37a53dec
commit
b3b51120e0
11 changed files with 797 additions and 176 deletions
|
@ -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
|
||||
)
|
||||
|
|
3
res/org.freedesktop.miracle.Sender.service.cmake
Normal file
3
res/org.freedesktop.miracle.Sender.service.cmake
Normal file
|
@ -0,0 +1,3 @@
|
|||
[D-BUS Service]
|
||||
Name=org.freedesktop.miracle.Sender
|
||||
Exec=@CMAKE_INSTALL_PREFIX@/bin/miracle-sender
|
|
@ -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 ###############
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -36,12 +36,16 @@
|
|||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <gio/gio.h>
|
||||
#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, "");
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
20
src/stream/sender-iface.xml
Normal file
20
src/stream/sender-iface.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.miracle.Sender">
|
||||
<method name="Prepare">
|
||||
<arg name="host" type="s" direction="in"/>
|
||||
<arg name="port" type="q" direction="in"/>
|
||||
<arg name="display" type="s" direction="in"/>
|
||||
<arg name="width" type="q" direction="in"/>
|
||||
<arg name="height" type="q" direction="in"/>
|
||||
<arg name="refresh_rate" type="q" direction="in"/>
|
||||
<arg name="interleave" type="b" direction="in"/>
|
||||
</method>
|
||||
<method name="Play">
|
||||
</method>
|
||||
<method name="Pause">
|
||||
</method>
|
||||
<method name="Stop">
|
||||
</method>
|
||||
<property name="State" type="s" access="read"/>
|
||||
</interface>
|
||||
</node>
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <glib-unix.h>
|
||||
|
@ -25,68 +26,24 @@
|
|||
#include <gst/gst.h>
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
#include <gst/pbutils/encoding-profile.h>
|
||||
#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;
|
||||
}
|
||||
|
|
25
src/stream/sender.h
Normal file
25
src/stream/sender.h
Normal file
|
@ -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,
|
||||
};
|
66
src/stream/sender.py
Executable file
66
src/stream/sender.py
Executable file
|
@ -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()
|
||||
|
Loading…
Reference in a new issue