Changed Name of Application
This commit is contained in:
382
airplayServer.cpp
Executable file
382
airplayServer.cpp
Executable file
@@ -0,0 +1,382 @@
|
||||
/**
|
||||
* RPiPlay - An open-source AirPlay mirroring server for Raspberry Pi
|
||||
* Copyright (C) 2019 Florian Draschbacher
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <cstring>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
#include "log.h"
|
||||
#include "lib/raop.h"
|
||||
#include "lib/stream.h"
|
||||
#include "lib/logger.h"
|
||||
#include "lib/dnssd.h"
|
||||
#include "renderers/video_renderer.h"
|
||||
#include "renderers/audio_renderer.h"
|
||||
|
||||
#include <gst/video/videooverlay.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/gdkx.h> // for GDK_WINDOW_XID
|
||||
#endif
|
||||
#ifdef GDK_WINDOWING_WIN32
|
||||
#include <gdk/gdkwin32.h> // for GDK_WINDOW_HWND
|
||||
#endif
|
||||
|
||||
#define VERSION "1.2"
|
||||
|
||||
#define DEFAULT_NAME "AirPlayServer for Linux"
|
||||
#define DEFAULT_BACKGROUND_MODE BACKGROUND_MODE_ON
|
||||
#define DEFAULT_AUDIO_DEVICE AUDIO_DEVICE_HDMI
|
||||
#define DEFAULT_LOW_LATENCY false
|
||||
#define DEFAULT_DEBUG_LOG false
|
||||
#define DEFAULT_HW_ADDRESS { (char) 0x48, (char) 0x5d, (char) 0x60, (char) 0x7c, (char) 0xee, (char) 0x22 }
|
||||
|
||||
|
||||
|
||||
int start_server(std::vector<char> hw_addr, std::string name, background_mode_t background_mode,
|
||||
audio_device_t audio_device, bool low_latency, bool debug_log, gulong embed_xid);
|
||||
|
||||
int stop_server();
|
||||
|
||||
static bool running = false;
|
||||
static dnssd_t *dnssd = NULL;
|
||||
static raop_t *raop = NULL;
|
||||
static video_renderer_t *video_renderer = NULL;
|
||||
static audio_renderer_t *audio_renderer = NULL;
|
||||
|
||||
struct GTK_STRUCT {
|
||||
GtkWidget *app_window, *video_window;
|
||||
GdkWindow *video_window_xwindow;
|
||||
gulong embed_xid;
|
||||
cairo_surface_t *surface = NULL;
|
||||
};
|
||||
|
||||
static GTK_STRUCT * GTK_data = NULL;
|
||||
|
||||
static void signal_handler(int sig) {
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
running = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void init_signals(void) {
|
||||
struct sigaction sigact;
|
||||
|
||||
sigact.sa_handler = signal_handler;
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = 0;
|
||||
sigaction(SIGINT, &sigact, NULL);
|
||||
sigaction(SIGTERM, &sigact, NULL);
|
||||
}
|
||||
|
||||
static int parse_hw_addr(std::string str, std::vector<char> &hw_addr) {
|
||||
for (int i = 0; i < str.length(); i += 3) {
|
||||
hw_addr.push_back((char) stol(str.substr(i), NULL, 16));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string find_mac() {
|
||||
std::ifstream iface_stream("/sys/class/net/eth0/address");
|
||||
if (!iface_stream) {
|
||||
iface_stream.open("/sys/class/net/wlan0/address");
|
||||
}
|
||||
if (!iface_stream) return "";
|
||||
|
||||
std::string mac_address;
|
||||
iface_stream >> mac_address;
|
||||
iface_stream.close();
|
||||
return mac_address;
|
||||
}
|
||||
|
||||
void print_info(char *name) {
|
||||
printf("RPiPlay %s: An open-source AirPlay mirroring server for Raspberry Pi\n", VERSION);
|
||||
printf("Usage: %s [-b (on|auto|off)] [-n name] [-a (hdmi|analog|off)]\n", name);
|
||||
printf("Options:\n");
|
||||
printf("-n name Specify the network name of the AirPlay server\n");
|
||||
printf("-b (on|auto|off) Show black background always, only during active connection, or never\n");
|
||||
printf("-a (hdmi|analog|off) Set audio output device\n");
|
||||
printf("-l Enable low-latency mode (disables render clock)\n");
|
||||
printf("-d Enable debug logging\n");
|
||||
printf("-v/-h Displays this help and version information\n");
|
||||
}
|
||||
|
||||
static void gtk_destroy (GtkWidget * widget, gpointer data) {
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
||||
gboolean draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data) {
|
||||
guint width, height;
|
||||
GdkRGBA color;
|
||||
GtkStyleContext *context;
|
||||
|
||||
context = gtk_widget_get_style_context (widget);
|
||||
|
||||
width = gtk_widget_get_allocated_width (widget);
|
||||
height = gtk_widget_get_allocated_height (widget);
|
||||
|
||||
gtk_render_background (context, cr, 0, 0, width, height);
|
||||
|
||||
cairo_rectangle (cr, 0, 0, width, height);
|
||||
|
||||
gtk_style_context_get_color (context,
|
||||
gtk_style_context_get_state (context),
|
||||
&color);
|
||||
gdk_cairo_set_source_rgba (cr, &color);
|
||||
|
||||
cairo_fill (cr);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void configure_callback(GtkWindow *window, GdkEvent *event, gpointer data) {
|
||||
gtk_widget_queue_draw(GTK_data->video_window);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
init_signals();
|
||||
|
||||
background_mode_t background = DEFAULT_BACKGROUND_MODE;
|
||||
std::string server_name = DEFAULT_NAME;
|
||||
std::vector<char> server_hw_addr = DEFAULT_HW_ADDRESS;
|
||||
audio_device_t audio_device = DEFAULT_AUDIO_DEVICE;
|
||||
bool low_latency = DEFAULT_LOW_LATENCY;
|
||||
bool debug_log = DEFAULT_DEBUG_LOG;
|
||||
|
||||
// Parse arguments
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg(argv[i]);
|
||||
if (arg == "-n") {
|
||||
if (i == argc - 1) continue;
|
||||
server_name = std::string(argv[++i]);
|
||||
} else if (arg == "-b") {
|
||||
// For backwards-compatibility, make just -b disable the background
|
||||
if (i == argc - 1 || argv[i + 1][0] == '-') {
|
||||
background = BACKGROUND_MODE_OFF;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string background_mode(argv[++i]);
|
||||
background = background_mode == "off" ? BACKGROUND_MODE_OFF :
|
||||
background_mode == "auto" ? BACKGROUND_MODE_AUTO :
|
||||
BACKGROUND_MODE_ON;
|
||||
} else if (arg == "-a") {
|
||||
if (i == argc - 1) continue;
|
||||
std::string audio_device_name(argv[++i]);
|
||||
audio_device = audio_device_name == "hdmi" ? AUDIO_DEVICE_HDMI :
|
||||
audio_device_name == "analog" ? AUDIO_DEVICE_ANALOG :
|
||||
AUDIO_DEVICE_NONE;
|
||||
} else if (arg == "-l") {
|
||||
low_latency = !low_latency;
|
||||
} else if (arg == "-d") {
|
||||
debug_log = !debug_log;
|
||||
} else if (arg == "-h" || arg == "-v") {
|
||||
print_info(argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
std::string mac_address = find_mac();
|
||||
if (!mac_address.empty()) {
|
||||
server_hw_addr.clear();
|
||||
parse_hw_addr(mac_address, server_hw_addr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//GTK
|
||||
gtk_init (&argc, &argv);
|
||||
|
||||
GTK_data = new GTK_STRUCT();
|
||||
|
||||
GTK_data->app_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title (GTK_WINDOW (GTK_data->app_window), DEFAULT_NAME);
|
||||
gtk_widget_set_size_request (GTK_data->app_window, 200, 200);
|
||||
GTK_data->video_window = gtk_drawing_area_new ();
|
||||
gtk_widget_set_size_request(GTK_data->video_window, 800, 600);
|
||||
g_signal_connect (GTK_data->video_window, "destroy",G_CALLBACK (gtk_destroy), NULL);
|
||||
//gtk_widget_set_double_buffered (GTK_data->video_window, FALSE);
|
||||
gtk_container_add (GTK_CONTAINER (GTK_data->app_window), GTK_data->video_window);
|
||||
gtk_widget_show_all (GTK_data->app_window);
|
||||
gtk_widget_realize (GTK_data->video_window);
|
||||
g_signal_connect (G_OBJECT (GTK_data->video_window), "draw",G_CALLBACK (draw_callback), NULL);
|
||||
g_signal_connect(G_OBJECT(GTK_data->app_window), "configure-event", G_CALLBACK(configure_callback), NULL);
|
||||
|
||||
GTK_data->video_window_xwindow = gtk_widget_get_window (GTK_data->video_window);
|
||||
GTK_data->embed_xid = GDK_WINDOW_XID (GTK_data->video_window_xwindow);
|
||||
if (start_server(server_hw_addr, server_name, background, audio_device, low_latency, debug_log, GTK_data->embed_xid) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
gtk_main();
|
||||
|
||||
/*
|
||||
running = true;
|
||||
while (running) {
|
||||
sleep(1);
|
||||
}
|
||||
*/
|
||||
|
||||
LOGI("Stopping...");
|
||||
stop_server();
|
||||
}
|
||||
|
||||
// Server callbacks
|
||||
extern "C" void conn_init(void *cls) {
|
||||
//video_renderer_update_background(video_renderer, 1);
|
||||
}
|
||||
|
||||
extern "C" void conn_destroy(void *cls) {
|
||||
gtk_widget_queue_draw(GTK_data->video_window);
|
||||
//int width = gtk_widget_get_allocated_width (GTK_data->video_window);
|
||||
//int height = gtk_widget_get_allocated_height (GTK_data->video_window);
|
||||
//video_renderer_update_background(video_renderer, -1, width, height);
|
||||
}
|
||||
|
||||
extern "C" void audio_process(void *cls, raop_ntp_t *ntp, aac_decode_struct *data) {
|
||||
if (audio_renderer != NULL) {
|
||||
audio_renderer_render_buffer(audio_renderer, ntp, data->data, data->data_len, data->pts);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void video_process(void *cls, raop_ntp_t *ntp, h264_decode_struct *data) {
|
||||
video_renderer_render_buffer(video_renderer, ntp, data->data, data->data_len, data->pts, data->frame_type);
|
||||
}
|
||||
|
||||
extern "C" void audio_flush(void *cls) {
|
||||
audio_renderer_flush(audio_renderer);
|
||||
}
|
||||
|
||||
extern "C" void video_flush(void *cls) {
|
||||
video_renderer_flush(video_renderer);
|
||||
}
|
||||
|
||||
extern "C" void audio_set_volume(void *cls, float volume) {
|
||||
if (audio_renderer != NULL) {
|
||||
audio_renderer_set_volume(audio_renderer, volume);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void log_callback(void *cls, int level, const char *msg) {
|
||||
switch (level) {
|
||||
case LOGGER_DEBUG: {
|
||||
LOGD("%s", msg);
|
||||
break;
|
||||
}
|
||||
case LOGGER_WARNING: {
|
||||
LOGW("%s", msg);
|
||||
break;
|
||||
}
|
||||
case LOGGER_INFO: {
|
||||
LOGI("%s", msg);
|
||||
break;
|
||||
}
|
||||
case LOGGER_ERR: {
|
||||
LOGE("%s", msg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int start_server(std::vector<char> hw_addr, std::string name, background_mode_t background_mode, audio_device_t audio_device, bool low_latency, bool debug_log, gulong embed_xid) {
|
||||
raop_callbacks_t raop_cbs;
|
||||
memset(&raop_cbs, 0, sizeof(raop_cbs));
|
||||
raop_cbs.conn_init = conn_init;
|
||||
raop_cbs.conn_destroy = conn_destroy;
|
||||
raop_cbs.audio_process = audio_process;
|
||||
raop_cbs.video_process = video_process;
|
||||
raop_cbs.audio_flush = audio_flush;
|
||||
raop_cbs.video_flush = video_flush;
|
||||
raop_cbs.audio_set_volume = audio_set_volume;
|
||||
|
||||
raop = raop_init(10, &raop_cbs);
|
||||
if (raop == NULL) {
|
||||
LOGE("Error initializing raop!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
raop_set_log_callback(raop, log_callback, NULL);
|
||||
raop_set_log_level(raop, debug_log ? RAOP_LOG_DEBUG : LOGGER_INFO);
|
||||
|
||||
logger_t *render_logger = logger_init();
|
||||
logger_set_callback(render_logger, log_callback, NULL);
|
||||
logger_set_level(render_logger, debug_log ? LOGGER_DEBUG : LOGGER_INFO);
|
||||
|
||||
if (low_latency) logger_log(render_logger, LOGGER_INFO, "Using low-latency mode");
|
||||
|
||||
if ((video_renderer = video_renderer_init(render_logger, background_mode, low_latency)) == NULL) {
|
||||
LOGE("Could not init video renderer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (audio_device == AUDIO_DEVICE_NONE) {
|
||||
LOGI("Audio disabled");
|
||||
} else if ((audio_renderer = audio_renderer_init(render_logger, video_renderer, audio_device, low_latency)) ==
|
||||
NULL) {
|
||||
LOGE("Could not init audio renderer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (video_renderer) {
|
||||
set_video_overlay2(video_renderer, embed_xid);
|
||||
video_renderer_start(video_renderer);
|
||||
}
|
||||
if (audio_renderer) audio_renderer_start(audio_renderer);
|
||||
|
||||
unsigned short port = 0;
|
||||
raop_start(raop, &port);
|
||||
raop_set_port(raop, port);
|
||||
|
||||
int error;
|
||||
dnssd = dnssd_init(name.c_str(), strlen(name.c_str()), hw_addr.data(), hw_addr.size(), &error);
|
||||
if (error) {
|
||||
LOGE("Could not initialize dnssd library!");
|
||||
return -2;
|
||||
}
|
||||
|
||||
raop_set_dnssd(raop, dnssd);
|
||||
|
||||
dnssd_register_raop(dnssd, port);
|
||||
dnssd_register_airplay(dnssd, port + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stop_server() {
|
||||
raop_destroy(raop);
|
||||
dnssd_unregister_raop(dnssd);
|
||||
dnssd_unregister_airplay(dnssd);
|
||||
audio_renderer_destroy(audio_renderer);
|
||||
video_renderer_destroy(video_renderer);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user