1 /*
2 * SRT - Secure, Reliable, Transport
3 * Copyright (c) 2018 Haivision Systems Inc.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 */
10
11 /*****************************************************************************
12 written by
13 Haivision Systems Inc.
14 *****************************************************************************/
15 #ifdef _WIN32
16 #include <direct.h>
17 #endif
18 #include <iostream>
19 #include <iterator>
20 #include <vector>
21 #include <map>
22 #include <stdexcept>
23 #include <string>
24 #include <csignal>
25 #include <thread>
26 #include <chrono>
27 #include <cassert>
28 #include <sys/stat.h>
29 #include <srt.h>
30 #include <udt.h>
31 #include <common.h>
32
33 #include "apputil.hpp"
34 #include "uriparser.hpp"
35 #include "logsupport.hpp"
36 #include "socketoptions.hpp"
37 #include "transmitmedia.hpp"
38 #include "verbose.hpp"
39
40
41 #ifndef S_ISDIR
42 #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
43 #endif
44
45
46 using namespace std;
47
48 static bool interrupt = false;
OnINT_ForceExit(int)49 void OnINT_ForceExit(int)
50 {
51 Verb() << "\n-------- REQUESTED INTERRUPT!\n";
52 interrupt = true;
53 }
54
55
56 struct FileTransmitConfig
57 {
58 unsigned long chunk_size;
59 bool skip_flushing;
60 bool quiet = false;
61 srt_logging::LogLevel::type loglevel = srt_logging::LogLevel::error;
62 set<srt_logging::LogFA> logfas;
63 string logfile;
64 int bw_report = 0;
65 int stats_report = 0;
66 string stats_out;
67 SrtStatsPrintFormat stats_pf = SRTSTATS_PROFMAT_2COLS;
68 bool full_stats = false;
69
70 string source;
71 string target;
72 };
73
74
PrintOptionHelp(const set<string> & opt_names,const string & value,const string & desc)75 void PrintOptionHelp(const set<string> &opt_names, const string &value, const string &desc)
76 {
77 cerr << "\t";
78 int i = 0;
79 for (auto opt : opt_names)
80 {
81 if (i++) cerr << ", ";
82 cerr << "-" << opt;
83 }
84
85 if (!value.empty())
86 cerr << ":" << value;
87 cerr << "\t- " << desc << "\n";
88 }
89
90
parse_args(FileTransmitConfig & cfg,int argc,char ** argv)91 int parse_args(FileTransmitConfig &cfg, int argc, char** argv)
92 {
93 const OptionName
94 o_chunk = { "c", "chunk" },
95 o_no_flush = { "sf", "skipflush" },
96 o_bwreport = { "r", "bwreport", "report", "bandwidth-report", "bitrate-report" },
97 o_statsrep = { "s", "stats", "stats-report-frequency" },
98 o_statsout = { "statsout" },
99 o_statspf = { "pf", "statspf" },
100 o_statsfull = { "f", "fullstats" },
101 o_loglevel = { "ll", "loglevel" },
102 o_logfa = { "logfa" },
103 o_logfile = { "logfile" },
104 o_quiet = { "q", "quiet" },
105 o_verbose = { "v", "verbose" },
106 o_help = { "h", "help" },
107 o_version = { "version" };
108
109 const vector<OptionScheme> optargs = {
110 { o_chunk, OptionScheme::ARG_ONE },
111 { o_no_flush, OptionScheme::ARG_NONE },
112 { o_bwreport, OptionScheme::ARG_ONE },
113 { o_statsrep, OptionScheme::ARG_ONE },
114 { o_statsout, OptionScheme::ARG_ONE },
115 { o_statspf, OptionScheme::ARG_ONE },
116 { o_statsfull, OptionScheme::ARG_NONE },
117 { o_loglevel, OptionScheme::ARG_ONE },
118 { o_logfa, OptionScheme::ARG_ONE },
119 { o_logfile, OptionScheme::ARG_ONE },
120 { o_quiet, OptionScheme::ARG_NONE },
121 { o_verbose, OptionScheme::ARG_NONE },
122 { o_help, OptionScheme::ARG_NONE },
123 { o_version, OptionScheme::ARG_NONE }
124 };
125
126 options_t params = ProcessOptions(argv, argc, optargs);
127
128 bool print_help = Option<OutBool>(params, false, o_help);
129 const bool print_version = Option<OutBool>(params, false, o_version);
130
131 if (params[""].size() != 2 && !print_help && !print_version)
132 {
133 cerr << "ERROR. Invalid syntax. Specify source and target URIs.\n";
134 if (params[""].size() > 0)
135 {
136 cerr << "The following options are passed without a key: ";
137 copy(params[""].begin(), params[""].end(), ostream_iterator<string>(cerr, ", "));
138 cerr << endl;
139 }
140 print_help = true; // Enable help to print it further
141 }
142
143
144 if (print_help)
145 {
146 cout << "SRT sample application to transmit files.\n";
147 cerr << "Built with SRT Library version: " << SRT_VERSION << endl;
148 const uint32_t srtver = srt_getversion();
149 const int major = srtver / 0x10000;
150 const int minor = (srtver / 0x100) % 0x100;
151 const int patch = srtver % 0x100;
152 cerr << "SRT Library version: " << major << "." << minor << "." << patch << endl;
153 cerr << "Usage: srt-file-transmit [options] <input-uri> <output-uri>\n";
154 cerr << "\n";
155
156 PrintOptionHelp(o_chunk, "<chunk=1456>", "max size of data read in one step");
157 PrintOptionHelp(o_no_flush, "", "skip output file flushing");
158 PrintOptionHelp(o_bwreport, "<every_n_packets=0>", "bandwidth report frequency");
159 PrintOptionHelp(o_statsrep, "<every_n_packets=0>", "frequency of status report");
160 PrintOptionHelp(o_statsout, "<filename>", "output stats to file");
161 PrintOptionHelp(o_statspf, "<format=default>", "stats printing format [json|csv|default]");
162 PrintOptionHelp(o_statsfull, "", "full counters in stats-report (prints total statistics)");
163 PrintOptionHelp(o_loglevel, "<level=error>", "log level [fatal,error,info,note,warning]");
164 PrintOptionHelp(o_logfa, "<fas=general,...>", "log functional area [all,general,bstats,control,data,tsbpd,rexmit]");
165 PrintOptionHelp(o_logfile, "<filename="">", "write logs to file");
166 PrintOptionHelp(o_quiet, "", "quiet mode (default off)");
167 PrintOptionHelp(o_verbose, "", "verbose mode (default off)");
168 cerr << "\n";
169 cerr << "\t-h,-help - show this help\n";
170 cerr << "\t-version - print SRT library version\n";
171 cerr << "\n";
172 cerr << "\t<input-uri> - URI specifying a medium to read from\n";
173 cerr << "\t<output-uri> - URI specifying a medium to write to\n";
174 cerr << "URI syntax: SCHEME://HOST:PORT/PATH?PARAM1=VALUE&PARAM2=VALUE...\n";
175 cerr << "Supported schemes:\n";
176 cerr << "\tsrt: use HOST, PORT, and PARAM for setting socket options\n";
177 cerr << "\tudp: use HOST, PORT and PARAM for some UDP specific settings\n";
178 cerr << "\tfile: file URI or file://con to use stdin or stdout\n";
179
180 return 2;
181 }
182
183 if (Option<OutBool>(params, false, o_version))
184 {
185 cerr << "SRT Library version: " << SRT_VERSION << endl;
186 return 2;
187 }
188
189 cfg.chunk_size = stoul(Option<OutString>(params, "1456", o_chunk));
190 cfg.skip_flushing = Option<OutBool>(params, false, o_no_flush);
191 cfg.bw_report = stoi(Option<OutString>(params, "0", o_bwreport));
192 cfg.stats_report = stoi(Option<OutString>(params, "0", o_statsrep));
193 cfg.stats_out = Option<OutString>(params, "", o_statsout);
194 const string pf = Option<OutString>(params, "default", o_statspf);
195 if (pf == "default")
196 {
197 cfg.stats_pf = SRTSTATS_PROFMAT_2COLS;
198 }
199 else if (pf == "json")
200 {
201 cfg.stats_pf = SRTSTATS_PROFMAT_JSON;
202 }
203 else if (pf == "csv")
204 {
205 cfg.stats_pf = SRTSTATS_PROFMAT_CSV;
206 }
207 else
208 {
209 cfg.stats_pf = SRTSTATS_PROFMAT_2COLS;
210 cerr << "ERROR: Unsupported print format: " << pf << endl;
211 return 1;
212 }
213
214 cfg.full_stats = Option<OutBool>(params, false, o_statsfull);
215 cfg.loglevel = SrtParseLogLevel(Option<OutString>(params, "error", o_loglevel));
216 cfg.logfas = SrtParseLogFA(Option<OutString>(params, "", o_logfa));
217 cfg.logfile = Option<OutString>(params, "", o_logfile);
218 cfg.quiet = Option<OutBool>(params, false, o_quiet);
219
220 if (Option<OutBool>(params, false, o_verbose))
221 Verbose::on = !cfg.quiet;
222
223 cfg.source = params[""].at(0);
224 cfg.target = params[""].at(1);
225
226 return 0;
227 }
228
229
ExtractPath(string path,string & w_dir,string & w_fname)230 void ExtractPath(string path, string& w_dir, string& w_fname)
231 {
232 string directory = path;
233 string filename = "";
234
235 struct stat state;
236 stat(path.c_str(), &state);
237
238 if (!S_ISDIR(state.st_mode))
239 {
240 // Extract directory as a butlast part of path
241 size_t pos = path.find_last_of("/");
242 if ( pos == string::npos )
243 {
244 filename = path;
245 directory = ".";
246 }
247 else
248 {
249 directory = path.substr(0, pos);
250 filename = path.substr(pos+1);
251 }
252 }
253
254 if (directory[0] != '/')
255 {
256 // Glue in the absolute prefix of the current directory
257 // to make it absolute. This is needed to properly interpret
258 // the fixed uri.
259 static const size_t s_max_path = 4096; // don't care how proper this is
260 char tmppath[s_max_path];
261 #ifdef _WIN32
262 const char* gwd = _getcwd(tmppath, s_max_path);
263 #else
264 const char* gwd = getcwd(tmppath, s_max_path);
265 #endif
266 if ( !gwd )
267 {
268 // Don't bother with that now. We need something better for
269 // that anyway.
270 throw std::invalid_argument("Path too long");
271 }
272 const string wd = gwd;
273
274 directory = wd + "/" + directory;
275 }
276
277 w_dir = directory;
278 w_fname = filename;
279 }
280
DoUpload(UriParser & ut,string path,string filename,const FileTransmitConfig & cfg,std::ostream & out_stats)281 bool DoUpload(UriParser& ut, string path, string filename,
282 const FileTransmitConfig &cfg, std::ostream &out_stats)
283 {
284 bool result = false;
285 unique_ptr<Target> tar;
286 SRTSOCKET s = SRT_INVALID_SOCK;
287 bool connected = false;
288 int pollid = -1;
289
290 ifstream ifile(path, ios::binary);
291 if ( !ifile )
292 {
293 cerr << "Error opening file: '" << path << "'";
294 goto exit;
295 }
296
297 pollid = srt_epoll_create();
298 if ( pollid < 0 )
299 {
300 cerr << "Can't initialize epoll";
301 goto exit;
302 }
303
304
305 while (!interrupt)
306 {
307 if (!tar.get())
308 {
309 tar = Target::Create(ut.makeUri());
310 if (!tar.get())
311 {
312 cerr << "Unsupported target type: " << ut.uri() << endl;
313 goto exit;
314 }
315
316 int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR;
317 if (srt_epoll_add_usock(pollid,
318 tar->GetSRTSocket(), &events))
319 {
320 cerr << "Failed to add SRT destination to poll, "
321 << tar->GetSRTSocket() << endl;
322 goto exit;
323 }
324 srt::setstreamid(tar->GetSRTSocket(), filename);
325 }
326
327 s = tar->GetSRTSocket();
328 assert(s != SRT_INVALID_SOCK);
329
330 SRTSOCKET efd;
331 int efdlen = 1;
332 if (srt_epoll_wait(pollid,
333 0, 0, &efd, &efdlen,
334 100, nullptr, nullptr, 0, 0) < 0)
335 {
336 continue;
337 }
338
339 assert(efd == s);
340 assert(efdlen == 1);
341
342 SRT_SOCKSTATUS status = srt_getsockstate(s);
343
344 switch (status)
345 {
346 case SRTS_LISTENING:
347 {
348 if (!tar->AcceptNewClient())
349 {
350 cerr << "Failed to accept SRT connection" << endl;
351 goto exit;
352 }
353
354 srt_epoll_remove_usock(pollid, s);
355
356 s = tar->GetSRTSocket();
357 int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR;
358 if (srt_epoll_add_usock(pollid, s, &events))
359 {
360 cerr << "Failed to add SRT client to poll" << endl;
361 goto exit;
362 }
363 cerr << "Target connected (listener)" << endl;
364 connected = true;
365 }
366 break;
367 case SRTS_CONNECTED:
368 {
369 if (!connected)
370 {
371 cerr << "Target connected (caller)" << endl;
372 connected = true;
373 }
374 }
375 break;
376 case SRTS_BROKEN:
377 case SRTS_NONEXIST:
378 case SRTS_CLOSED:
379 {
380 cerr << "Target disconnected" << endl;
381 goto exit;
382 }
383 default:
384 {
385 // No-Op
386 }
387 break;
388 }
389
390 if (connected)
391 {
392 vector<char> buf(cfg.chunk_size);
393 size_t n = ifile.read(buf.data(), cfg.chunk_size).gcount();
394 size_t shift = 0;
395 while (n > 0)
396 {
397 int st = tar->Write(buf.data() + shift, n, 0, out_stats);
398 Verb() << "Upload: " << n << " --> " << st
399 << (!shift ? string() : "+" + Sprint(shift));
400 if (st == SRT_ERROR)
401 {
402 cerr << "Upload: SRT error: " << srt_getlasterror_str()
403 << endl;
404 goto exit;
405 }
406
407 n -= st;
408 shift += st;
409 }
410
411 if (ifile.eof())
412 {
413 cerr << "File sent" << endl;
414 result = true;
415 break;
416 }
417
418 if ( !ifile.good() )
419 {
420 cerr << "ERROR while reading file\n";
421 goto exit;
422 }
423
424 }
425 }
426
427 if (result && !cfg.skip_flushing)
428 {
429 assert(s != SRT_INVALID_SOCK);
430
431 // send-flush-loop
432 result = false;
433 while (!interrupt)
434 {
435 size_t bytes;
436 size_t blocks;
437 int st = srt_getsndbuffer(s, &blocks, &bytes);
438 if (st == SRT_ERROR)
439 {
440 cerr << "Error in srt_getsndbuffer: " << srt_getlasterror_str()
441 << endl;
442 goto exit;
443 }
444 if (bytes == 0)
445 {
446 cerr << "Buffers flushed" << endl;
447 result = true;
448 break;
449 }
450 Verb() << "Sending buffer still: bytes=" << bytes << " blocks="
451 << blocks;
452 srt::sync::this_thread::sleep_for(srt::sync::milliseconds_from(250));
453 }
454 }
455
456 exit:
457 if (pollid >= 0)
458 {
459 srt_epoll_release(pollid);
460 }
461
462 return result;
463 }
464
DoDownload(UriParser & us,string directory,string filename,const FileTransmitConfig & cfg,std::ostream & out_stats)465 bool DoDownload(UriParser& us, string directory, string filename,
466 const FileTransmitConfig &cfg, std::ostream &out_stats)
467 {
468 bool result = false;
469 unique_ptr<Source> src;
470 SRTSOCKET s = SRT_INVALID_SOCK;
471 bool connected = false;
472 int pollid = -1;
473 string id;
474 ofstream ofile;
475 SRT_SOCKSTATUS status;
476 SRTSOCKET efd;
477 int efdlen = 1;
478
479 pollid = srt_epoll_create();
480 if ( pollid < 0 )
481 {
482 cerr << "Can't initialize epoll";
483 goto exit;
484 }
485
486 while (!interrupt)
487 {
488 if (!src.get())
489 {
490 src = Source::Create(us.makeUri());
491 if (!src.get())
492 {
493 cerr << "Unsupported source type: " << us.uri() << endl;
494 goto exit;
495 }
496
497 int events = SRT_EPOLL_IN | SRT_EPOLL_ERR;
498 if (srt_epoll_add_usock(pollid,
499 src->GetSRTSocket(), &events))
500 {
501 cerr << "Failed to add SRT source to poll, "
502 << src->GetSRTSocket() << endl;
503 goto exit;
504 }
505 }
506
507 s = src->GetSRTSocket();
508 assert(s != SRT_INVALID_SOCK);
509
510 if (srt_epoll_wait(pollid,
511 &efd, &efdlen, 0, 0,
512 100, nullptr, nullptr, 0, 0) < 0)
513 {
514 continue;
515 }
516
517 assert(efd == s);
518 assert(efdlen == 1);
519
520 status = srt_getsockstate(s);
521 Verb() << "Event with status " << status << "\n";
522
523 switch (status)
524 {
525 case SRTS_LISTENING:
526 {
527 if (!src->AcceptNewClient())
528 {
529 cerr << "Failed to accept SRT connection" << endl;
530 goto exit;
531 }
532
533 srt_epoll_remove_usock(pollid, s);
534
535 s = src->GetSRTSocket();
536 int events = SRT_EPOLL_IN | SRT_EPOLL_ERR;
537 if (srt_epoll_add_usock(pollid, s, &events))
538 {
539 cerr << "Failed to add SRT client to poll" << endl;
540 goto exit;
541 }
542 id = srt::getstreamid(s);
543 cerr << "Source connected (listener), id ["
544 << id << "]" << endl;
545 connected = true;
546 continue;
547 }
548 break;
549 case SRTS_CONNECTED:
550 {
551 if (!connected)
552 {
553 id = srt::getstreamid(s);
554 cerr << "Source connected (caller), id ["
555 << id << "]" << endl;
556 connected = true;
557 }
558 }
559 break;
560
561 // No need to do any special action in case of broken.
562 // The app will just try to read and in worst case it will
563 // get an error.
564 case SRTS_BROKEN:
565 cerr << "Connection closed, reading buffer remains\n";
566 break;
567
568 case SRTS_NONEXIST:
569 case SRTS_CLOSED:
570 {
571 cerr << "Source disconnected" << endl;
572 goto exit;
573 }
574 break;
575 default:
576 {
577 // No-Op
578 }
579 break;
580 }
581
582 if (connected)
583 {
584 MediaPacket packet(cfg.chunk_size);
585
586 if (!ofile.is_open())
587 {
588 const char * fn = id.empty() ? filename.c_str() : id.c_str();
589 directory.append("/");
590 directory.append(fn);
591 ofile.open(directory.c_str(), ios::out | ios::trunc | ios::binary);
592
593 if (!ofile.is_open())
594 {
595 cerr << "Error opening file [" << directory << "]" << endl;
596 goto exit;
597 }
598 cerr << "Writing output to [" << directory << "]" << endl;
599 }
600
601 int n = src->Read(cfg.chunk_size, packet, out_stats);
602 if (n == SRT_ERROR)
603 {
604 cerr << "Download: SRT error: " << srt_getlasterror_str() << endl;
605 goto exit;
606 }
607
608 if (n == 0)
609 {
610 result = true;
611 cerr << "Download COMPLETE.\n";
612 break;
613 }
614
615 // Write to file any amount of data received
616 Verb() << "Download: --> " << n;
617 ofile.write(packet.payload.data(), n);
618 if (!ofile.good())
619 {
620 cerr << "Error writing file" << endl;
621 goto exit;
622 }
623
624 }
625 }
626
627 exit:
628 if (pollid >= 0)
629 {
630 srt_epoll_release(pollid);
631 }
632
633 return result;
634 }
635
Upload(UriParser & srt_target_uri,UriParser & fileuri,const FileTransmitConfig & cfg,std::ostream & out_stats)636 bool Upload(UriParser& srt_target_uri, UriParser& fileuri,
637 const FileTransmitConfig &cfg, std::ostream &out_stats)
638 {
639 if ( fileuri.scheme() != "file" )
640 {
641 cerr << "Upload: source accepted only as a file\n";
642 return false;
643 }
644 // fileuri is source-reading file
645 // srt_target_uri is SRT target
646
647 string path = fileuri.path();
648 string directory, filename;
649 ExtractPath(path, (directory), (filename));
650 Verb() << "Extract path '" << path << "': directory=" << directory << " filename=" << filename;
651 // Set ID to the filename.
652 // Directory will be preserved.
653
654 // Add some extra parameters.
655 srt_target_uri["transtype"] = "file";
656
657 return DoUpload(srt_target_uri, path, filename, cfg, out_stats);
658 }
659
Download(UriParser & srt_source_uri,UriParser & fileuri,const FileTransmitConfig & cfg,std::ostream & out_stats)660 bool Download(UriParser& srt_source_uri, UriParser& fileuri,
661 const FileTransmitConfig &cfg, std::ostream &out_stats)
662 {
663 if (fileuri.scheme() != "file" )
664 {
665 cerr << "Download: target accepted only as a file\n";
666 return false;
667 }
668
669 string path = fileuri.path(), directory, filename;
670 ExtractPath(path, (directory), (filename));
671 Verb() << "Extract path '" << path << "': directory=" << directory << " filename=" << filename;
672
673 // Add some extra parameters.
674 srt_source_uri["transtype"] = "file";
675
676 return DoDownload(srt_source_uri, directory, filename, cfg, out_stats);
677 }
678
679
main(int argc,char ** argv)680 int main(int argc, char** argv)
681 {
682 FileTransmitConfig cfg;
683 const int parse_ret = parse_args(cfg, argc, argv);
684 if (parse_ret != 0)
685 return parse_ret == 1 ? EXIT_FAILURE : 0;
686
687 //
688 // Set global config variables
689 //
690 if (cfg.chunk_size != SRT_LIVE_MAX_PLSIZE)
691 transmit_chunk_size = cfg.chunk_size;
692 transmit_stats_writer = SrtStatsWriterFactory(cfg.stats_pf);
693 transmit_bw_report = cfg.bw_report;
694 transmit_stats_report = cfg.stats_report;
695 transmit_total_stats = cfg.full_stats;
696
697 //
698 // Set SRT log levels and functional areas
699 //
700 srt_setloglevel(cfg.loglevel);
701 for (set<srt_logging::LogFA>::iterator i = cfg.logfas.begin(); i != cfg.logfas.end(); ++i)
702 srt_addlogfa(*i);
703
704 //
705 // SRT log handler
706 //
707 std::ofstream logfile_stream; // leave unused if not set
708 if (!cfg.logfile.empty())
709 {
710 logfile_stream.open(cfg.logfile.c_str());
711 if (!logfile_stream)
712 {
713 cerr << "ERROR: Can't open '" << cfg.logfile.c_str() << "' for writing - fallback to cerr\n";
714 }
715 else
716 {
717 srt::setlogstream(logfile_stream);
718 }
719 }
720
721 //
722 // SRT stats output
723 //
724 std::ofstream logfile_stats; // leave unused if not set
725 if (cfg.stats_out != "" && cfg.stats_out != "stdout")
726 {
727 logfile_stats.open(cfg.stats_out.c_str());
728 if (!logfile_stats)
729 {
730 cerr << "ERROR: Can't open '" << cfg.stats_out << "' for writing stats. Fallback to stdout.\n";
731 return 1;
732 }
733 }
734 else if (cfg.bw_report != 0 || cfg.stats_report != 0)
735 {
736 g_stats_are_printed_to_stdout = true;
737 }
738
739 ostream &out_stats = logfile_stats.is_open() ? logfile_stats : cout;
740
741 // File transmission code
742
743 UriParser us(cfg.source), ut(cfg.target);
744
745 Verb() << "SOURCE type=" << us.scheme() << ", TARGET type=" << ut.scheme();
746
747 signal(SIGINT, OnINT_ForceExit);
748 signal(SIGTERM, OnINT_ForceExit);
749
750 try
751 {
752 if (us.scheme() == "srt")
753 {
754 if (ut.scheme() != "file")
755 {
756 cerr << "SRT to FILE should be specified\n";
757 return 1;
758 }
759 Download(us, ut, cfg, out_stats);
760 }
761 else if (ut.scheme() == "srt")
762 {
763 if (us.scheme() != "file")
764 {
765 cerr << "FILE to SRT should be specified\n";
766 return 1;
767 }
768 Upload(ut, us, cfg, out_stats);
769 }
770 else
771 {
772 cerr << "SRT URI must be one of given media.\n";
773 return 1;
774 }
775 }
776 catch (std::exception& x)
777 {
778 cerr << "ERROR: " << x.what() << endl;
779 return 1;
780 }
781
782
783 return 0;
784 }
785