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