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