aboutsummaryrefslogtreecommitdiff
path: root/cast/standalone_sender/main.cc
blob: 75b505534a48354ffb648a3b569a01e16fbfd18d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "platform/impl/logging.h"

#if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS)
#include <getopt.h>

#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>
#include <vector>

#include "cast/common/certificate/cast_trust_store.h"
#include "cast/standalone_sender/constants.h"
#include "cast/standalone_sender/looping_file_cast_agent.h"
#include "cast/standalone_sender/receiver_chooser.h"
#include "cast/streaming/constants.h"
#include "platform/api/network_interface.h"
#include "platform/api/time.h"
#include "platform/base/error.h"
#include "platform/base/ip_address.h"
#include "platform/impl/platform_client_posix.h"
#include "platform/impl/task_runner.h"
#include "platform/impl/text_trace_logging_platform.h"
#include "util/chrono_helpers.h"
#include "util/stringprintf.h"

namespace openscreen {
namespace cast {
namespace {

void LogUsage(const char* argv0) {
  constexpr char kTemplate[] = R"(
usage: %s <options> network_interface media_file

or

usage: %s <options> addr[:port] media_file

   The first form runs this application in discovery+interactive mode. It will
   scan for Cast Receivers on the LAN reachable from the given network
   interface, and then the user will choose one interactively via a menu on the
   console.

   The second form runs this application in direct mode. It will not attempt to
   discover Cast Receivers, and instead connect directly to the Cast Receiver at
   addr:[port] (e.g., 192.168.1.22, 192.168.1.22:%d or [::1]:%d).

      -m, --max-bitrate=N
           Specifies the maximum bits per second for the media streams.

           Default if not set: %d
)"
#if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
                               R"(
      -d, --developer-certificate=path-to-cert
           Specifies the path to a self-signed developer certificate that will
           be permitted for use as a root CA certificate for receivers that
           this sender instance will connect to. If omitted, only connections to
           receivers using an official Google-signed cast certificate chain will
           be permitted.
)"
#endif
                               R"(
      -a, --android-hack:
           Use the wrong RTP payload types, for compatibility with older Android
           TV receivers.

      -t, --tracing: Enable performance tracing logging.

      -v, --verbose: Enable verbose logging.

      -h, --help: Show this help message.
)";

  std::cerr << StringPrintf(kTemplate, argv0, argv0, kDefaultCastPort,
                            kDefaultCastPort, kDefaultMaxBitrate);
}

// Attempts to parse |string_form| into an IPEndpoint. The format is a
// standard-format IPv4 or IPv6 address followed by an optional colon and port.
// If the port is not provided, kDefaultCastPort is assumed.
//
// If the parse fails, a zero-port IPEndpoint is returned.
IPEndpoint ParseAsEndpoint(const char* string_form) {
  IPEndpoint result{};
  const ErrorOr<IPEndpoint> parsed_endpoint = IPEndpoint::Parse(string_form);
  if (parsed_endpoint.is_value()) {
    result = parsed_endpoint.value();
  } else {
    const ErrorOr<IPAddress> parsed_address = IPAddress::Parse(string_form);
    if (parsed_address.is_value()) {
      result = {parsed_address.value(), kDefaultCastPort};
    }
  }
  return result;
}

int StandaloneSenderMain(int argc, char* argv[]) {
  // A note about modifying command line arguments: consider uniformity
  // between all Open Screen executables. If it is a platform feature
  // being exposed, consider if it applies to the standalone receiver,
  // standalone sender, osp demo, and test_main argument options.
  const struct option kArgumentOptions[] = {
    {"max-bitrate", required_argument, nullptr, 'm'},
#if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
    {"developer-certificate", required_argument, nullptr, 'd'},
#endif
    {"android-hack", no_argument, nullptr, 'a'},
    {"tracing", no_argument, nullptr, 't'},
    {"verbose", no_argument, nullptr, 'v'},
    {"help", no_argument, nullptr, 'h'},
    {nullptr, 0, nullptr, 0}
  };

  bool is_verbose = false;
  std::string developer_certificate_path;
  bool use_android_rtp_hack = false;
  int max_bitrate = kDefaultMaxBitrate;
  std::unique_ptr<TextTraceLoggingPlatform> trace_logger;
  int ch = -1;
  while ((ch = getopt_long(argc, argv, "m:d:atvh", kArgumentOptions,
                           nullptr)) != -1) {
    switch (ch) {
      case 'm':
        max_bitrate = atoi(optarg);
        if (max_bitrate < kMinRequiredBitrate) {
          OSP_LOG_ERROR << "Invalid --max-bitrate specified: " << optarg
                        << " is less than " << kMinRequiredBitrate;
          LogUsage(argv[0]);
          return 1;
        }
        break;
#if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
      case 'd':
        developer_certificate_path = optarg;
        break;
#endif
      case 'a':
        use_android_rtp_hack = true;
        break;
      case 't':
        trace_logger = std::make_unique<TextTraceLoggingPlatform>();
        break;
      case 'v':
        is_verbose = true;
        break;
      case 'h':
        LogUsage(argv[0]);
        return 1;
    }
  }

  openscreen::SetLogLevel(is_verbose ? openscreen::LogLevel::kVerbose
                                     : openscreen::LogLevel::kInfo);
  // The second to last command line argument must be one of: 1) the network
  // interface name or 2) a specific IP address (port is optional). The last
  // argument must be the path to the file.
  if (optind != (argc - 2)) {
    LogUsage(argv[0]);
    return 1;
  }
  const char* const iface_or_endpoint = argv[optind++];
  const char* const path = argv[optind];

#if defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
  if (!developer_certificate_path.empty()) {
    CastTrustStore::CreateInstanceFromPemFile(developer_certificate_path);
  }
#endif

  auto* const task_runner = new TaskRunnerImpl(&Clock::now);
  PlatformClientPosix::Create(milliseconds(50),
                              std::unique_ptr<TaskRunnerImpl>(task_runner));

  IPEndpoint remote_endpoint = ParseAsEndpoint(iface_or_endpoint);
  if (!remote_endpoint.port) {
    for (const InterfaceInfo& interface : GetNetworkInterfaces()) {
      if (interface.name == iface_or_endpoint) {
        ReceiverChooser chooser(interface, task_runner,
                                [&](IPEndpoint endpoint) {
                                  remote_endpoint = endpoint;
                                  task_runner->RequestStopSoon();
                                });
        task_runner->RunUntilSignaled();
        break;
      }
    }

    if (!remote_endpoint.port) {
      OSP_LOG_ERROR << "No Cast Receiver chosen, or bad command-line argument. "
                       "Cannot continue.";
      LogUsage(argv[0]);
      return 2;
    }
  }

  // |cast_agent| must be constructed and destroyed from a Task run by the
  // TaskRunner.
  LoopingFileCastAgent* cast_agent = nullptr;
  task_runner->PostTask([&] {
    cast_agent = new LoopingFileCastAgent(
        task_runner, [&] { task_runner->RequestStopSoon(); });
    cast_agent->Connect({remote_endpoint, path, max_bitrate,
                         true /* should_include_video */,
                         use_android_rtp_hack});
  });

  // Run the event loop until SIGINT (e.g., CTRL-C at the console) or
  // SIGTERM are signaled.
  task_runner->RunUntilSignaled();

  // Spin the TaskRunner to destroy the |cast_agent| and execute any lingering
  // destruction/shutdown tasks.
  OSP_LOG_INFO << "Shutting down...";
  task_runner->PostTask([&] {
    delete cast_agent;
    task_runner->RequestStopSoon();
  });
  task_runner->RunUntilStopped();
  OSP_LOG_INFO << "Bye!";

  PlatformClientPosix::ShutDown();
  return 0;
}

}  // namespace
}  // namespace cast
}  // namespace openscreen
#endif

int main(int argc, char* argv[]) {
#if defined(CAST_STANDALONE_SENDER_HAVE_EXTERNAL_LIBS)
  return openscreen::cast::StandaloneSenderMain(argc, argv);
#else
  OSP_LOG_ERROR
      << "It compiled! However, you need to configure the build to point to "
         "external libraries in order to build a useful app.";
  return 1;
#endif
}