Files
UltraGrid/src/main.c
2012-07-04 13:48:36 +02:00

1298 lines
49 KiB
C

/*
* FILE: main.c
* AUTHORS: Colin Perkins <csp@csperkins.org>
* Ladan Gharai <ladan@isi.edu>
* Martin Benes <martinbenesh@gmail.com>
* Lukas Hejtmanek <xhejtman@ics.muni.cz>
* Petr Holub <hopet@ics.muni.cz>
* Milos Liska <xliska@fi.muni.cz>
* Jiri Matela <matela@ics.muni.cz>
* Dalibor Matura <255899@mail.muni.cz>
* Ian Wesley-Smith <iwsmith@cct.lsu.edu>
*
* Copyright (c) 2005-2010 CESNET z.s.p.o.
* Copyright (c) 2001-2004 University of Southern California
* Copyright (c) 2003-2004 University of Glasgow
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
*
* This product includes software developed by the University of Southern
* California Information Sciences Institute. This product also includes
* software developed by CESNET z.s.p.o.
*
* 4. Neither the name of the University nor of the Institute may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <pthread.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#endif // HAVE_CONFIG_H
#include "debug.h"
#ifdef HAVE_CUDA
#include "libgpujpeg/gpujpeg_common.h"
#endif // HAVE_CUDA
#include "perf.h"
#include "rtp/decoders.h"
#include "rtp/rtp.h"
#include "rtp/rtp_callback.h"
#include "rtp/pbuf.h"
#include "video_codec.h"
#include "video_capture.h"
#include "video_display.h"
#include "video_display/sdl.h"
#include "video_compress.h"
#include "video_decompress.h"
#include "pdb.h"
#include "tv.h"
#include "transmit.h"
#include "tfrc.h"
#include "ihdtv/ihdtv.h"
#include "lib_common.h"
#include "compat/platform_semaphore.h"
#include "audio/audio.h"
#if defined DEBUG && defined HAVE_LINUX
#include <mcheck.h>
#endif
#define EXIT_FAIL_USAGE 1
#define EXIT_FAIL_UI 2
#define EXIT_FAIL_DISPLAY 3
#define EXIT_FAIL_CAPTURE 4
#define EXIT_FAIL_NETWORK 5
#define EXIT_FAIL_TRANSMIT 6
#define EXIT_FAIL_COMPRESS 7
#define EXIT_FAIL_DECODER 8
#define PORT_BASE 5004
#define PORT_AUDIO 5006
/* please see comments before transmit.c:audio_tx_send() */
/* also note that this actually differs from video */
#define DEFAULT_AUDIO_FEC "mult:3"
#define AUDIO_CHANNEL_MAP (('a' << 8) | 'm')
#define AUDIO_CAPTURE_CHANNELS (('a' << 8) | 'c')
#define AUDIO_SCALE (('a' << 8) | 's')
#define ECHO_CANCELLATION (('E' << 8) | 'C')
#define CUDA_DEVICE (('C' << 8) | 'D')
struct state_uv {
int recv_port_number;
int send_port_number;
struct rtp **network_devices;
unsigned int connections_count;
struct vidcap *capture_device;
struct timeval start_time, curr_time;
struct pdb *participants;
char *decoder_mode;
char *postprocess;
uint32_t ts;
struct tx *tx;
struct display *display_device;
char *requested_compression;
const char *requested_display;
const char *requested_capture;
unsigned requested_mtu;
int use_ihdtv_protocol;
struct state_audio *audio;
/* used maily to serialize initialization */
pthread_mutex_t master_lock;
volatile unsigned int has_item_to_send:1;
volatile unsigned int sender_waiting:1;
volatile unsigned int compress_thread_waiting:1;
volatile unsigned int should_exit_sender:1;
pthread_mutex_t sender_lock;
pthread_cond_t compress_thread_cv;
pthread_cond_t sender_cv;
struct video_frame * volatile tx_frame;
};
long packet_rate;
volatile int should_exit = FALSE;
volatile int wait_to_finish = FALSE;
volatile int threads_joined = FALSE;
static int exit_status = EXIT_SUCCESS;
unsigned int cuda_device = 0;
unsigned int audio_capture_channels = 2;
uint32_t RTT = 0; /* this is computed by handle_rr in rtp_callback */
struct video_frame *frame_buffer = NULL;
uint32_t hd_color_spc = 0;
long frame_begin[2];
int uv_argc;
char **uv_argv;
static struct state_uv *uv_state;
void list_video_display_devices(void);
void list_video_capture_devices(void);
struct display *initialize_video_display(const char *requested_display,
char *fmt, unsigned int flags);
struct vidcap *initialize_video_capture(const char *requested_capture,
char *fmt, unsigned int flags);
static void sender_finish(struct state_uv *uv);
#ifndef WIN32
static void signal_handler(int signal)
{
debug_msg("Caught signal %d\n", signal);
exit_uv(0);
return;
}
#endif /* WIN32 */
void _exit_uv(int status);
void _exit_uv(int status) {
exit_status = status;
wait_to_finish = TRUE;
should_exit = TRUE;
if(!threads_joined) {
if(uv_state->capture_device) {
vidcap_finish(uv_state->capture_device);
pthread_mutex_lock(&uv_state->sender_lock);
uv_state->has_item_to_send = FALSE;
if(uv_state->compress_thread_waiting) {
pthread_cond_signal(&uv_state->compress_thread_cv);
}
pthread_mutex_unlock(&uv_state->sender_lock);
}
if(uv_state->display_device)
display_finish(uv_state->display_device);
if(uv_state->audio)
audio_finish(uv_state->audio);
}
wait_to_finish = FALSE;
}
void (*exit_uv)(int status) = _exit_uv;
static void usage(void)
{
/* TODO -c -p -b are deprecated options */
printf("\nUsage: uv [-d <display_device>] [-t <capture_device>] [-r <audio_playout>] [-s <audio_caputre>] [-l <limit_bitrate>\n");
printf(" [-m <mtu>] [-c] [-i] [-M <video_mode>] [-p <postprocess>] [-f <FEC_options>] [-P <port>] address(es)\n\n");
printf
("\t-d <display_device> \tselect display device, use '-d help' to get\n");
printf("\t \tlist of supported devices\n");
printf("\n");
printf
("\t-t <capture_device> \tselect capture device, use '-t help' to get\n");
printf("\t \tlist of supported devices\n");
printf("\n");
printf("\t-c <cfg> \tcompress video (see '-c help')\n");
printf("\n");
printf("\t-i \tiHDTV compatibility mode\n");
printf("\n");
printf("\t-r <playback_device> \tAudio playback device (see '-r help')\n");
printf("\n");
printf("\t-s <capture_device> \tAudio capture device (see '-s help')\n");
printf("\n");
printf("\t-j <settings> \tJACK Audio Connection Kit settings (see '-j help')\n");
printf("\n");
printf("\t-M <video_mode> \treceived video mode (eg tiled-4K, 3D, dual-link)\n");
printf("\n");
printf("\t-p <postprocess> \tpostprocess module\n");
printf("\n");
printf("\t-f <settings> \tconfig forward error checking, currently \"mult:<nr>\" or\"ldgm[:<k>:<m>]\"\n");
printf("\n");
printf("\t-P <port> | <recv_port>:<send_port>\n");
printf("\t \tbase port number, also 3 subsequent ports can be used for RTCP and audio streams. Default: %d.\n", PORT_BASE);
printf("\t \tIf one given, it will be used for both sending and receiving, if two, first one\n");
printf("\t \twill be used for receiving, second one for sending.\n");
printf("\n");
printf("\t-l <limit_bitrate> \tlimit sending bitrate (aggregate)\n");
printf("\t \tto limit_bitrate Mb/s\n");
printf("\n");
printf("\t--audio-channel-map <mapping> \tmapping of input audio channels to output audio channels\n");
printf("\t \tcomma-separated list of channel mapping\n");
printf("\t \teg. 0:0,1:0 - mixes first 2 channels into one mono stream\n");
printf("\t \t 0:0,:1 - first stream is left as is, second stream is set to silence\n");
printf("\t \t 0:0,0:1 - splits mono stream into 2 channels\n");
printf("\t \t\n");
printf("\n");
printf("\t--audio-scale [<factor>|<method>]\n");
printf("\t \tFloating point number which tells a static scaling factor for all output channels.\n");
printf("\t \tScaling method can be one from these:\n");
printf("\t \t\tmixauto - automatically adjust volume if using channel mixing/remapping (default)\n");
printf("\t \t\tauto - automatically adjust volume\n");
printf("\t \t\tnone - no scaling will be performed\n");
printf("\t \t\n");
printf("\n");
printf("\t--audio-capture-channels <count>\n");
printf("\t \tHow many of input channels should be captured (default 2).\n");
printf("\t \t\n");
printf("\n");
printf("\n");
printf("\t--echo-cancellation \tapply acustic echo cancellation to audio\n");
printf("\n");
printf("\t--cuda-device [<index>|help]\tuse specified CUDA device\n");
printf("\n");
printf("\taddress(es) \tdestination address\n");
printf("\n");
printf("\t \tIf comma-separated list of addresses\n");
printf("\t \tis entered, video frames are split\n");
printf("\t \tand chunks are sent/received independently.\n");
printf("\n");
}
void list_video_display_devices()
{
int i;
display_type_t *dt;
printf("Available display devices:\n");
display_init_devices();
for (i = 0; i < display_get_device_count(); i++) {
dt = display_get_device_details(i);
printf("\t%s\n", dt->name);
}
display_free_devices();
}
struct display *initialize_video_display(const char *requested_display,
char *fmt, unsigned int flags)
{
struct display *d;
display_type_t *dt;
display_id_t id = 0;
int i;
if(!strcmp(requested_display, "none"))
id = display_get_null_device_id();
if (display_init_devices() != 0) {
printf("Unable to initialise devices\n");
abort();
} else {
debug_msg("Found %d display devices\n",
display_get_device_count());
}
for (i = 0; i < display_get_device_count(); i++) {
dt = display_get_device_details(i);
if (strcmp(requested_display, dt->name) == 0) {
id = dt->id;
debug_msg("Found device\n");
break;
} else {
debug_msg("Device %s does not match %s\n", dt->name,
requested_display);
}
}
if(i == display_get_device_count()) {
fprintf(stderr, "WARNING: Selected '%s' display card "
"was not found.\n", requested_display);
return NULL;
}
display_free_devices();
d = display_init(id, fmt, flags);
return d;
}
void list_video_capture_devices()
{
int i;
struct vidcap_type *vt;
printf("Available capture devices:\n");
vidcap_init_devices();
for (i = 0; i < vidcap_get_device_count(); i++) {
vt = vidcap_get_device_details(i);
printf("\t%s\n", vt->name);
}
vidcap_free_devices();
}
struct vidcap *initialize_video_capture(const char *requested_capture,
char *fmt, unsigned int flags)
{
struct vidcap_type *vt;
vidcap_id_t id = 0;
int i;
if(!strcmp(requested_capture, "none"))
id = vidcap_get_null_device_id();
vidcap_init_devices();
for (i = 0; i < vidcap_get_device_count(); i++) {
vt = vidcap_get_device_details(i);
if (strcmp(vt->name, requested_capture) == 0) {
id = vt->id;
break;
}
}
if(i == vidcap_get_device_count()) {
fprintf(stderr, "WARNING: Selected '%s' capture card "
"was not found.\n", requested_capture);
return NULL;
}
vidcap_free_devices();
return vidcap_init(id, fmt, flags);
}
static struct rtp **initialize_network(char *addrs, int recv_port_base, int send_port_base, struct pdb *participants)
{
struct rtp **devices = NULL;
double rtcp_bw = 5 * 1024 * 1024; /* FIXME */
int ttl = 255;
char *saveptr = NULL;
char *addr;
char *tmp;
int required_connections, index;
int recv_port = recv_port_base;
int send_port = send_port_base;
tmp = strdup(addrs);
if(strtok_r(tmp, ",", &saveptr) == NULL) {
free(tmp);
return NULL;
}
else required_connections = 1;
while(strtok_r(NULL, ",", &saveptr) != NULL)
++required_connections;
free(tmp);
tmp = strdup(addrs);
devices = (struct rtp **)
malloc((required_connections + 1) * sizeof(struct rtp *));
for(index = 0, addr = strtok_r(addrs, ",", &saveptr);
index < required_connections;
++index, addr = strtok_r(NULL, ",", &saveptr), recv_port += 2, send_port += 2)
{
/* port + 2 is reserved for audio */
if (recv_port == recv_port_base + 2)
recv_port += 2;
if (send_port == send_port_base + 2)
send_port += 2;
devices[index] = rtp_init(addr, recv_port, send_port, ttl, rtcp_bw,
FALSE, rtp_recv_callback,
(void *)participants);
if (devices[index] != NULL) {
rtp_set_option(devices[index], RTP_OPT_WEAK_VALIDATION,
TRUE);
rtp_set_sdes(devices[index], rtp_my_ssrc(devices[index]),
RTCP_SDES_TOOL,
PACKAGE_STRING, strlen(PACKAGE_STRING));
#ifdef HAVE_MACOSX
rtp_set_recv_buf(devices[index], 5944320);
#else
rtp_set_recv_buf(devices[index], 8*1024*1024);
#endif
pdb_add(participants, rtp_my_ssrc(devices[index]));
}
else {
int index_nest;
for(index_nest = 0; index_nest < index; ++index_nest) {
rtp_done(devices[index_nest]);
}
free(devices);
devices = NULL;
}
}
if(devices != NULL) devices[index] = NULL;
free(tmp);
return devices;
}
static void destroy_devices(struct rtp ** network_devices)
{
struct rtp ** current = network_devices;
if(!network_devices)
return;
while(*current != NULL) {
rtp_done(*current++);
}
free(network_devices);
}
static struct tx *initialize_transmit(unsigned requested_mtu, char *fec)
{
/* Currently this is trivial. It'll get more complex once we */
/* have multiple codecs and/or error correction. */
return tx_init(requested_mtu, fec);
}
static void *ihdtv_reciever_thread(void *arg)
{
ihdtv_connection *connection = (ihdtv_connection *) ((void **)arg)[0];
struct display *display_device = (struct display *)((void **)arg)[1];
while (!should_exit) {
if (ihdtv_recieve
(connection, frame_buffer->tiles[0].data, frame_buffer->tiles[0].data_len))
return 0; // we've got some error. probably empty buffer
display_put_frame(display_device, frame_buffer->tiles[0].data);
frame_buffer = display_get_frame(display_device);
}
return 0;
}
static void *ihdtv_sender_thread(void *arg)
{
ihdtv_connection *connection = (ihdtv_connection *) ((void **)arg)[0];
struct vidcap *capture_device = (struct vidcap *)((void **)arg)[1];
struct video_frame *tx_frame;
struct audio_frame *audio;
while (!should_exit) {
if ((tx_frame = vidcap_grab(capture_device, &audio)) != NULL) {
ihdtv_send(connection, tx_frame, 9000000); // FIXME: fix the use of frame size!!
free(tx_frame);
} else {
fprintf(stderr,
"Error recieving frame from capture device\n");
return 0;
}
}
return 0;
}
static void *receiver_thread(void *arg)
{
struct state_uv *uv = (struct state_uv *)arg;
struct pdb_e *cp;
struct timeval timeout;
int fr;
int ret;
unsigned int tiles_post = 0;
struct timeval last_tile_received = {0, 0};
struct pbuf_video_data pbuf_data;
initialize_video_decompress();
pbuf_data.decoder = decoder_init(uv->decoder_mode, uv->postprocess);
if(!pbuf_data.decoder) {
fprintf(stderr, "Error initializing decoder (incorrect '-M' or '-p' option).\n");
exit_uv(1);
} else {
decoder_register_video_display(pbuf_data.decoder, uv->display_device);
}
pbuf_data.frame_buffer = frame_buffer;
pthread_mutex_unlock(&uv->master_lock);
fr = 1;
while (!should_exit) {
/* Housekeeping and RTCP... */
gettimeofday(&uv->curr_time, NULL);
uv->ts = tv_diff(uv->curr_time, uv->start_time) * 90000;
rtp_update(uv->network_devices[0], uv->curr_time);
rtp_send_ctrl(uv->network_devices[0], uv->ts, 0, uv->curr_time);
/* Receive packets from the network... The timeout is adjusted */
/* to match the video capture rate, so the transmitter works. */
if (fr) {
gettimeofday(&uv->curr_time, NULL);
fr = 0;
}
timeout.tv_sec = 0;
timeout.tv_usec = 999999 / 59.94;
ret = rtp_recv_poll_r(uv->network_devices, &timeout, uv->ts);
/*
if (ret == FALSE) {
printf("Failed to receive data\n");
}
*/
UNUSED(ret);
/* Decode and render for each participant in the conference... */
cp = pdb_iter_init(uv->participants);
while (cp != NULL) {
if (tfrc_feedback_is_due(cp->tfrc_state, uv->curr_time)) {
debug_msg("tfrc rate %f\n",
tfrc_feedback_txrate(cp->tfrc_state,
uv->curr_time));
}
/* Decode and render video... */
if (pbuf_decode
(cp->playout_buffer, uv->curr_time, decode_frame, &pbuf_data)) {
tiles_post++;
/* we have data from all connections we need */
if(tiles_post == uv->connections_count)
{
tiles_post = 0;
gettimeofday(&uv->curr_time, NULL);
fr = 1;
display_put_frame(uv->display_device,
(char *) pbuf_data.frame_buffer);
pbuf_data.frame_buffer =
display_get_frame(uv->display_device);
}
last_tile_received = uv->curr_time;
}
pbuf_remove(cp->playout_buffer, uv->curr_time);
cp = pdb_iter_next(uv->participants);
}
pdb_iter_done(uv->participants);
/* dual-link TIMEOUT - we won't wait for next tiles */
if(tiles_post > 1 && tv_diff(uv->curr_time, last_tile_received) >
999999 / 59.94 / uv->connections_count) {
tiles_post = 0;
gettimeofday(&uv->curr_time, NULL);
fr = 1;
display_put_frame(uv->display_device,
pbuf_data.frame_buffer->tiles[0].data);
pbuf_data.frame_buffer =
display_get_frame(uv->display_device);
last_tile_received = uv->curr_time;
}
}
decoder_destroy(pbuf_data.decoder);
return 0;
}
static void sender_finish(struct state_uv *uv) {
pthread_mutex_lock(&uv->sender_lock);
uv->should_exit_sender = TRUE;
if(uv->sender_waiting) {
uv->has_item_to_send = TRUE;
pthread_cond_signal(&uv->sender_cv);
}
pthread_mutex_unlock(&uv->sender_lock);
}
static void *sender_thread(void *arg) {
struct state_uv *uv = (struct state_uv *)arg;
struct video_frame *splitted_frames = NULL;
int tile_y_count;
tile_y_count = uv->connections_count;
/* we have more than one connection */
if(tile_y_count > 1) {
/* it is simply stripping frame */
splitted_frames = vf_alloc(tile_y_count);
}
while(!uv->should_exit_sender) {
pthread_mutex_lock(&uv->sender_lock);
while(!uv->has_item_to_send && !uv->should_exit_sender) {
uv->sender_waiting = TRUE;
pthread_cond_wait(&uv->sender_cv, &uv->sender_lock);
uv->sender_waiting = FALSE;
}
struct video_frame *tx_frame = uv->tx_frame;
if(uv->should_exit_sender) {
uv->has_item_to_send = FALSE;
pthread_mutex_unlock(&uv->sender_lock);
goto exit;
}
pthread_mutex_unlock(&uv->sender_lock);
if(uv->connections_count == 1) { /* normal case - only one connection */
tx_send(uv->tx, tx_frame,
uv->network_devices[0]);
} else { /* split */
int i;
//assert(frame_count == 1);
vf_split_horizontal(splitted_frames, tx_frame,
tile_y_count);
for (i = 0; i < tile_y_count; ++i) {
tx_send_tile(uv->tx, splitted_frames, i,
uv->network_devices[i]);
}
}
pthread_mutex_lock(&uv->sender_lock);
uv->has_item_to_send = FALSE;
if(uv->compress_thread_waiting) {
pthread_cond_signal(&uv->compress_thread_cv);
}
pthread_mutex_unlock(&uv->sender_lock);
}
exit:
vf_free(splitted_frames);
return NULL;
}
static void *compress_thread(void *arg)
{
struct state_uv *uv = (struct state_uv *)arg;
struct video_frame *tx_frame;
struct audio_frame *audio;
//struct video_frame *splitted_frames = NULL;
pthread_t sender_thread_id;
int i = 0;
struct compress_state *compression;
compression = compress_init(uv->requested_compression);
pthread_mutex_unlock(&uv->master_lock);
/* NOTE: unlock before propagating possible error */
if(compression == NULL) {
fprintf(stderr, "Error initializing compression.\n");
exit_uv(0);
goto compress_done;
}
if (pthread_create
(&sender_thread_id, NULL, sender_thread,
(void *)uv) != 0) {
perror("Unable to create sender thread!\n");
exit_uv(EXIT_FAILURE);
goto join_thread;
}
while (!should_exit) {
/* Capture and transmit video... */
tx_frame = vidcap_grab(uv->capture_device, &audio);
if (tx_frame != NULL) {
if(audio) {
audio_sdi_send(uv->audio, audio);
}
//TODO: Unghetto this
tx_frame = compress_frame(compression, tx_frame, i);
if(!tx_frame)
continue;
i = (i + 1) % 2;
/* when sending uncompressed video, we simply post it for send
* and wait until done */
if(is_compress_none(compression)) {
pthread_mutex_lock(&uv->sender_lock);
uv->tx_frame = tx_frame;
uv->has_item_to_send = TRUE;
if(uv->sender_waiting) {
pthread_cond_signal(&uv->sender_cv);
}
if(should_exit) {
pthread_mutex_unlock(&uv->sender_lock);
goto join_thread;
}
while(uv->has_item_to_send) {
uv->compress_thread_waiting = TRUE;
pthread_cond_wait(&uv->compress_thread_cv, &uv->sender_lock);
uv->compress_thread_waiting = FALSE;
}
pthread_mutex_unlock(&uv->sender_lock);
} else
/* we post for sending (after previous frame is done) and schedule a new one
* frames may overlap then */
{
pthread_mutex_lock(&uv->sender_lock);
if(should_exit) {
pthread_mutex_unlock(&uv->sender_lock);
goto join_thread;
}
while(uv->has_item_to_send) {
uv->compress_thread_waiting = TRUE;
pthread_cond_wait(&uv->compress_thread_cv, &uv->sender_lock);
uv->compress_thread_waiting = FALSE;
}
uv->tx_frame = tx_frame;
uv->has_item_to_send = TRUE;
if(uv->sender_waiting) {
pthread_cond_signal(&uv->sender_cv);
}
pthread_mutex_unlock(&uv->sender_lock);
}
}
}
join_thread:
sender_finish(uv);
pthread_join(sender_thread_id, NULL);
compress_done:
compress_done(compression);
return NULL;
}
int main(int argc, char *argv[])
{
#if defined HAVE_SCHED_SETSCHEDULER && defined USE_RT
struct sched_param sp;
#endif
char *network_device = NULL;
char *capture_cfg = NULL;
char *display_cfg = NULL;
char *audio_recv = NULL;
char *audio_send = NULL;
char *jack_cfg = NULL;
char *requested_fec = NULL;
char *save_ptr = NULL;
char *audio_channel_map = NULL;
char *audio_scale = "mixauto";
bool echo_cancellation = false;
int bitrate = 0;
struct state_uv *uv;
int ch;
pthread_t receiver_thread_id, compress_thread_id,
ihdtv_sender_thread_id;
unsigned vidcap_flags = 0,
display_flags = 0;
#if defined DEBUG && defined HAVE_LINUX
mtrace();
#endif
if (argc == 1) {
usage();
return EXIT_FAIL_USAGE;
}
uv_argc = argc;
uv_argv = argv;
static struct option getopt_options[] = {
{"display", required_argument, 0, 'd'},
{"capture", required_argument, 0, 't'},
{"mtu", required_argument, 0, 'm'},
{"mode", required_argument, 0, 'M'},
{"version", no_argument, 0, 'v'},
{"compress", required_argument, 0, 'c'},
{"ihdtv", no_argument, 0, 'i'},
{"receive", required_argument, 0, 'r'},
{"send", required_argument, 0, 's'},
{"help", no_argument, 0, 'h'},
{"jack", required_argument, 0, 'j'},
{"fec", required_argument, 0, 'f'},
{"port", required_argument, 0, 'P'},
{"limit-bitrate", required_argument, 0, 'l'},
{"audio-channel-map", required_argument, 0, AUDIO_CHANNEL_MAP},
{"audio-scale", required_argument, 0, AUDIO_SCALE},
{"audio-capture-channels", required_argument, 0, AUDIO_CAPTURE_CHANNELS},
{"echo-cancellation", no_argument, 0, ECHO_CANCELLATION},
{"cuda-device", required_argument, 0, CUDA_DEVICE},
{0, 0, 0, 0}
};
int option_index = 0;
// uv = (struct state_uv *) calloc(1, sizeof(struct state_uv));
uv = (struct state_uv *)malloc(sizeof(struct state_uv));
uv_state = uv;
uv->audio = NULL;
uv->ts = 0;
uv->display_device = NULL;
uv->requested_display = "none";
uv->requested_capture = "none";
uv->requested_compression = "none";
uv->decoder_mode = NULL;
uv->postprocess = NULL;
uv->requested_mtu = 0;
uv->use_ihdtv_protocol = 0;
uv->participants = NULL;
uv->tx = NULL;
uv->network_devices = NULL;
uv->recv_port_number =
uv->send_port_number =
PORT_BASE;
pthread_mutex_init(&uv->master_lock, NULL);
uv->has_item_to_send = FALSE;
uv->sender_waiting = FALSE;
uv->compress_thread_waiting = FALSE;
uv->should_exit_sender = FALSE;
pthread_mutex_init(&uv->sender_lock, NULL);
pthread_cond_init(&uv->compress_thread_cv, NULL);
pthread_cond_init(&uv->sender_cv, NULL);
perf_init();
perf_record(UVP_INIT, 0);
init_lib_common();
while ((ch =
getopt_long(argc, argv, "d:t:m:r:s:vc:ihj:M:p:f:P:l:", getopt_options,
&option_index)) != -1) {
switch (ch) {
case 'd':
if (!strcmp(optarg, "help")) {
list_video_display_devices();
return 0;
}
uv->requested_display = strtok_r(optarg, ":", &save_ptr);
if(save_ptr && strlen(save_ptr) > 0)
display_cfg = save_ptr;
break;
case 't':
if (!strcmp(optarg, "help")) {
list_video_capture_devices();
return 0;
}
uv->requested_capture = strtok_r(optarg, ":", &save_ptr);
if(save_ptr && strlen(save_ptr) > 0)
capture_cfg = save_ptr;
break;
case 'm':
uv->requested_mtu = atoi(optarg);
break;
case 'M':
uv->decoder_mode = optarg;
break;
case 'p':
uv->postprocess = optarg;
break;
case 'v':
printf("%s", PACKAGE_STRING);
#ifdef GIT_VERSION
printf(" (rev %s)", GIT_VERSION);
#endif
printf("\n");
return EXIT_SUCCESS;
case 'c':
uv->requested_compression = optarg;
break;
case 'i':
uv->use_ihdtv_protocol = 1;
printf("setting ihdtv protocol\n");
break;
case 'r':
audio_recv = optarg;
break;
case 's':
audio_send = optarg;
break;
case 'j':
jack_cfg = optarg;
break;
case 'f':
requested_fec = optarg;
break;
case 'h':
usage();
return 0;
case 'P':
if(strchr(optarg, ':')) {
char *save_ptr = NULL;
uv->recv_port_number = atoi(strtok_r(optarg, ":", &save_ptr));
uv->send_port_number = atoi(strtok_r(NULL, ":", &save_ptr));
} else {
uv->recv_port_number =
uv->send_port_number =
atoi(optarg);
}
break;
case 'l':
bitrate = atoi(optarg);
if(bitrate <= 0) {
usage();
return EXIT_FAIL_USAGE;
}
break;
case '?':
break;
case AUDIO_CHANNEL_MAP:
audio_channel_map = optarg;
break;
case AUDIO_SCALE:
audio_scale = optarg;
break;
case AUDIO_CAPTURE_CHANNELS:
audio_capture_channels = atoi(optarg);
break;
case ECHO_CANCELLATION:
echo_cancellation = true;
break;
case CUDA_DEVICE:
#ifdef HAVE_CUDA
if(strcmp("help", optarg) == 0) {
printf("\nCUDA devices:\n");
gpujpeg_print_devices_info();
} else {
cuda_device = atoi(optarg);
}
break;
#else
fprintf(stderr, "CUDA support is not enabled!\n");
return EXIT_FAIL_USAGE;
#endif // HAVE_CUDA
default:
usage();
return EXIT_FAIL_USAGE;
}
}
argc -= optind;
argv += optind;
printf("%s", PACKAGE_STRING);
#ifdef GIT_VERSION
printf(" (rev %s)", GIT_VERSION);
#endif
printf("\n");
printf("Display device: %s\n", uv->requested_display);
printf("Capture device: %s\n", uv->requested_capture);
printf("MTU : %d\n", uv->requested_mtu);
printf("Compression : %s\n", uv->requested_compression);
if (uv->use_ihdtv_protocol)
printf("Network protocol: ihdtv\n");
else
printf("Network protocol: ultragrid rtp\n");
gettimeofday(&uv->start_time, NULL);
if(uv->requested_mtu > RTP_MAX_PACKET_LEN) {
fprintf(stderr, "Requested MTU exceeds maximal value allowed by RTP library (%d).\n",
RTP_MAX_PACKET_LEN);
return EXIT_FAIL_USAGE;
}
if (uv->use_ihdtv_protocol) {
if ((argc != 0) && (argc != 1) && (argc != 2)) {
usage();
return EXIT_FAIL_USAGE;
}
} else {
if (argc == 0) {
network_device = strdup("localhost");
} else {
network_device = (char *) argv[0];
}
}
char *tmp_requested_fec = strdup(DEFAULT_AUDIO_FEC);
uv->audio = audio_cfg_init (network_device, uv->recv_port_number + 2, uv->send_port_number + 2, audio_send, audio_recv, jack_cfg,
tmp_requested_fec, audio_channel_map, audio_scale, echo_cancellation);
free(tmp_requested_fec);
if(!uv->audio)
goto cleanup;
vidcap_flags |= audio_get_vidcap_flags(uv->audio);
display_flags |= audio_get_display_flags(uv->audio);
uv->participants = pdb_init();
if ((uv->capture_device =
initialize_video_capture(uv->requested_capture, capture_cfg, vidcap_flags)) == NULL) {
printf("Unable to open capture device: %s\n",
uv->requested_capture);
exit_uv(EXIT_FAIL_CAPTURE);
goto cleanup_wait_audio;
}
printf("Video capture initialized-%s\n", uv->requested_capture);
if ((uv->display_device =
initialize_video_display(uv->requested_display, display_cfg, display_flags)) == NULL) {
printf("Unable to open display device: %s\n",
uv->requested_display);
exit_uv(EXIT_FAIL_DISPLAY);
goto cleanup_wait_capture;
}
printf("Display initialized-%s\n", uv->requested_display);
#ifndef WIN32
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGHUP, signal_handler);
signal(SIGABRT, signal_handler);
#endif
#ifdef USE_RT
#ifdef HAVE_SCHED_SETSCHEDULER
sp.sched_priority = sched_get_priority_max(SCHED_FIFO);
if (sched_setscheduler(0, SCHED_FIFO, &sp) != 0) {
printf("WARNING: Unable to set real-time scheduling\n");
}
#else
printf("WARNING: System does not support real-time scheduling\n");
#endif /* HAVE_SCHED_SETSCHEDULER */
#endif /* USE_RT */
if (uv->use_ihdtv_protocol) {
ihdtv_connection tx_connection, rx_connection;
printf("Initializing ihdtv protocol\n");
// we cannot act as both together, because parameter parsing whould have to be revamped
if ((strcmp("none", uv->requested_display) != 0)
&& (strcmp("none", uv->requested_capture) != 0)) {
printf
("Error: cannot act as both sender and reciever together in ihdtv mode\n");
return -1;
}
void *rx_connection_and_display[] =
{ (void *)&rx_connection, (void *)uv->display_device };
void *tx_connection_and_display[] =
{ (void *)&tx_connection, (void *)uv->capture_device };
if (uv->requested_mtu == 0) // mtu not specified on command line
{
uv->requested_mtu = 8112; // not really a mtu, but a video-data payload per packet
}
if (strcmp("none", uv->requested_display) != 0) {
if (ihdtv_init_rx_session
(&rx_connection, (argc == 0) ? NULL : argv[0],
(argc ==
0) ? NULL : ((argc == 1) ? argv[0] : argv[1]),
3000, 3001, uv->requested_mtu) != 0) {
fprintf(stderr,
"Error initializing reciever session\n");
return 1;
}
if (pthread_create
(&receiver_thread_id, NULL, ihdtv_reciever_thread,
rx_connection_and_display) != 0) {
fprintf(stderr,
"Error creating reciever thread. Quitting\n");
return 1;
}
}
if (strcmp("none", uv->requested_capture) != 0) {
if (argc == 0) {
fprintf(stderr,
"Error: specify the destination address\n");
usage();
return EXIT_FAIL_USAGE;
}
if (ihdtv_init_tx_session
(&tx_connection, argv[0],
(argc == 2) ? argv[1] : argv[0],
uv->requested_mtu) != 0) {
fprintf(stderr,
"Error initializing sender session\n");
return 1;
}
if (pthread_create
(&ihdtv_sender_thread_id, NULL, ihdtv_sender_thread,
tx_connection_and_display) != 0) {
fprintf(stderr,
"Error creating sender thread. Quitting\n");
return 1;
}
}
while (!should_exit)
sleep(1);
} else {
if ((uv->network_devices =
initialize_network(network_device, uv->recv_port_number, uv->send_port_number, uv->participants)) == NULL) {
printf("Unable to open network\n");
exit_uv(EXIT_FAIL_NETWORK);
goto cleanup_wait_display;
} else {
struct rtp **item;
uv->connections_count = 0;
/* only count how many connections has initialize_network opened */
for(item = uv->network_devices; *item != NULL; ++item)
++uv->connections_count;
}
if (uv->requested_mtu == 0) // mtu wasn't specified on the command line
{
uv->requested_mtu = 1500; // the default value for RTP
}
if(bitrate == 0) { // else packet_rate defaults to 13600 or so
bitrate = 6618;
}
packet_rate = 1000 * uv->requested_mtu * 8 / bitrate;
if ((uv->tx = initialize_transmit(uv->requested_mtu, requested_fec)) == NULL) {
printf("Unable to initialize transmitter.\n");
exit_uv(EXIT_FAIL_TRANSMIT);
goto cleanup_wait_display;
}
/* following block only shows help (otherwise initialized in receiver thread */
if((uv->postprocess && strstr(uv->postprocess, "help") != NULL) ||
(uv->decoder_mode && strstr(uv->decoder_mode, "help") != NULL)) {
struct state_decoder *dec = decoder_init(uv->decoder_mode, uv->postprocess);
decoder_destroy(dec);
exit_uv(EXIT_SUCCESS);
goto cleanup_wait_display;
}
/* following block only shows help (otherwise initialized in sender thread */
if(strstr(uv->requested_compression,"help") != NULL) {
struct compress_state *compression = compress_init(uv->requested_compression);
compress_done(compression);
exit_uv(EXIT_SUCCESS);
goto cleanup_wait_display;
}
if (strcmp("none", uv->requested_display) != 0) {
pthread_mutex_lock(&uv->master_lock);
if (pthread_create
(&receiver_thread_id, NULL, receiver_thread,
(void *)uv) != 0) {
perror("Unable to create display thread!\n");
exit_uv(EXIT_FAILURE);
goto cleanup_wait_display;
}
}
if (strcmp("none", uv->requested_capture) != 0) {
pthread_mutex_lock(&uv->master_lock);
if (pthread_create
(&compress_thread_id, NULL, compress_thread,
(void *)uv) != 0) {
perror("Unable to create capture thread!\n");
exit_uv(EXIT_FAILURE);
goto cleanup_wait_capture;
}
}
}
pthread_mutex_lock(&uv->master_lock);
if(audio_get_display_flags(uv->audio)) {
audio_register_get_callback(uv->audio, (struct audio_frame * (*)(void *)) display_get_audio_frame, uv->display_device);
audio_register_put_callback(uv->audio, (void (*)(void *, struct audio_frame *)) display_put_audio_frame, uv->display_device);
audio_register_reconfigure_callback(uv->audio, (int (*)(void *, int, int,
int)) display_reconfigure_audio, uv->display_device);
}
if (strcmp("none", uv->requested_display) != 0)
display_run(uv->display_device);
cleanup_wait_display:
if (strcmp("none", uv->requested_display) != 0)
pthread_join(receiver_thread_id, NULL);
cleanup_wait_capture:
if (strcmp("none", uv->requested_capture) != 0)
pthread_join(uv->use_ihdtv_protocol ?
ihdtv_sender_thread_id :
compress_thread_id,
NULL);
cleanup_wait_audio:
/* also wait for audio threads */
audio_join(uv->audio);
cleanup:
while(wait_to_finish)
;
threads_joined = TRUE;
if(uv->audio)
audio_done(uv->audio);
if(uv->tx)
tx_done(uv->tx);
if(uv->network_devices)
destroy_devices(uv->network_devices);
if(uv->capture_device)
vidcap_done(uv->capture_device);
if(uv->display_device)
display_done(uv->display_device);
if (uv->participants != NULL)
pdb_destroy(&uv->participants);
pthread_mutex_destroy(&uv->sender_lock);
pthread_cond_destroy(&uv->compress_thread_cv);
pthread_cond_destroy(&uv->sender_cv);
pthread_mutex_unlock(&uv->master_lock);
pthread_mutex_destroy(&uv->master_lock);
free(uv);
lib_common_done();
#if defined DEBUG && defined HAVE_LINUX
muntrace();
#endif
printf("Exit\n");
return exit_status;
}