mirror of
https://github.com/albfan/miraclecast.git
synced 2025-02-13 10:51:54 +00:00
remove unused files
This commit is contained in:
parent
9dd805378e
commit
11016d9e03
9 changed files with 0 additions and 2665 deletions
|
@ -5,7 +5,6 @@ add_subdirectory(wifi)
|
||||||
add_subdirectory(dhcp)
|
add_subdirectory(dhcp)
|
||||||
add_subdirectory(ctl)
|
add_subdirectory(ctl)
|
||||||
add_subdirectory(uibc)
|
add_subdirectory(uibc)
|
||||||
add_subdirectory(stream)
|
|
||||||
|
|
||||||
set(miracled_SRCS miracled.h miracled.c)
|
set(miracled_SRCS miracled.h miracled.c)
|
||||||
add_executable(miracled ${miracled_SRCS})
|
add_executable(miracled ${miracled_SRCS})
|
||||||
|
|
|
@ -48,7 +48,6 @@ include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_D
|
||||||
########### next target ###############
|
########### next target ###############
|
||||||
set(miracle-wfdctl_SRCS ctl-cli.c
|
set(miracle-wfdctl_SRCS ctl-cli.c
|
||||||
ctl-wifi.c
|
ctl-wifi.c
|
||||||
wfd-src.c
|
|
||||||
wfd-sink.c
|
wfd-sink.c
|
||||||
wfd-dbus.c
|
wfd-dbus.c
|
||||||
wfd-session.c
|
wfd-session.c
|
||||||
|
|
873
src/ctl/srcctl.c
873
src/ctl/srcctl.c
|
@ -1,873 +0,0 @@
|
||||||
/*
|
|
||||||
* MiracleCast - Wifi-Display/Miracast Implementation
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
|
|
||||||
*
|
|
||||||
* MiracleCast is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2.1 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* MiracleCast 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
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <locale.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <strings.h>
|
|
||||||
#include <sys/signalfd.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <systemd/sd-bus.h>
|
|
||||||
#include <systemd/sd-event.h>
|
|
||||||
#include <systemd/sd-journal.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#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;
|
|
||||||
static struct ctl_src *src;
|
|
||||||
static sd_event_source *scan_timeout;
|
|
||||||
static sd_event_source *src_timeout;
|
|
||||||
static unsigned int src_timeout_time;
|
|
||||||
static bool src_connected;
|
|
||||||
|
|
||||||
//static char *selected_ link;
|
|
||||||
static struct ctl_link *running_link;
|
|
||||||
static struct ctl_peer *running_peer;
|
|
||||||
static struct ctl_peer *pending_peer;
|
|
||||||
|
|
||||||
//
|
|
||||||
//char *gst_scale_res;
|
|
||||||
static int gst_audio_en = 1;
|
|
||||||
static const int DEFAULT_RTSP_PORT = 1991;
|
|
||||||
//bool uibc;
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int cmd_select(char **args, unsigned int n)
|
|
||||||
{
|
|
||||||
struct ctl_link *l;
|
|
||||||
|
|
||||||
if (!n) {
|
|
||||||
if (running_link) {
|
|
||||||
cli_printf("link %s deselected\n",
|
|
||||||
running_link->label);
|
|
||||||
running_link = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
l = ctl_wifi_search_link(wifi, args[0]);
|
|
||||||
if (!l) {
|
|
||||||
cli_error("unknown link %s", args[0]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
running_link = l;
|
|
||||||
cli_printf("link %s selected\n", running_link->label);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* cmd list
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int cmd_list(char **args, unsigned int n)
|
|
||||||
{
|
|
||||||
size_t link_cnt = 0, peer_cnt = 0;
|
|
||||||
struct shl_dlist *i, *j;
|
|
||||||
struct ctl_link *l;
|
|
||||||
struct ctl_peer *p;
|
|
||||||
|
|
||||||
/* list links */
|
|
||||||
|
|
||||||
cli_printf("%6s %-24s %-30s\n",
|
|
||||||
"LINK", "INTERFACE", "FRIENDLY-NAME");
|
|
||||||
|
|
||||||
shl_dlist_for_each(i, &wifi->links) {
|
|
||||||
l = link_from_dlist(i);
|
|
||||||
++link_cnt;
|
|
||||||
|
|
||||||
cli_printf("%6s %-24s %-30s\n",
|
|
||||||
l->label,
|
|
||||||
shl_isempty(l->ifname) ?
|
|
||||||
"<unknown>" : l->ifname,
|
|
||||||
shl_isempty(l->friendly_name) ?
|
|
||||||
"<unknown>" : l->friendly_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_printf("\n");
|
|
||||||
|
|
||||||
/* list peers */
|
|
||||||
|
|
||||||
cli_printf("%6s %-24s %-30s %-10s\n",
|
|
||||||
"LINK", "PEER-ID", "FRIENDLY-NAME", "CONNECTED");
|
|
||||||
|
|
||||||
shl_dlist_for_each(i, &wifi->links) {
|
|
||||||
l = link_from_dlist(i);
|
|
||||||
|
|
||||||
shl_dlist_for_each(j, &l->peers) {
|
|
||||||
p = peer_from_dlist(j);
|
|
||||||
++peer_cnt;
|
|
||||||
|
|
||||||
cli_printf("%6s %-24s %-30s %-10s\n",
|
|
||||||
p->l->label,
|
|
||||||
p->label,
|
|
||||||
shl_isempty(p->friendly_name) ?
|
|
||||||
"<unknown>" : p->friendly_name,
|
|
||||||
p->connected ? "yes" : "no");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_printf("\n %u peers and %u links listed.\n", peer_cnt, link_cnt);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* cmd: show
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int cmd_show(char **args, unsigned int n)
|
|
||||||
{
|
|
||||||
struct ctl_link *l = NULL;
|
|
||||||
struct ctl_peer *p = NULL;
|
|
||||||
|
|
||||||
if (n > 0) {
|
|
||||||
if (!(l = ctl_wifi_find_link(wifi, args[0])) &&
|
|
||||||
!(p = ctl_wifi_find_peer(wifi, args[0])) &&
|
|
||||||
!(l = ctl_wifi_search_link(wifi, args[0])) &&
|
|
||||||
!(p = ctl_wifi_search_peer(wifi, args[0]))) {
|
|
||||||
cli_error("unknown link or peer %s", args[0]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l) {
|
|
||||||
cli_printf("Link=%s\n", l->label);
|
|
||||||
if (l->ifindex > 0)
|
|
||||||
cli_printf("InterfaceIndex=%u\n", l->ifindex);
|
|
||||||
if (l->ifname && *l->ifname)
|
|
||||||
cli_printf("InterfaceName=%s\n", l->ifname);
|
|
||||||
if (l->friendly_name && *l->friendly_name)
|
|
||||||
cli_printf("FriendlyName=%s\n", l->friendly_name);
|
|
||||||
cli_printf("P2PScanning=%d\n", l->p2p_scanning);
|
|
||||||
if (l->wfd_subelements && *l->wfd_subelements)
|
|
||||||
cli_printf("WfdSubelements=%s\n", l->wfd_subelements);
|
|
||||||
} else if (p) {
|
|
||||||
cli_printf("Peer=%s\n", p->label);
|
|
||||||
if (p->p2p_mac && *p->p2p_mac)
|
|
||||||
cli_printf("P2PMac=%s\n", p->p2p_mac);
|
|
||||||
if (p->friendly_name && *p->friendly_name)
|
|
||||||
cli_printf("FriendlyName=%s\n", p->friendly_name);
|
|
||||||
cli_printf("Connected=%d\n", p->connected);
|
|
||||||
if (p->interface && *p->interface)
|
|
||||||
cli_printf("Interface=%s\n", p->interface);
|
|
||||||
if (p->local_address && *p->local_address)
|
|
||||||
cli_printf("LocalAddress=%s\n", p->local_address);
|
|
||||||
if (p->remote_address && *p->remote_address)
|
|
||||||
cli_printf("RemoteAddress=%s\n", p->remote_address);
|
|
||||||
if (p->wfd_subelements && *p->wfd_subelements)
|
|
||||||
cli_printf("WfdSubelements=%s\n", p->wfd_subelements);
|
|
||||||
} else {
|
|
||||||
cli_printf("Show what?\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* cmd: set-friendly-name
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int cmd_set_friendly_name(char **args, unsigned int n)
|
|
||||||
{
|
|
||||||
struct ctl_link *l = NULL;
|
|
||||||
const char *name;
|
|
||||||
|
|
||||||
if (n < 1) {
|
|
||||||
cli_printf("To what?\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n > 1) {
|
|
||||||
l = ctl_wifi_search_link(wifi, args[0]);
|
|
||||||
if (!l) {
|
|
||||||
cli_error("unknown link %s", args[0]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = args[1];
|
|
||||||
} else {
|
|
||||||
name = args[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
l = l ? : running_link;
|
|
||||||
if (!l) {
|
|
||||||
cli_error("no link selected");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctl_link_set_friendly_name(l, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* cmd: p2p-scan
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int cmd_p2p_scan(char **args, unsigned int n)
|
|
||||||
{
|
|
||||||
struct ctl_link *l = NULL;
|
|
||||||
unsigned int i;
|
|
||||||
bool stop = false;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
for (i = 0; i < n; ++i) {
|
|
||||||
if (!strcmp(args[i], "stop")) {
|
|
||||||
stop = true;
|
|
||||||
} else {
|
|
||||||
l = ctl_wifi_search_link(wifi, args[i]);
|
|
||||||
if (!l) {
|
|
||||||
cli_error("unknown link %s", args[i]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
l = l ? : running_link;
|
|
||||||
if (!l) {
|
|
||||||
cli_error("no link selected");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctl_link_set_wfd_subelements(l, "000600101c4400c8");
|
|
||||||
r = ctl_link_set_p2p_scanning(l, !stop);
|
|
||||||
if(!r && !running_link) {
|
|
||||||
running_link = l;
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* cmd: connect
|
|
||||||
*/
|
|
||||||
|
|
||||||
static bool is_valid_prov(const char *prov)
|
|
||||||
{
|
|
||||||
return prov && (!strcmp(prov, "auto") ||
|
|
||||||
!strcmp(prov, "pbc") ||
|
|
||||||
!strcmp(prov, "display") ||
|
|
||||||
!strcmp(prov, "pin"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cmd_connect(char **args, unsigned int n)
|
|
||||||
{
|
|
||||||
struct ctl_peer *p;
|
|
||||||
const char *prov, *pin;
|
|
||||||
|
|
||||||
if (n < 1) {
|
|
||||||
cli_printf("To whom?\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = ctl_wifi_search_peer(wifi, args[0]);
|
|
||||||
if (!p) {
|
|
||||||
cli_error("unknown peer %s", args[0]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n > 2) {
|
|
||||||
prov = args[1];
|
|
||||||
pin = args[2];
|
|
||||||
} else if (n > 1) {
|
|
||||||
if (is_valid_prov(args[1])) {
|
|
||||||
prov = args[1];
|
|
||||||
pin = "";
|
|
||||||
} else {
|
|
||||||
prov = "auto";
|
|
||||||
pin = args[1];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prov = "auto";
|
|
||||||
pin = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctl_peer_connect(p, prov, pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* cmd: disconnect
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int cmd_disconnect(char **args, unsigned int n)
|
|
||||||
{
|
|
||||||
struct ctl_peer *p;
|
|
||||||
|
|
||||||
if (n < 1) {
|
|
||||||
cli_printf("From whom?\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p = ctl_wifi_search_peer(wifi, args[0]);
|
|
||||||
if (!p) {
|
|
||||||
cli_error("unknown peer %s", args[0]);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctl_peer_disconnect(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* cmd: quit/exit
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int cmd_quit(char **args, unsigned int n)
|
|
||||||
{
|
|
||||||
cli_exit();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* main
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void schedule_timeout(sd_event_source **out,
|
|
||||||
uint64_t rel_usec,
|
|
||||||
sd_event_time_handler_t timeout_fn,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
int r;
|
|
||||||
|
|
||||||
rel_usec += shl_now(CLOCK_MONOTONIC);
|
|
||||||
|
|
||||||
if (*out) {
|
|
||||||
r = sd_event_source_set_time(*out, rel_usec);
|
|
||||||
if (r < 0)
|
|
||||||
cli_vERR(r);
|
|
||||||
} else {
|
|
||||||
r = sd_event_add_time(cli_event,
|
|
||||||
out,
|
|
||||||
CLOCK_MONOTONIC,
|
|
||||||
rel_usec,
|
|
||||||
0,
|
|
||||||
timeout_fn,
|
|
||||||
data);
|
|
||||||
if (r < 0)
|
|
||||||
cli_vERR(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void stop_timeout(sd_event_source **out)
|
|
||||||
{
|
|
||||||
if (*out) {
|
|
||||||
sd_event_source_set_enabled(*out, SD_EVENT_OFF);
|
|
||||||
sd_event_source_unref(*out);
|
|
||||||
*out = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int scan_timeout_fn(sd_event_source *s, uint64_t usec, void *data)
|
|
||||||
{
|
|
||||||
stop_timeout(&scan_timeout);
|
|
||||||
|
|
||||||
if (pending_peer) {
|
|
||||||
if (cli_running()) {
|
|
||||||
cli_printf("[" CLI_RED "TIMEOUT" CLI_DEFAULT "] waiting for %s\n",
|
|
||||||
pending_peer->friendly_name);
|
|
||||||
}
|
|
||||||
pending_peer = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (running_link)
|
|
||||||
ctl_link_set_p2p_scanning(running_link, true);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_timeout_fn(sd_event_source *s, uint64_t usec, void *data)
|
|
||||||
{
|
|
||||||
int r;
|
|
||||||
|
|
||||||
stop_timeout(&src_timeout);
|
|
||||||
|
|
||||||
if (running_peer &&
|
|
||||||
running_peer->connected &&
|
|
||||||
ctl_src_is_closed(src)) {
|
|
||||||
r = ctl_src_listen(src, running_peer->local_address);
|
|
||||||
if (r < 0) {
|
|
||||||
if (src_timeout_time++ >= 3)
|
|
||||||
cli_vERR(r);
|
|
||||||
else
|
|
||||||
schedule_timeout(&src_timeout,
|
|
||||||
src_timeout_time * 1000ULL * 1000ULL,
|
|
||||||
src_timeout_fn,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
log_info("listening on %s", running_peer->local_address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct cli_cmd cli_cmds[] = {
|
|
||||||
{ "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" },
|
|
||||||
{ "select", "[link]", CLI_Y, CLI_LESS, 1, cmd_select, "Select default link" },
|
|
||||||
{ "show", "<link|peer>", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" },
|
|
||||||
{ "set-friendly-name", "[link] <name>", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object" },
|
|
||||||
{ "p2p-scan", "[link] [stop]", CLI_Y, CLI_LESS, 2, cmd_p2p_scan, "Control neighborhood P2P scanning" },
|
|
||||||
{ "connect", "<peer> [provision] [pin]", CLI_M, CLI_LESS, 3, cmd_connect, "Connect to peer" },
|
|
||||||
{ "disconnect", "<peer>", CLI_M, CLI_EQUAL, 1, cmd_disconnect, "Disconnect from peer" },
|
|
||||||
{ "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" },
|
|
||||||
{ "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL },
|
|
||||||
{ "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" },
|
|
||||||
{ },
|
|
||||||
};
|
|
||||||
|
|
||||||
static void stop_sender(void)
|
|
||||||
{
|
|
||||||
GError *error = NULL;
|
|
||||||
|
|
||||||
if (!sender)
|
|
||||||
return;
|
|
||||||
|
|
||||||
sender_call_stop_sync(sender, NULL, &error);
|
|
||||||
if(error) {
|
|
||||||
cli_error("SOURCE failed to stop sender: %s", error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_object_unref(sender);
|
|
||||||
sender = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ctl_fn_wfd_connected(struct ctl_src *s)
|
|
||||||
{
|
|
||||||
cli_notice("SOURCE connected");
|
|
||||||
src_connected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ctl_fn_wfd_disconnected(struct ctl_src *s)
|
|
||||||
{
|
|
||||||
if (!src_connected) {
|
|
||||||
/* treat HUP as timeout */
|
|
||||||
src_timeout_fn(src_timeout, 0, NULL);
|
|
||||||
} 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_wfd_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",
|
|
||||||
"/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,
|
|
||||||
25,
|
|
||||||
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_wfd_playing(struct ctl_src *s)
|
|
||||||
{
|
|
||||||
GError *error = NULL;
|
|
||||||
|
|
||||||
cli_printf("SRC got play request\n");
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_printf("SRC sender playing\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ctl_fn_peer_new(struct ctl_peer *p)
|
|
||||||
{
|
|
||||||
if (p->l != running_link || shl_isempty(p->wfd_subelements))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Peer: %s\n",
|
|
||||||
p->label);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ctl_fn_peer_free(struct ctl_peer *p)
|
|
||||||
{
|
|
||||||
if (p->l != running_link || shl_isempty(p->wfd_subelements))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (p == pending_peer) {
|
|
||||||
cli_printf("no longer waiting for peer %s (%s)\n",
|
|
||||||
p->friendly_name, p->label);
|
|
||||||
pending_peer = NULL;
|
|
||||||
stop_timeout(&scan_timeout);
|
|
||||||
ctl_link_set_p2p_scanning(p->l, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p == running_peer) {
|
|
||||||
cli_printf("no longer running on peer %s\n",
|
|
||||||
running_peer->label);
|
|
||||||
stop_timeout(&src_timeout);
|
|
||||||
stop_sender();
|
|
||||||
ctl_src_close(src);
|
|
||||||
running_peer = NULL;
|
|
||||||
stop_timeout(&scan_timeout);
|
|
||||||
ctl_link_set_p2p_scanning(p->l, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_RED "REMOVE" CLI_DEFAULT "] Peer: %s\n",
|
|
||||||
p->label);
|
|
||||||
}
|
|
||||||
//
|
|
||||||
void ctl_fn_peer_provision_discovery(struct ctl_peer *p,
|
|
||||||
const char *prov,
|
|
||||||
const char *pin)
|
|
||||||
{
|
|
||||||
if (p->l != running_link || shl_isempty(p->wfd_subelements))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_YELLOW "PROV" CLI_DEFAULT "] Peer: %s Type: %s PIN: %s\n",
|
|
||||||
p->label, prov, pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ctl_fn_peer_go_neg_request(struct ctl_peer *p,
|
|
||||||
const char *prov,
|
|
||||||
const char *pin)
|
|
||||||
{
|
|
||||||
if (p->l != running_link || shl_isempty(p->wfd_subelements))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_YELLOW "GO NEG" CLI_DEFAULT "] Peer: %s Type: %s PIN: %s\n",
|
|
||||||
p->label, prov, pin);
|
|
||||||
|
|
||||||
if (!running_peer) {
|
|
||||||
/* auto accept any incoming connection attempt */
|
|
||||||
ctl_peer_connect(p, "auto", "");
|
|
||||||
pending_peer = p;
|
|
||||||
|
|
||||||
/* 60s timeout in case the connect fails. Yes, stupid wpas does
|
|
||||||
* not catch this and notify us.. and as it turns out, DHCP
|
|
||||||
* negotiation with some proprietary devices can take up to 30s
|
|
||||||
* so lets be safe. */
|
|
||||||
schedule_timeout(&scan_timeout,
|
|
||||||
60 * 1000ULL * 1000ULL,
|
|
||||||
scan_timeout_fn,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ctl_fn_peer_formation_failure(struct ctl_peer *p, const char *reason)
|
|
||||||
{
|
|
||||||
if (p->l != running_link || shl_isempty(p->wfd_subelements))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_YELLOW "FAIL" CLI_DEFAULT "] Peer: %s Reason: %s\n",
|
|
||||||
p->label, reason);
|
|
||||||
|
|
||||||
if (!running_peer) {
|
|
||||||
stop_timeout(&scan_timeout);
|
|
||||||
ctl_link_set_p2p_scanning(p->l, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ctl_fn_peer_connected(struct ctl_peer *p)
|
|
||||||
{
|
|
||||||
if (p->l != running_link || shl_isempty(p->wfd_subelements))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_GREEN "CONNECT" CLI_DEFAULT "] Peer: %s\n",
|
|
||||||
p->label);
|
|
||||||
|
|
||||||
pending_peer = NULL;
|
|
||||||
|
|
||||||
if (!running_peer) {
|
|
||||||
running_peer = p;
|
|
||||||
cli_printf("now running on peer %s\n", running_peer->label);
|
|
||||||
stop_timeout(&scan_timeout);
|
|
||||||
|
|
||||||
src_connected = false;
|
|
||||||
src_timeout_time = 1;
|
|
||||||
schedule_timeout(&src_timeout,
|
|
||||||
src_timeout_time * 1000ULL * 1000ULL,
|
|
||||||
src_timeout_fn,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//
|
|
||||||
void ctl_fn_peer_disconnected(struct ctl_peer *p)
|
|
||||||
{
|
|
||||||
if (p->l != running_link || shl_isempty(p->wfd_subelements))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (p == running_peer) {
|
|
||||||
cli_printf("no longer running on peer %s\n",
|
|
||||||
running_peer->label);
|
|
||||||
stop_timeout(&src_timeout);
|
|
||||||
stop_sender();
|
|
||||||
ctl_src_close(src);
|
|
||||||
running_peer = NULL;
|
|
||||||
stop_timeout(&scan_timeout);
|
|
||||||
ctl_link_set_p2p_scanning(p->l, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_YELLOW "DISCONNECT" CLI_DEFAULT "] Peer: %s\n",
|
|
||||||
p->label);
|
|
||||||
}
|
|
||||||
//
|
|
||||||
void ctl_fn_link_new(struct ctl_link *l)
|
|
||||||
{
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_GREEN "ADD" CLI_DEFAULT "] Link: %s\n",
|
|
||||||
l->label);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ctl_fn_link_free(struct ctl_link *l)
|
|
||||||
{
|
|
||||||
if (l == running_link) {
|
|
||||||
cli_printf("no longer running on link %s\n",
|
|
||||||
running_link->label);
|
|
||||||
running_link = NULL;
|
|
||||||
stop_timeout(&scan_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cli_running())
|
|
||||||
cli_printf("[" CLI_RED "REMOVE" CLI_DEFAULT "] Link: %s\n",
|
|
||||||
l->label);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cli_fn_help()
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* 80-char barrier:
|
|
||||||
* 01234567890123456789012345678901234567890123456789012345678901234567890123456789
|
|
||||||
*/
|
|
||||||
printf("%s [OPTIONS...] ...\n\n"
|
|
||||||
"Control a dedicated local sink via MiracleCast.\n"
|
|
||||||
" -h --help Show this help\n"
|
|
||||||
" --version Show package version\n"
|
|
||||||
" --log-level <lvl> Maximum level for log messages\n"
|
|
||||||
" --log-journal-level <lvl> Maximum level for journal log messages\n"
|
|
||||||
" --audio <0/1> Enable audio support (default %d)\n"
|
|
||||||
"\n"
|
|
||||||
, program_invocation_short_name, gst_audio_en
|
|
||||||
);
|
|
||||||
/*
|
|
||||||
* 80-char barrier:
|
|
||||||
* 01234567890123456789012345678901234567890123456789012345678901234567890123456789
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ctl_interactive(char **argv, int argc)
|
|
||||||
{
|
|
||||||
int r;
|
|
||||||
|
|
||||||
r = cli_init(bus, cli_cmds);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
r = ctl_src_new(&src, cli_event);
|
|
||||||
if (r < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
r = ctl_wifi_fetch(wifi);
|
|
||||||
if (r < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if (argc > 0) {
|
|
||||||
r = cli_do(cli_cmds, argv, argc);
|
|
||||||
if (r == -EAGAIN)
|
|
||||||
cli_error("unknown operation %s", argv[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
r = cli_run();
|
|
||||||
|
|
||||||
error:
|
|
||||||
stop_sender();
|
|
||||||
ctl_src_free(src);
|
|
||||||
src = NULL;
|
|
||||||
cli_destroy();
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ctl_main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
struct shl_dlist *i;
|
|
||||||
struct ctl_link *l;
|
|
||||||
int r, left;
|
|
||||||
|
|
||||||
r = ctl_wifi_new(&wifi, bus);
|
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
left = argc - optind;
|
|
||||||
r = ctl_interactive(argv + optind, left <= 0 ? 0 : left);
|
|
||||||
|
|
||||||
/* stop all scans */
|
|
||||||
shl_dlist_for_each(i, &wifi->links) {
|
|
||||||
l = link_from_dlist(i);
|
|
||||||
if (l->have_p2p_scan)
|
|
||||||
ctl_link_set_p2p_scanning(l, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctl_wifi_free(wifi);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_argv(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
enum {
|
|
||||||
ARG_VERSION = 0x100,
|
|
||||||
ARG_LOG_LEVEL,
|
|
||||||
ARG_JOURNAL_LEVEL,
|
|
||||||
ARG_AUDIO,
|
|
||||||
};
|
|
||||||
static const struct option options[] = {
|
|
||||||
{ "help", no_argument, NULL, 'h' },
|
|
||||||
{ "version", no_argument, NULL, ARG_VERSION },
|
|
||||||
{ "log-level", required_argument, NULL, ARG_LOG_LEVEL },
|
|
||||||
{ "log-journal-level", required_argument, NULL, ARG_JOURNAL_LEVEL },
|
|
||||||
{ "audio", required_argument, NULL, ARG_AUDIO },
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
int c;
|
|
||||||
|
|
||||||
rtsp_port = DEFAULT_RTSP_PORT;
|
|
||||||
|
|
||||||
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
|
|
||||||
switch (c) {
|
|
||||||
case 'h':
|
|
||||||
return cli_help(cli_cmds);
|
|
||||||
case ARG_VERSION:
|
|
||||||
puts(PACKAGE_STRING);
|
|
||||||
return 0;
|
|
||||||
case ARG_LOG_LEVEL:
|
|
||||||
cli_max_sev = log_parse_arg(optarg);
|
|
||||||
break;
|
|
||||||
case ARG_JOURNAL_LEVEL:
|
|
||||||
log_max_sev = log_parse_arg(optarg);
|
|
||||||
break;
|
|
||||||
case ARG_AUDIO:
|
|
||||||
gst_audio_en = atoi(optarg);
|
|
||||||
break;
|
|
||||||
case '?':
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int r;
|
|
||||||
setlocale(LC_ALL, "");
|
|
||||||
|
|
||||||
r = parse_argv(argc, argv);
|
|
||||||
if (r < 0)
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
if (!r)
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
|
|
||||||
r = sd_bus_default_system(&bus);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_error("cannot connect to system bus: %s", strerror(-r));
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = ctl_main(argc, argv);
|
|
||||||
sd_bus_unref(bus);
|
|
||||||
|
|
||||||
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,851 +0,0 @@
|
||||||
/*
|
|
||||||
* MiracleCast - Wifi-Display/Miracast Implementation
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
|
|
||||||
*
|
|
||||||
* MiracleCast is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2.1 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* MiracleCast 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
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "wfd-src.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
#define DEFAULT_RTSP_PORT (7236)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RTSP Session
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void src_handle_options(struct wfd_src *s,
|
|
||||||
struct rtsp_message *m)
|
|
||||||
{
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *rep = NULL;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
cli_debug("INCOMING (M2): %s\n", rtsp_message_get_raw(m));
|
|
||||||
|
|
||||||
r = rtsp_message_new_reply_for(m, &rep, RTSP_CODE_OK, NULL);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_append(rep, "<s>",
|
|
||||||
"Public",
|
|
||||||
"org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER, "
|
|
||||||
"SETUP, PLAY, PAUSE, TEARDOWN");
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtsp_message_seal(rep);
|
|
||||||
cli_debug("OUTGOING (M2): %s\n", rtsp_message_get_raw(rep));
|
|
||||||
|
|
||||||
r = rtsp_send(s->rtsp, rep);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
error:
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_trigger_play_rep_fn(struct rtsp *bus,
|
|
||||||
struct rtsp_message *m,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
cli_debug("INCOMING (M5): %s\n", rtsp_message_get_raw(m));
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void src_handle_setup(struct wfd_src *s,
|
|
||||||
struct rtsp_message *m)
|
|
||||||
{
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *rep = NULL;
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *req = NULL;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
cli_debug("INCOMING (M6): %s\n", rtsp_message_get_raw(m));
|
|
||||||
|
|
||||||
r = rtsp_message_new_reply_for(m, &rep, RTSP_CODE_OK, NULL);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_append(rep, "<s>",
|
|
||||||
"Session", "0;timeout=30");
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[256];
|
|
||||||
snprintf(buf, sizeof(buf), "RTP/AVP/UDP;unicast;client_port=%d", s->sink.rtp_ports.port0);
|
|
||||||
r = rtsp_message_append(rep, "<s>",
|
|
||||||
"Transport", buf);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtsp_message_seal(rep);
|
|
||||||
cli_debug("OUTGOING (M6): %s\n", rtsp_message_get_raw(rep));
|
|
||||||
|
|
||||||
r = rtsp_send(s->rtsp, rep);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_new_request(s->rtsp,
|
|
||||||
&req,
|
|
||||||
"SET_PARAMETER",
|
|
||||||
s->url);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_append(req, "{&}", "wfd_trigger_method: PLAY");
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtsp_message_seal(req);
|
|
||||||
cli_debug("OUTGOING (M5): %s\n", rtsp_message_get_raw(req));
|
|
||||||
|
|
||||||
r = rtsp_call_async(s->rtsp, req, src_trigger_play_rep_fn, s, 0, NULL);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
wfd_fn_src_setup(s);
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
error:
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void src_handle_play(struct wfd_src *s,
|
|
||||||
struct rtsp_message *m)
|
|
||||||
{
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *rep = NULL;
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *req = NULL;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
cli_debug("INCOMING (M7): %s\n", rtsp_message_get_raw(m));
|
|
||||||
|
|
||||||
r = rtsp_message_new_reply_for(m, &rep, RTSP_CODE_OK, NULL);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_append(rep, "<s>",
|
|
||||||
"Session", "0;timeout=30");
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_append(rep, "<s>",
|
|
||||||
"Range", "ntp=now-");
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtsp_message_seal(rep);
|
|
||||||
cli_debug("OUTGOING (M7): %s\n", rtsp_message_get_raw(rep));
|
|
||||||
|
|
||||||
r = rtsp_send(s->rtsp, rep);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
wfd_fn_src_playing(s);
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
error:
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void src_handle_pause(struct wfd_src *s,
|
|
||||||
struct rtsp_message *m)
|
|
||||||
{
|
|
||||||
cli_debug("INCOMING (M9): %s\n", rtsp_message_get_raw(m));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void src_handle_teardown(struct wfd_src *s,
|
|
||||||
struct rtsp_message *m)
|
|
||||||
{
|
|
||||||
cli_debug("INCOMING (M8): %s\n", rtsp_message_get_raw(m));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool parse_video_formats(struct rtsp_message *m,
|
|
||||||
struct video_formats *formats)
|
|
||||||
{
|
|
||||||
const char *param;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
r = rtsp_message_read(m, "{<&>}", "wfd_video_formats", ¶m);
|
|
||||||
if(r < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else if(!strncmp("none", param, 4) || strlen(param) < 55) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = sscanf(param, "%hhx %hhx %hhx %hhx %x %x %x %hhx %hx %hx %hhx",
|
|
||||||
&formats->native_disp_mode,
|
|
||||||
&formats->pref_disp_mode,
|
|
||||||
&formats->codec_profile,
|
|
||||||
&formats->codec_level,
|
|
||||||
&formats->resolutions_cea,
|
|
||||||
&formats->resolutions_vesa,
|
|
||||||
&formats->resolutions_hh,
|
|
||||||
&formats->latency,
|
|
||||||
&formats->min_slice_size,
|
|
||||||
&formats->slice_enc_params,
|
|
||||||
&formats->frame_rate_control);
|
|
||||||
if(r < 11) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
formats->hres = -1;
|
|
||||||
formats->vres = -1;
|
|
||||||
sscanf(param + 55, "%x %x", &formats->hres, &formats->vres);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error:
|
|
||||||
cli_printf("[" CLI_RED "ERROR" CLI_DEFAULT "] Invalid video formats\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool parse_audio_codecs(struct rtsp_message *m,
|
|
||||||
struct audio_codecs *codecs)
|
|
||||||
{
|
|
||||||
const char *param;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
r = rtsp_message_read(m, "{<&>}", "wfd_audio_codecs", ¶m);
|
|
||||||
if(r < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else if(!strncmp("none", param, 4) || strlen(param) < 4) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_printf("audio codecs: %s\n", param);
|
|
||||||
|
|
||||||
if(!strncmp("LPCM", param, 4)) {
|
|
||||||
codecs->format = AUDIO_FORMAT_LPCM;
|
|
||||||
}
|
|
||||||
else if(!strncmp("AAC", param, 3)) {
|
|
||||||
codecs->format = AUDIO_FORMAT_AAC;
|
|
||||||
}
|
|
||||||
else if(!strncmp("AC3", param, 3)) {
|
|
||||||
codecs->format = AUDIO_FORMAT_AC3;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = sscanf(param, "%x %hhx",
|
|
||||||
&codecs->modes,
|
|
||||||
&codecs->latency);
|
|
||||||
if(r < 2) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error:
|
|
||||||
cli_printf("[" CLI_RED "ERROR" CLI_DEFAULT "] Invalid audio codecs\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool parse_client_rtp_ports(struct rtsp_message *m,
|
|
||||||
struct client_rtp_ports *ports)
|
|
||||||
{
|
|
||||||
const char *param;
|
|
||||||
int r;
|
|
||||||
char mode[10] = "";
|
|
||||||
|
|
||||||
r = rtsp_message_read(m, "{<&>}", "wfd_client_rtp_ports", ¶m);
|
|
||||||
if(r < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = sscanf(param, "%ms %hu %hu %9s",
|
|
||||||
&ports->profile,
|
|
||||||
&ports->port0,
|
|
||||||
&ports->port1,
|
|
||||||
mode);
|
|
||||||
if(r < 4 || strcmp("mode=play", mode)) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error:
|
|
||||||
cli_printf("[" CLI_RED "ERROR" CLI_DEFAULT "] Invalid client RTP ports\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_trigger_setup_rep_fn(struct rtsp *bus,
|
|
||||||
struct rtsp_message *m,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
struct wfd_src *s = data;
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *req = NULL;
|
|
||||||
|
|
||||||
cli_debug("INCOMING (M5): %s\n", rtsp_message_get_raw(m));
|
|
||||||
|
|
||||||
if(rtsp_message_is_reply(m, RTSP_CODE_OK, NULL)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_printf("[" CLI_RED "ERROR" CLI_DEFAULT "] Sink failed to SETUP\n");
|
|
||||||
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_set_parameter_rep_fn(struct rtsp *bus,
|
|
||||||
struct rtsp_message *m,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
struct wfd_src *s = data;
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *req = NULL;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
cli_debug("INCOMING (M4): %s\n", rtsp_message_get_raw(m));
|
|
||||||
|
|
||||||
if(!rtsp_message_is_reply(m, RTSP_CODE_OK, NULL)) {
|
|
||||||
r = -1;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_new_request(s->rtsp,
|
|
||||||
&req,
|
|
||||||
"SET_PARAMETER",
|
|
||||||
s->url);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_append(req, "{&}", "wfd_trigger_method: SETUP");
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtsp_message_seal(req);
|
|
||||||
cli_debug("OUTGOING (M5): %s\n", rtsp_message_get_raw(req));
|
|
||||||
|
|
||||||
r = rtsp_call_async(s->rtsp, req, src_trigger_setup_rep_fn, s, 0, NULL);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error:
|
|
||||||
cli_printf("[" CLI_RED "ERROR" CLI_DEFAULT "] SETUP failed\n");
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[1024];
|
|
||||||
|
|
||||||
static int src_send_set_parameter(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *req;
|
|
||||||
int r;
|
|
||||||
const static char tmp[] =
|
|
||||||
"wfd_video_formats: 38 00 02 10 00000080 00000000 00000000 00 0000 0000 11 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"
|
|
||||||
"wfd_presentation_URL: %s/streamid=0 none\n"
|
|
||||||
"wfd_client_rtp_ports: %s %d %d mode=play";
|
|
||||||
|
|
||||||
r = rtsp_message_new_request(s->rtsp,
|
|
||||||
&req,
|
|
||||||
"SET_PARAMETER",
|
|
||||||
s->url);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(buf, sizeof(buf), tmp, s->url, s->sink.rtp_ports.profile,
|
|
||||||
s->sink.rtp_ports.port0, s->sink.rtp_ports.port1);
|
|
||||||
|
|
||||||
r = rtsp_message_append(req, "{&}", buf);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtsp_message_seal(req);
|
|
||||||
cli_debug("OUTGOING (M4): %s\n", rtsp_message_get_raw(req));
|
|
||||||
|
|
||||||
r = rtsp_call_async(s->rtsp, req, src_set_parameter_rep_fn, s, 0, NULL);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error:
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_get_parameter_rep_fn(struct rtsp *bus,
|
|
||||||
struct rtsp_message *m,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
struct wfd_src *s = data;
|
|
||||||
|
|
||||||
cli_debug("INCOMING (M3): %s\n", rtsp_message_get_raw(m));
|
|
||||||
|
|
||||||
if (!rtsp_message_is_reply(m, RTSP_CODE_OK, NULL)) {
|
|
||||||
cli_printf("[" CLI_RED "ERROR" CLI_DEFAULT "] GET_PARAMETER failed\n");
|
|
||||||
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(s->sink.rtp_ports.profile);
|
|
||||||
s->sink.rtp_ports.profile = NULL;
|
|
||||||
|
|
||||||
s->sink.has_video_formats = parse_video_formats(m, &s->sink.video_formats);
|
|
||||||
//s->sink.has_audio_codecs = parse_audio_codecs(m, &s->sink.audio_codecs);
|
|
||||||
s->sink.has_rtp_ports = parse_client_rtp_ports(m, &s->sink.rtp_ports);
|
|
||||||
|
|
||||||
return src_send_set_parameter(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_options_rep_fn(struct rtsp *bus,
|
|
||||||
struct rtsp_message *m,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
struct wfd_src *s = data;
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *req = NULL;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
cli_debug("INCOMING (M1): %s\n", rtsp_message_get_raw(m));
|
|
||||||
|
|
||||||
if(!rtsp_message_is_reply(m, RTSP_CODE_OK, NULL)) {
|
|
||||||
cli_printf("[" CLI_RED "ERROR" CLI_DEFAULT "] Failed to get OPTIONS from sink\n");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_new_request(s->rtsp,
|
|
||||||
&req,
|
|
||||||
"GET_PARAMETER",
|
|
||||||
s->url);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = rtsp_message_append(req, "{&}",
|
|
||||||
"wfd_video_formats\n"
|
|
||||||
//"wfd_audio_codecs\n"
|
|
||||||
"wfd_client_rtp_ports\n"
|
|
||||||
//"wfd_uibc_capability"
|
|
||||||
);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
rtsp_message_seal(req);
|
|
||||||
cli_debug("OUTGOING (M3): %s\n", rtsp_message_get_raw(req));
|
|
||||||
|
|
||||||
r = rtsp_call_async(s->rtsp, req, src_get_parameter_rep_fn, s, 0, NULL);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error:
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void src_handle(struct wfd_src *s,
|
|
||||||
struct rtsp_message *m)
|
|
||||||
{
|
|
||||||
const char *method;
|
|
||||||
|
|
||||||
if(!m) {
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
method = rtsp_message_get_method(m);
|
|
||||||
if(!method) {
|
|
||||||
cli_debug("INCOMING: Unexpected message (%d): %s\n",
|
|
||||||
rtsp_message_get_type(m),
|
|
||||||
rtsp_message_get_raw(m));
|
|
||||||
}
|
|
||||||
else if (!strcmp(method, "OPTIONS")) {
|
|
||||||
src_handle_options(s, m);
|
|
||||||
} else if (!strcmp(method, "SETUP")) {
|
|
||||||
src_handle_setup(s, m);
|
|
||||||
} else if (!strcmp(method, "PLAY")) {
|
|
||||||
src_handle_play(s, m);
|
|
||||||
} else if (!strcmp(method, "PAUSE")) {
|
|
||||||
src_handle_pause(s, m);
|
|
||||||
} else if (!strcmp(method, "TEARDOWN")) {
|
|
||||||
src_handle_teardown(s, m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_rtsp_fn(struct rtsp *bus,
|
|
||||||
struct rtsp_message *m,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
struct wfd_src *s = data;
|
|
||||||
|
|
||||||
if (!m)
|
|
||||||
s->hup = true;
|
|
||||||
else
|
|
||||||
src_handle(s, m);
|
|
||||||
|
|
||||||
if (s->hup) {
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void src_send_options(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
_rtsp_message_unref_ struct rtsp_message *req = NULL;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
r = rtsp_message_new_request(s->rtsp,
|
|
||||||
&req,
|
|
||||||
"OPTIONS",
|
|
||||||
"*");
|
|
||||||
if (r < 0)
|
|
||||||
return cli_vERR(r);
|
|
||||||
|
|
||||||
r = rtsp_message_append(req, "<s>",
|
|
||||||
"Require",
|
|
||||||
"org.wfa.wfd1.0");
|
|
||||||
if (r < 0)
|
|
||||||
return cli_vERR(r);
|
|
||||||
|
|
||||||
rtsp_message_seal(req);
|
|
||||||
|
|
||||||
r = rtsp_call_async(s->rtsp, req, src_options_rep_fn, s, 0, NULL);
|
|
||||||
if (r < 0)
|
|
||||||
return cli_vERR(r);
|
|
||||||
|
|
||||||
cli_debug("OUTGOING (M1): %s\n", rtsp_message_get_raw(req));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Source I/O
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void src_connected(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
int r, val;
|
|
||||||
struct sockaddr_storage addr;
|
|
||||||
socklen_t len;
|
|
||||||
|
|
||||||
cli_printf("got incomming connection request\n");
|
|
||||||
|
|
||||||
if (s->connected || s->hup)
|
|
||||||
return;
|
|
||||||
|
|
||||||
sd_event_source_set_enabled(s->fd_source, SD_EVENT_OFF);
|
|
||||||
|
|
||||||
len = sizeof(addr);
|
|
||||||
int fd = accept4(s->fd, (struct sockaddr *) &addr, &len, SOCK_CLOEXEC);
|
|
||||||
|
|
||||||
r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &len);
|
|
||||||
if (r < 0) {
|
|
||||||
s->hup = true;
|
|
||||||
cli_vERRNO();
|
|
||||||
return;
|
|
||||||
} else if (val) {
|
|
||||||
s->hup = true;
|
|
||||||
errno = val;
|
|
||||||
cli_error("cannot connect to remote host (%d): %m", errno);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_debug("connection established");
|
|
||||||
|
|
||||||
close(s->fd);
|
|
||||||
s->fd = fd;
|
|
||||||
s->addr = addr;
|
|
||||||
s->addr_size = len;
|
|
||||||
|
|
||||||
r = rtsp_open(&s->rtsp, s->fd);
|
|
||||||
if (r < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
r = rtsp_attach_event(s->rtsp, s->event, 0);
|
|
||||||
if (r < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
r = rtsp_add_match(s->rtsp, src_rtsp_fn, s);
|
|
||||||
if (r < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
s->connected = true;
|
|
||||||
wfd_fn_src_connected(s);
|
|
||||||
|
|
||||||
src_send_options(s);
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
error:
|
|
||||||
s->hup = true;
|
|
||||||
cli_vERR(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void src_io(struct wfd_src *s, uint32_t mask)
|
|
||||||
{
|
|
||||||
cli_notice("src_io: %u", mask);
|
|
||||||
|
|
||||||
if (mask & EPOLLIN) {
|
|
||||||
src_connected(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mask & EPOLLERR) {
|
|
||||||
cli_notice("ERR on socket");
|
|
||||||
s->hup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->hup) {
|
|
||||||
wfd_src_close(s);
|
|
||||||
wfd_fn_src_disconnected(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_io_fn(sd_event_source *source,
|
|
||||||
int fd,
|
|
||||||
uint32_t mask,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
src_io(data, mask);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int src_listen(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
int fd, r, enable = 1;
|
|
||||||
|
|
||||||
if (!s)
|
|
||||||
return cli_EINVAL();
|
|
||||||
if (s->fd >= 0)
|
|
||||||
return 0;
|
|
||||||
if (!s->addr.ss_family || !s->addr_size)
|
|
||||||
return cli_EINVAL();
|
|
||||||
|
|
||||||
fd = socket(s->addr.ss_family,
|
|
||||||
SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
|
|
||||||
0);
|
|
||||||
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 (r < 0) {
|
|
||||||
r = -errno;
|
|
||||||
cli_vERR(r);
|
|
||||||
goto err_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = listen(fd, 1);
|
|
||||||
if (r < 0) {
|
|
||||||
r = -errno;
|
|
||||||
if (r != -EINPROGRESS) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto err_close;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_printf("Wait for RTSP connection request from sink...\n");
|
|
||||||
|
|
||||||
r = sd_event_add_io(s->event,
|
|
||||||
&s->fd_source,
|
|
||||||
fd,
|
|
||||||
EPOLLERR | EPOLLIN | EPOLLET,
|
|
||||||
src_io_fn,
|
|
||||||
s);
|
|
||||||
if (r < 0) {
|
|
||||||
cli_vERR(r);
|
|
||||||
goto err_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
s->fd = fd;
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err_close:
|
|
||||||
close(fd);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void src_close(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
if (!s || s->fd < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
free(s->sink.rtp_ports.profile);
|
|
||||||
s->sink.rtp_ports.profile = NULL;
|
|
||||||
rtsp_remove_match(s->rtsp, src_rtsp_fn, s);
|
|
||||||
rtsp_detach_event(s->rtsp);
|
|
||||||
rtsp_unref(s->rtsp);
|
|
||||||
s->rtsp = NULL;
|
|
||||||
sd_event_source_unref(s->fd_source);
|
|
||||||
s->fd_source = NULL;
|
|
||||||
close(s->fd);
|
|
||||||
s->fd = -1;
|
|
||||||
s->connected = false;
|
|
||||||
s->hup = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Source Management
|
|
||||||
*/
|
|
||||||
|
|
||||||
int wfd_src_new(struct wfd_src **out,
|
|
||||||
sd_event *event)
|
|
||||||
{
|
|
||||||
struct wfd_src *s;
|
|
||||||
|
|
||||||
if (!out || !event)
|
|
||||||
return cli_EINVAL();
|
|
||||||
|
|
||||||
s = calloc(1, sizeof(*s));
|
|
||||||
if (!s)
|
|
||||||
return cli_ENOMEM();
|
|
||||||
|
|
||||||
s->event = sd_event_ref(event);
|
|
||||||
s->fd = -1;
|
|
||||||
|
|
||||||
*out = s;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wfd_src_free(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
if (!s)
|
|
||||||
return;
|
|
||||||
|
|
||||||
wfd_src_close(s);
|
|
||||||
free(s->local);
|
|
||||||
free(s->session);
|
|
||||||
sd_event_unref(s->event);
|
|
||||||
free(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
int wfd_src_listen(struct wfd_src *s, const char *local)
|
|
||||||
{
|
|
||||||
struct sockaddr_in addr = { };
|
|
||||||
char *l;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
if (!s || !local || s->fd >= 0)
|
|
||||||
return cli_EINVAL();
|
|
||||||
|
|
||||||
addr.sin_family = AF_INET;
|
|
||||||
addr.sin_port = htons(DEFAULT_RTSP_PORT);
|
|
||||||
r = inet_pton(AF_INET, local, &addr.sin_addr);
|
|
||||||
if (r != 1)
|
|
||||||
return cli_EINVAL();
|
|
||||||
|
|
||||||
l = strdup(local);
|
|
||||||
if (!l)
|
|
||||||
return cli_ENOMEM();
|
|
||||||
|
|
||||||
free(s->local);
|
|
||||||
s->local = l;
|
|
||||||
|
|
||||||
memcpy(&s->addr, &addr, sizeof(addr));
|
|
||||||
s->addr_size = sizeof(addr);
|
|
||||||
|
|
||||||
snprintf(s->url, sizeof(s->url), "rtsp://%s/wfd1.0", local);
|
|
||||||
|
|
||||||
return src_listen(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wfd_src_close(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
if (!s)
|
|
||||||
return;
|
|
||||||
|
|
||||||
src_close(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool wfd_src_is_connecting(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
return s && s->fd >= 0 && !s->connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool wfd_src_is_connected(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
return s && s->connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool wfd_src_is_closed(struct wfd_src *s)
|
|
||||||
{
|
|
||||||
return !s || s->fd < 0;
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
/*
|
|
||||||
* MiracleCast - Wifi-Display/Miracast Implementation
|
|
||||||
*
|
|
||||||
* MiracleCast is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2.1 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* MiracleCast 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
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef CTL_SRC_H
|
|
||||||
#define CTL_SRC_H
|
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <systemd/sd-event.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include "ctl.h"
|
|
||||||
|
|
||||||
#include "rtsp.h"
|
|
||||||
#include "shl_macro.h"
|
|
||||||
#include "shl_util.h"
|
|
||||||
#include "wfd.h"
|
|
||||||
|
|
||||||
enum audio_format {
|
|
||||||
AUDIO_FORMAT_UNKNOWN,
|
|
||||||
AUDIO_FORMAT_LPCM,
|
|
||||||
AUDIO_FORMAT_AAC,
|
|
||||||
AUDIO_FORMAT_AC3,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct video_formats {
|
|
||||||
uint8_t native_disp_mode;
|
|
||||||
uint8_t pref_disp_mode;
|
|
||||||
uint8_t codec_profile;
|
|
||||||
uint8_t codec_level;
|
|
||||||
unsigned int resolutions_cea;
|
|
||||||
unsigned int resolutions_vesa;
|
|
||||||
unsigned int resolutions_hh;
|
|
||||||
uint8_t latency;
|
|
||||||
unsigned short min_slice_size;
|
|
||||||
unsigned short slice_enc_params;
|
|
||||||
uint8_t frame_rate_control;
|
|
||||||
int hres;
|
|
||||||
int vres;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct audio_codecs {
|
|
||||||
enum audio_format format;
|
|
||||||
unsigned int modes;
|
|
||||||
uint8_t latency;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct client_rtp_ports {
|
|
||||||
char *profile;
|
|
||||||
unsigned short port0;
|
|
||||||
unsigned short port1;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wfd_src {
|
|
||||||
sd_event *event;
|
|
||||||
|
|
||||||
char *local;
|
|
||||||
char *session;
|
|
||||||
char url[256];
|
|
||||||
struct sockaddr_storage addr;
|
|
||||||
size_t addr_size;
|
|
||||||
int fd;
|
|
||||||
sd_event_source *fd_source;
|
|
||||||
|
|
||||||
sd_event_source *req_source;
|
|
||||||
|
|
||||||
struct rtsp *rtsp;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
struct video_formats video_formats;
|
|
||||||
struct audio_codecs audio_codecs;
|
|
||||||
struct client_rtp_ports rtp_ports;
|
|
||||||
|
|
||||||
bool has_video_formats : 1;
|
|
||||||
bool has_audio_codecs : 1;
|
|
||||||
bool has_rtp_ports : 1;
|
|
||||||
} sink;
|
|
||||||
|
|
||||||
bool connected : 1;
|
|
||||||
bool hup : 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* CTL_SRC_H */
|
|
|
@ -1,42 +0,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(
|
|
||||||
${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.Sender
|
|
||||||
${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 sender-iface
|
|
||||||
${GDK_LIBRARIES}
|
|
||||||
${GIO_LIBRARIES}
|
|
||||||
${GST_LIBRARIES}
|
|
||||||
${GST_PBUTILS_LIBRARIES})
|
|
||||||
|
|
||||||
install(TARGETS miracle-sender DESTINATION bin)
|
|
|
@ -1,20 +0,0 @@
|
||||||
<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>
|
|
|
@ -1,746 +0,0 @@
|
||||||
/*
|
|
||||||
* MiracleCast - Wifi-Display/Miracast Implementation
|
|
||||||
*
|
|
||||||
* MiracleCast is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2.1 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* MiracleCast 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
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with MiracleCast; If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <glib.h>
|
|
||||||
#include <glib-object.h>
|
|
||||||
#include <glib-unix.h>
|
|
||||||
#include <glib/gstdio.h>
|
|
||||||
#include <glib/gprintf.h>
|
|
||||||
#include <gdk/gdk.h>
|
|
||||||
#include <gst/gst.h>
|
|
||||||
#include <gst/pbutils/pbutils.h>
|
|
||||||
#include <gst/pbutils/encoding-profile.h>
|
|
||||||
#include "sender-iface.h"
|
|
||||||
#include "sender.h"
|
|
||||||
|
|
||||||
struct SenderImpl
|
|
||||||
{
|
|
||||||
Sender *skeleton;
|
|
||||||
|
|
||||||
GMainLoop *loop;
|
|
||||||
|
|
||||||
GstElement *pipeline;
|
|
||||||
|
|
||||||
struct CmdChannel *channel;
|
|
||||||
|
|
||||||
guint bus_owner_id;
|
|
||||||
|
|
||||||
guint bus_obj_id;
|
|
||||||
|
|
||||||
GDBusMethodInvocation *method_invoke;
|
|
||||||
|
|
||||||
guint timer_handle;
|
|
||||||
};
|
|
||||||
|
|
||||||
static gchar *arg_host = NULL;
|
|
||||||
|
|
||||||
static guint arg_port = 1991;
|
|
||||||
|
|
||||||
static guint arg_width = 0;
|
|
||||||
|
|
||||||
static guint arg_height = 0;
|
|
||||||
|
|
||||||
static gint arg_screen = -1;
|
|
||||||
|
|
||||||
static gchar *arg_acodec = NULL;
|
|
||||||
|
|
||||||
static gboolean arg_audio_only = FALSE;
|
|
||||||
|
|
||||||
static guint arg_refresh_rate = 25;
|
|
||||||
|
|
||||||
static const char *vpipeline_desc =
|
|
||||||
"ximagesrc name=vsrc use-damage=false show-pointer=false starty=%d startx=%d endy=%d endx=%d "
|
|
||||||
"capsfilter name=caps_framerate caps=\"video/x-raw, framerate=%u/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 "
|
|
||||||
//"vaapih264enc name=vencoder max-bframes=0 "
|
|
||||||
"x264enc name=vencoder tune=zerolatency "
|
|
||||||
"capsfilter name=caps_vencoder caps=\"video/x-h264, profile=baseline\" "
|
|
||||||
//"queue name=vqueue max-size-buffers=0 max-size-bytes=0 "
|
|
||||||
"mpegtsmux name=muxer "
|
|
||||||
"rtpmp2tpay name=rtppay "
|
|
||||||
"udpsink name=sink host=\"%s\" port=%d ";
|
|
||||||
//"filesink name=sink location=vaapi.mp2t ";
|
|
||||||
|
|
||||||
static const char *apipeline_desc =
|
|
||||||
"pulsesrc name=asrc device=\"%s\" "
|
|
||||||
"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);
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sender_impl_free(struct SenderImpl *self)
|
|
||||||
{
|
|
||||||
if(self->pipeline) {
|
|
||||||
gst_element_set_state(self->pipeline, GST_STATE_NULL);
|
|
||||||
g_object_unref(G_OBJECT(self->pipeline));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self->loop) {
|
|
||||||
g_main_loop_unref(self->loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(self->bus_owner_id) {
|
|
||||||
g_bus_unown_name(self->bus_owner_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_slice_free(struct SenderImpl, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void get_screen_dimension(gint *top,
|
|
||||||
gint *left,
|
|
||||||
gint *bottom,
|
|
||||||
gint *right)
|
|
||||||
{
|
|
||||||
GdkRectangle rect;
|
|
||||||
|
|
||||||
#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 {
|
|
||||||
monitor = gdk_display_get_monitor(display, arg_screen);
|
|
||||||
}
|
|
||||||
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
|
|
||||||
|
|
||||||
*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);
|
|
||||||
if(!e1) {
|
|
||||||
g_warning("no such element: %s", name1);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
GstElement *e2 = gst_bin_get_by_name(bin, name2);
|
|
||||||
if(!e2) {
|
|
||||||
g_warning("no such element: %s", name2);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
g_info("sender playing");
|
|
||||||
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(self->pipeline),
|
|
||||||
GST_DEBUG_GRAPH_SHOW_ALL,
|
|
||||||
"miracle-sender-playing");
|
|
||||||
break;
|
|
||||||
case GST_STATE_READY:
|
|
||||||
g_object_set(self->skeleton, "state", "paused", NULL);
|
|
||||||
if(self->method_invoke) {
|
|
||||||
sender_complete_prepare(SENDER(self->skeleton),
|
|
||||||
self->method_invoke);
|
|
||||||
self->method_invoke = NULL;
|
|
||||||
}
|
|
||||||
g_info("sender ready");
|
|
||||||
|
|
||||||
GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(self->pipeline),
|
|
||||||
GST_DEBUG_GRAPH_SHOW_ALL,
|
|
||||||
"miracle-sender-ready");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
g_info("sender paused");
|
|
||||||
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("pipeline 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,
|
|
||||||
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,
|
|
||||||
apipeline_desc,
|
|
||||||
"alsa_output.pci-0000_00_1b.0.analog-stereo.monitor");
|
|
||||||
}
|
|
||||||
|
|
||||||
g_debug("finale pipeline: %s", desc->str);
|
|
||||||
|
|
||||||
self->pipeline = gst_parse_launch(desc->str, &error);
|
|
||||||
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),
|
|
||||||
//"profile", vencode_profile,
|
|
||||||
"max-bframes", 0,
|
|
||||||
NULL);
|
|
||||||
g_object_unref(G_OBJECT(vencoder));
|
|
||||||
//g_object_unref(G_OBJECT(vencode_profile));
|
|
||||||
|
|
||||||
if(arg_acodec) {
|
|
||||||
const char *format;
|
|
||||||
if(!strncmp("ac3", arg_acodec, 3)) {
|
|
||||||
format = "audio/x-ac3, framed=true";
|
|
||||||
}
|
|
||||||
else if(!strncmp("pcm", arg_acodec, 3)) {
|
|
||||||
format = "audio/x-lpcm";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
format = "audio/mpeg, framed=true, mpegversion=4, stream-format=adts";
|
|
||||||
}
|
|
||||||
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",
|
|
||||||
"caps_vencoder",
|
|
||||||
//"vqueue",
|
|
||||||
"muxer",
|
|
||||||
"rtppay",
|
|
||||||
"sink",
|
|
||||||
NULL);
|
|
||||||
if(result < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(arg_acodec) {
|
|
||||||
link_elements(GST_BIN(self->pipeline),
|
|
||||||
"asrc",
|
|
||||||
"aconv",
|
|
||||||
"aresample",
|
|
||||||
"aencoder",
|
|
||||||
"aqueue",
|
|
||||||
"muxer",
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
gst_element_set_state(self->pipeline, GST_STATE_READY);
|
|
||||||
|
|
||||||
goto free_desc;
|
|
||||||
|
|
||||||
error:
|
|
||||||
if(self->pipeline) {
|
|
||||||
g_object_unref(self->pipeline);
|
|
||||||
self->pipeline = NULL;
|
|
||||||
}
|
|
||||||
free_desc:
|
|
||||||
g_string_free(desc, TRUE);
|
|
||||||
end:
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sender_on_name_acquired(GDBusConnection *conn,
|
|
||||||
const char *name,
|
|
||||||
gpointer user_data)
|
|
||||||
{
|
|
||||||
struct SenderImpl *self = user_data;
|
|
||||||
GError *error = NULL;
|
|
||||||
gboolean result;
|
|
||||||
|
|
||||||
g_info("dbus name org.freedesktop.miracle acquired");
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_info("trying to acquire dbus name org.freedesktop.miracle...");
|
|
||||||
|
|
||||||
self->bus_owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
|
|
||||||
"org.freedesktop.miracle",
|
|
||||||
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->timer_handle) {
|
|
||||||
g_source_remove(self->timer_handle);
|
|
||||||
self->timer_handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 start_play(gpointer user_data)
|
|
||||||
{
|
|
||||||
gst_element_set_state(((struct SenderImpl *) user_data)->pipeline, GST_STATE_PLAYING);
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
g_timeout_add_seconds(1, start_play, self);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 gboolean on_timeout_quit(gpointer user_data)
|
|
||||||
{
|
|
||||||
struct SenderImpl *self = user_data;
|
|
||||||
|
|
||||||
g_main_loop_quit(self->loop);
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
self->timer_handle = g_timeout_add_seconds(3, on_timeout_quit, self);
|
|
||||||
|
|
||||||
end:
|
|
||||||
sender_complete_stop(SENDER(self->skeleton), invocation);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sender_impl_run(struct SenderImpl *self)
|
|
||||||
{
|
|
||||||
g_main_loop_run(self->loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
static GOptionEntry entries[] = {
|
|
||||||
{ "host", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &arg_host, "the hostname of sink", "" },
|
|
||||||
{ "port", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &arg_port, "the port which sink is waiting for RTP string", "" },
|
|
||||||
{ "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", '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)
|
|
||||||
{
|
|
||||||
GOptionContext *opt_context;
|
|
||||||
GError *error = NULL;
|
|
||||||
|
|
||||||
opt_context = g_option_context_new("");
|
|
||||||
if(!opt_context) {
|
|
||||||
g_error("%s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
g_option_context_add_main_entries(opt_context, entries, NULL);
|
|
||||||
if(!g_option_context_parse(opt_context, argc, args, &error)) {
|
|
||||||
g_fprintf(stderr, "%s\n", error->message);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(0 >= arg_port || arg_port > 65535) {
|
|
||||||
g_fprintf(stderr, "Invalid port number: %i\n", arg_port);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_option_context_free(opt_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_raise_rank(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 SenderImpl *sender;
|
|
||||||
|
|
||||||
arg_parse(&argc, &args);
|
|
||||||
|
|
||||||
gdk_init(&argc, &args);
|
|
||||||
gst_init(&argc, &args);
|
|
||||||
gst_pb_utils_init();
|
|
||||||
gst_raise_rank("vaapih264enc", "vaapienc_h264", "glcolorconvert", NULL);
|
|
||||||
|
|
||||||
sender = sender_impl_new();
|
|
||||||
if(!sender) {
|
|
||||||
g_error("%s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!sender_impl_init(sender)) {
|
|
||||||
g_error("%s", strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
sender_impl_run(sender);
|
|
||||||
|
|
||||||
sender_impl_free(sender);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
/*
|
|
||||||
* =====================================================================================
|
|
||||||
*
|
|
||||||
* 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,
|
|
||||||
};
|
|
Loading…
Reference in a new issue