1*3f982cf4SFabien Sanglard // Copyright 2020 The Chromium Authors. All rights reserved.
2*3f982cf4SFabien Sanglard // Use of this source code is governed by a BSD-style license that can be
3*3f982cf4SFabien Sanglard // found in the LICENSE file.
4*3f982cf4SFabien Sanglard
5*3f982cf4SFabien Sanglard #include "platform/impl/logging.h"
6*3f982cf4SFabien Sanglard
7*3f982cf4SFabien Sanglard #if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS)
8*3f982cf4SFabien Sanglard #include <getopt.h>
9*3f982cf4SFabien Sanglard
10*3f982cf4SFabien Sanglard #include <cinttypes>
11*3f982cf4SFabien Sanglard #include <cstdio>
12*3f982cf4SFabien Sanglard #include <cstring>
13*3f982cf4SFabien Sanglard #include <iostream>
14*3f982cf4SFabien Sanglard #include <sstream>
15*3f982cf4SFabien Sanglard #include <vector>
16*3f982cf4SFabien Sanglard
17*3f982cf4SFabien Sanglard #include "cast/common/certificate/cast_trust_store.h"
18*3f982cf4SFabien Sanglard #include "cast/standalone_sender/constants.h"
19*3f982cf4SFabien Sanglard #include "cast/standalone_sender/looping_file_cast_agent.h"
20*3f982cf4SFabien Sanglard #include "cast/standalone_sender/receiver_chooser.h"
21*3f982cf4SFabien Sanglard #include "cast/streaming/constants.h"
22*3f982cf4SFabien Sanglard #include "platform/api/network_interface.h"
23*3f982cf4SFabien Sanglard #include "platform/api/time.h"
24*3f982cf4SFabien Sanglard #include "platform/base/error.h"
25*3f982cf4SFabien Sanglard #include "platform/base/ip_address.h"
26*3f982cf4SFabien Sanglard #include "platform/impl/network_interface.h"
27*3f982cf4SFabien Sanglard #include "platform/impl/platform_client_posix.h"
28*3f982cf4SFabien Sanglard #include "platform/impl/task_runner.h"
29*3f982cf4SFabien Sanglard #include "platform/impl/text_trace_logging_platform.h"
30*3f982cf4SFabien Sanglard #include "util/chrono_helpers.h"
31*3f982cf4SFabien Sanglard #include "util/stringprintf.h"
32*3f982cf4SFabien Sanglard
33*3f982cf4SFabien Sanglard namespace openscreen {
34*3f982cf4SFabien Sanglard namespace cast {
35*3f982cf4SFabien Sanglard namespace {
36*3f982cf4SFabien Sanglard
LogUsage(const char * argv0)37*3f982cf4SFabien Sanglard void LogUsage(const char* argv0) {
38*3f982cf4SFabien Sanglard constexpr char kTemplate[] = R"(
39*3f982cf4SFabien Sanglard usage: %s <options> network_interface media_file
40*3f982cf4SFabien Sanglard
41*3f982cf4SFabien Sanglard or
42*3f982cf4SFabien Sanglard
43*3f982cf4SFabien Sanglard usage: %s <options> addr[:port] media_file
44*3f982cf4SFabien Sanglard
45*3f982cf4SFabien Sanglard The first form runs this application in discovery+interactive mode. It will
46*3f982cf4SFabien Sanglard scan for Cast Receivers on the LAN reachable from the given network
47*3f982cf4SFabien Sanglard interface, and then the user will choose one interactively via a menu on the
48*3f982cf4SFabien Sanglard console.
49*3f982cf4SFabien Sanglard
50*3f982cf4SFabien Sanglard The second form runs this application in direct mode. It will not attempt to
51*3f982cf4SFabien Sanglard discover Cast Receivers, and instead connect directly to the Cast Receiver at
52*3f982cf4SFabien Sanglard addr:[port] (e.g., 192.168.1.22, 192.168.1.22:%d or [::1]:%d).
53*3f982cf4SFabien Sanglard
54*3f982cf4SFabien Sanglard -m, --max-bitrate=N
55*3f982cf4SFabien Sanglard Specifies the maximum bits per second for the media streams.
56*3f982cf4SFabien Sanglard
57*3f982cf4SFabien Sanglard Default if not set: %d
58*3f982cf4SFabien Sanglard
59*3f982cf4SFabien Sanglard -n, --no-looping
60*3f982cf4SFabien Sanglard Disable looping the passed in video after it finishes playing.
61*3f982cf4SFabien Sanglard
62*3f982cf4SFabien Sanglard )"
63*3f982cf4SFabien Sanglard #if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
64*3f982cf4SFabien Sanglard R"(
65*3f982cf4SFabien Sanglard -d, --developer-certificate=path-to-cert
66*3f982cf4SFabien Sanglard Specifies the path to a self-signed developer certificate that will
67*3f982cf4SFabien Sanglard be permitted for use as a root CA certificate for receivers that
68*3f982cf4SFabien Sanglard this sender instance will connect to. If omitted, only connections to
69*3f982cf4SFabien Sanglard receivers using an official Google-signed cast certificate chain will
70*3f982cf4SFabien Sanglard be permitted.
71*3f982cf4SFabien Sanglard )"
72*3f982cf4SFabien Sanglard #endif
73*3f982cf4SFabien Sanglard R"(
74*3f982cf4SFabien Sanglard -a, --android-hack:
75*3f982cf4SFabien Sanglard Use the wrong RTP payload types, for compatibility with older Android
76*3f982cf4SFabien Sanglard TV receivers. See https://crbug.com/631828.
77*3f982cf4SFabien Sanglard
78*3f982cf4SFabien Sanglard -r, --remoting: Enable remoting content instead of mirroring.
79*3f982cf4SFabien Sanglard
80*3f982cf4SFabien Sanglard -t, --tracing: Enable performance tracing logging.
81*3f982cf4SFabien Sanglard
82*3f982cf4SFabien Sanglard -v, --verbose: Enable verbose logging.
83*3f982cf4SFabien Sanglard
84*3f982cf4SFabien Sanglard -h, --help: Show this help message.
85*3f982cf4SFabien Sanglard
86*3f982cf4SFabien Sanglard -c, --codec: Specifies the video codec to be used. Can be one of:
87*3f982cf4SFabien Sanglard vp8, vp9, av1. Defaults to vp8 if not specified.
88*3f982cf4SFabien Sanglard )";
89*3f982cf4SFabien Sanglard
90*3f982cf4SFabien Sanglard std::cerr << StringPrintf(kTemplate, argv0, argv0, kDefaultCastPort,
91*3f982cf4SFabien Sanglard kDefaultCastPort, kDefaultMaxBitrate);
92*3f982cf4SFabien Sanglard }
93*3f982cf4SFabien Sanglard
94*3f982cf4SFabien Sanglard // Attempts to parse |string_form| into an IPEndpoint. The format is a
95*3f982cf4SFabien Sanglard // standard-format IPv4 or IPv6 address followed by an optional colon and port.
96*3f982cf4SFabien Sanglard // If the port is not provided, kDefaultCastPort is assumed.
97*3f982cf4SFabien Sanglard //
98*3f982cf4SFabien Sanglard // If the parse fails, a zero-port IPEndpoint is returned.
ParseAsEndpoint(const char * string_form)99*3f982cf4SFabien Sanglard IPEndpoint ParseAsEndpoint(const char* string_form) {
100*3f982cf4SFabien Sanglard IPEndpoint result{};
101*3f982cf4SFabien Sanglard const ErrorOr<IPEndpoint> parsed_endpoint = IPEndpoint::Parse(string_form);
102*3f982cf4SFabien Sanglard if (parsed_endpoint.is_value()) {
103*3f982cf4SFabien Sanglard result = parsed_endpoint.value();
104*3f982cf4SFabien Sanglard } else {
105*3f982cf4SFabien Sanglard const ErrorOr<IPAddress> parsed_address = IPAddress::Parse(string_form);
106*3f982cf4SFabien Sanglard if (parsed_address.is_value()) {
107*3f982cf4SFabien Sanglard result = {parsed_address.value(), kDefaultCastPort};
108*3f982cf4SFabien Sanglard }
109*3f982cf4SFabien Sanglard }
110*3f982cf4SFabien Sanglard return result;
111*3f982cf4SFabien Sanglard }
112*3f982cf4SFabien Sanglard
StandaloneSenderMain(int argc,char * argv[])113*3f982cf4SFabien Sanglard int StandaloneSenderMain(int argc, char* argv[]) {
114*3f982cf4SFabien Sanglard // A note about modifying command line arguments: consider uniformity
115*3f982cf4SFabien Sanglard // between all Open Screen executables. If it is a platform feature
116*3f982cf4SFabien Sanglard // being exposed, consider if it applies to the standalone receiver,
117*3f982cf4SFabien Sanglard // standalone sender, osp demo, and test_main argument options.
118*3f982cf4SFabien Sanglard const struct option kArgumentOptions[] = {
119*3f982cf4SFabien Sanglard {"max-bitrate", required_argument, nullptr, 'm'},
120*3f982cf4SFabien Sanglard {"no-looping", no_argument, nullptr, 'n'},
121*3f982cf4SFabien Sanglard #if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
122*3f982cf4SFabien Sanglard {"developer-certificate", required_argument, nullptr, 'd'},
123*3f982cf4SFabien Sanglard #endif
124*3f982cf4SFabien Sanglard {"android-hack", no_argument, nullptr, 'a'},
125*3f982cf4SFabien Sanglard {"remoting", no_argument, nullptr, 'r'},
126*3f982cf4SFabien Sanglard {"tracing", no_argument, nullptr, 't'},
127*3f982cf4SFabien Sanglard {"verbose", no_argument, nullptr, 'v'},
128*3f982cf4SFabien Sanglard {"help", no_argument, nullptr, 'h'},
129*3f982cf4SFabien Sanglard {"codec", required_argument, nullptr, 'c'},
130*3f982cf4SFabien Sanglard {nullptr, 0, nullptr, 0}
131*3f982cf4SFabien Sanglard };
132*3f982cf4SFabien Sanglard
133*3f982cf4SFabien Sanglard int max_bitrate = kDefaultMaxBitrate;
134*3f982cf4SFabien Sanglard bool should_loop_video = true;
135*3f982cf4SFabien Sanglard std::string developer_certificate_path;
136*3f982cf4SFabien Sanglard bool use_android_rtp_hack = false;
137*3f982cf4SFabien Sanglard bool use_remoting = false;
138*3f982cf4SFabien Sanglard bool is_verbose = false;
139*3f982cf4SFabien Sanglard VideoCodec codec = VideoCodec::kVp8;
140*3f982cf4SFabien Sanglard std::unique_ptr<TextTraceLoggingPlatform> trace_logger;
141*3f982cf4SFabien Sanglard int ch = -1;
142*3f982cf4SFabien Sanglard while ((ch = getopt_long(argc, argv, "m:nd:artvhc:", kArgumentOptions,
143*3f982cf4SFabien Sanglard nullptr)) != -1) {
144*3f982cf4SFabien Sanglard switch (ch) {
145*3f982cf4SFabien Sanglard case 'm':
146*3f982cf4SFabien Sanglard max_bitrate = atoi(optarg);
147*3f982cf4SFabien Sanglard if (max_bitrate < kMinRequiredBitrate) {
148*3f982cf4SFabien Sanglard OSP_LOG_ERROR << "Invalid --max-bitrate specified: " << optarg
149*3f982cf4SFabien Sanglard << " is less than " << kMinRequiredBitrate;
150*3f982cf4SFabien Sanglard LogUsage(argv[0]);
151*3f982cf4SFabien Sanglard return 1;
152*3f982cf4SFabien Sanglard }
153*3f982cf4SFabien Sanglard break;
154*3f982cf4SFabien Sanglard case 'n':
155*3f982cf4SFabien Sanglard should_loop_video = false;
156*3f982cf4SFabien Sanglard break;
157*3f982cf4SFabien Sanglard #if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
158*3f982cf4SFabien Sanglard case 'd':
159*3f982cf4SFabien Sanglard developer_certificate_path = optarg;
160*3f982cf4SFabien Sanglard break;
161*3f982cf4SFabien Sanglard #endif
162*3f982cf4SFabien Sanglard case 'a':
163*3f982cf4SFabien Sanglard use_android_rtp_hack = true;
164*3f982cf4SFabien Sanglard break;
165*3f982cf4SFabien Sanglard case 'r':
166*3f982cf4SFabien Sanglard use_remoting = true;
167*3f982cf4SFabien Sanglard break;
168*3f982cf4SFabien Sanglard case 't':
169*3f982cf4SFabien Sanglard trace_logger = std::make_unique<TextTraceLoggingPlatform>();
170*3f982cf4SFabien Sanglard break;
171*3f982cf4SFabien Sanglard case 'v':
172*3f982cf4SFabien Sanglard is_verbose = true;
173*3f982cf4SFabien Sanglard break;
174*3f982cf4SFabien Sanglard case 'h':
175*3f982cf4SFabien Sanglard LogUsage(argv[0]);
176*3f982cf4SFabien Sanglard return 1;
177*3f982cf4SFabien Sanglard case 'c':
178*3f982cf4SFabien Sanglard auto specified_codec = StringToVideoCodec(optarg);
179*3f982cf4SFabien Sanglard if (specified_codec.is_value() &&
180*3f982cf4SFabien Sanglard (specified_codec.value() == VideoCodec::kVp8 ||
181*3f982cf4SFabien Sanglard specified_codec.value() == VideoCodec::kVp9 ||
182*3f982cf4SFabien Sanglard specified_codec.value() == VideoCodec::kAv1)) {
183*3f982cf4SFabien Sanglard codec = specified_codec.value();
184*3f982cf4SFabien Sanglard } else {
185*3f982cf4SFabien Sanglard OSP_LOG_ERROR << "Invalid --codec specified: " << optarg
186*3f982cf4SFabien Sanglard << " is not one of: vp8, vp9, av1.";
187*3f982cf4SFabien Sanglard LogUsage(argv[0]);
188*3f982cf4SFabien Sanglard return 1;
189*3f982cf4SFabien Sanglard }
190*3f982cf4SFabien Sanglard break;
191*3f982cf4SFabien Sanglard }
192*3f982cf4SFabien Sanglard }
193*3f982cf4SFabien Sanglard
194*3f982cf4SFabien Sanglard openscreen::SetLogLevel(is_verbose ? openscreen::LogLevel::kVerbose
195*3f982cf4SFabien Sanglard : openscreen::LogLevel::kInfo);
196*3f982cf4SFabien Sanglard // The second to last command line argument must be one of: 1) the network
197*3f982cf4SFabien Sanglard // interface name or 2) a specific IP address (port is optional). The last
198*3f982cf4SFabien Sanglard // argument must be the path to the file.
199*3f982cf4SFabien Sanglard if (optind != (argc - 2)) {
200*3f982cf4SFabien Sanglard LogUsage(argv[0]);
201*3f982cf4SFabien Sanglard return 1;
202*3f982cf4SFabien Sanglard }
203*3f982cf4SFabien Sanglard const char* const iface_or_endpoint = argv[optind++];
204*3f982cf4SFabien Sanglard const char* const path = argv[optind];
205*3f982cf4SFabien Sanglard
206*3f982cf4SFabien Sanglard #if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
207*3f982cf4SFabien Sanglard if (!developer_certificate_path.empty()) {
208*3f982cf4SFabien Sanglard CastTrustStore::CreateInstanceFromPemFile(developer_certificate_path);
209*3f982cf4SFabien Sanglard }
210*3f982cf4SFabien Sanglard #endif
211*3f982cf4SFabien Sanglard
212*3f982cf4SFabien Sanglard auto* const task_runner = new TaskRunnerImpl(&Clock::now);
213*3f982cf4SFabien Sanglard PlatformClientPosix::Create(milliseconds(50),
214*3f982cf4SFabien Sanglard std::unique_ptr<TaskRunnerImpl>(task_runner));
215*3f982cf4SFabien Sanglard
216*3f982cf4SFabien Sanglard IPEndpoint remote_endpoint = ParseAsEndpoint(iface_or_endpoint);
217*3f982cf4SFabien Sanglard if (!remote_endpoint.port) {
218*3f982cf4SFabien Sanglard for (const InterfaceInfo& interface : GetAllInterfaces()) {
219*3f982cf4SFabien Sanglard if (interface.name == iface_or_endpoint) {
220*3f982cf4SFabien Sanglard ReceiverChooser chooser(interface, task_runner,
221*3f982cf4SFabien Sanglard [&](IPEndpoint endpoint) {
222*3f982cf4SFabien Sanglard remote_endpoint = endpoint;
223*3f982cf4SFabien Sanglard task_runner->RequestStopSoon();
224*3f982cf4SFabien Sanglard });
225*3f982cf4SFabien Sanglard task_runner->RunUntilSignaled();
226*3f982cf4SFabien Sanglard break;
227*3f982cf4SFabien Sanglard }
228*3f982cf4SFabien Sanglard }
229*3f982cf4SFabien Sanglard
230*3f982cf4SFabien Sanglard if (!remote_endpoint.port) {
231*3f982cf4SFabien Sanglard OSP_LOG_ERROR << "No Cast Receiver chosen, or bad command-line argument. "
232*3f982cf4SFabien Sanglard "Cannot continue.";
233*3f982cf4SFabien Sanglard LogUsage(argv[0]);
234*3f982cf4SFabien Sanglard return 2;
235*3f982cf4SFabien Sanglard }
236*3f982cf4SFabien Sanglard }
237*3f982cf4SFabien Sanglard
238*3f982cf4SFabien Sanglard // |cast_agent| must be constructed and destroyed from a Task run by the
239*3f982cf4SFabien Sanglard // TaskRunner.
240*3f982cf4SFabien Sanglard LoopingFileCastAgent* cast_agent = nullptr;
241*3f982cf4SFabien Sanglard task_runner->PostTask([&] {
242*3f982cf4SFabien Sanglard cast_agent = new LoopingFileCastAgent(
243*3f982cf4SFabien Sanglard task_runner, [&] { task_runner->RequestStopSoon(); });
244*3f982cf4SFabien Sanglard
245*3f982cf4SFabien Sanglard cast_agent->Connect({.receiver_endpoint = remote_endpoint,
246*3f982cf4SFabien Sanglard .path_to_file = path,
247*3f982cf4SFabien Sanglard .max_bitrate = max_bitrate,
248*3f982cf4SFabien Sanglard .should_include_video = true,
249*3f982cf4SFabien Sanglard .use_android_rtp_hack = use_android_rtp_hack,
250*3f982cf4SFabien Sanglard .use_remoting = use_remoting,
251*3f982cf4SFabien Sanglard .should_loop_video = should_loop_video,
252*3f982cf4SFabien Sanglard .codec = codec});
253*3f982cf4SFabien Sanglard });
254*3f982cf4SFabien Sanglard
255*3f982cf4SFabien Sanglard // Run the event loop until SIGINT (e.g., CTRL-C at the console) or
256*3f982cf4SFabien Sanglard // SIGTERM are signaled.
257*3f982cf4SFabien Sanglard task_runner->RunUntilSignaled();
258*3f982cf4SFabien Sanglard
259*3f982cf4SFabien Sanglard // Spin the TaskRunner to destroy the |cast_agent| and execute any lingering
260*3f982cf4SFabien Sanglard // destruction/shutdown tasks.
261*3f982cf4SFabien Sanglard OSP_LOG_INFO << "Shutting down...";
262*3f982cf4SFabien Sanglard task_runner->PostTask([&] {
263*3f982cf4SFabien Sanglard delete cast_agent;
264*3f982cf4SFabien Sanglard task_runner->RequestStopSoon();
265*3f982cf4SFabien Sanglard });
266*3f982cf4SFabien Sanglard task_runner->RunUntilStopped();
267*3f982cf4SFabien Sanglard OSP_LOG_INFO << "Bye!";
268*3f982cf4SFabien Sanglard
269*3f982cf4SFabien Sanglard PlatformClientPosix::ShutDown();
270*3f982cf4SFabien Sanglard return 0;
271*3f982cf4SFabien Sanglard }
272*3f982cf4SFabien Sanglard
273*3f982cf4SFabien Sanglard } // namespace
274*3f982cf4SFabien Sanglard } // namespace cast
275*3f982cf4SFabien Sanglard } // namespace openscreen
276*3f982cf4SFabien Sanglard #endif
277*3f982cf4SFabien Sanglard
main(int argc,char * argv[])278*3f982cf4SFabien Sanglard int main(int argc, char* argv[]) {
279*3f982cf4SFabien Sanglard #if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS)
280*3f982cf4SFabien Sanglard return openscreen::cast::StandaloneSenderMain(argc, argv);
281*3f982cf4SFabien Sanglard #else
282*3f982cf4SFabien Sanglard OSP_LOG_ERROR
283*3f982cf4SFabien Sanglard << "It compiled! However, you need to configure the build to point to "
284*3f982cf4SFabien Sanglard "external libraries in order to build a useful app. For more "
285*3f982cf4SFabien Sanglard "information, see "
286*3f982cf4SFabien Sanglard "[external_libraries.md](../../build/config/external_libraries.md).";
287*3f982cf4SFabien Sanglard return 1;
288*3f982cf4SFabien Sanglard #endif
289*3f982cf4SFabien Sanglard }
290