xref: /aosp_15_r20/external/openscreen/cast/standalone_sender/main.cc (revision 3f982cf4871df8771c9d4abe6e9a6f8d829b2736)
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