| 1 | /* --------------------------------------------------------------------------- |
| 2 | ** This software is in the public domain, furnished "as is", without technical |
| 3 | ** support, and with no warranty, express or implied, as to its usefulness for |
| 4 | ** any purpose. |
| 5 | ** |
| 6 | ** main.cpp |
| 7 | ** |
| 8 | ** V4L2 RTSP streamer |
| 9 | ** |
| 10 | ** H264 capture using V4L2 |
| 11 | ** RTSP using live555 |
| 12 | ** |
| 13 | ** -------------------------------------------------------------------------*/ |
| 14 | |
| 15 | #include <stdio.h> |
| 16 | #include <stdlib.h> |
| 17 | #include <string.h> |
| 18 | #include <errno.h> |
| 19 | #include <signal.h> |
| 20 | #include <sys/ioctl.h> |
| 21 | #include <dirent.h> |
| 22 | |
| 23 | #include <sstream> |
| 24 | |
| 25 | // libv4l2 |
| 26 | #include <linux/videodev2.h> |
| 27 | |
| 28 | // live555 |
| 29 | #include <BasicUsageEnvironment.hh> |
| 30 | #include <GroupsockHelper.hh> |
| 31 | |
| 32 | // project |
| 33 | #include "logger.h" |
| 34 | |
| 35 | #include "V4l2Device.h" |
| 36 | #include "V4l2Capture.h" |
| 37 | #include "V4l2Output.h" |
| 38 | |
| 39 | #include "H264_V4l2DeviceSource.h" |
| 40 | #include "ServerMediaSubsession.h" |
| 41 | #include "UnicastServerMediaSubsession.h" |
| 42 | #include "MulticastServerMediaSubsession.h" |
| 43 | #include "TSServerMediaSubsession.h" |
| 44 | #include "HTTPServer.h" |
| 45 | |
| 46 | #ifdef HAVE_ALSA |
| 47 | #include "ALSACapture.h" |
| 48 | #endif |
| 49 | |
| 50 | // ----------------------------------------- |
| 51 | // signal handler |
| 52 | // ----------------------------------------- |
| 53 | char quit = 0; |
| 54 | void sighandler(int n) |
| 55 | { |
| 56 | printf("SIGINT\n" ); |
| 57 | quit =1; |
| 58 | } |
| 59 | |
| 60 | |
| 61 | // ----------------------------------------- |
| 62 | // create UserAuthenticationDatabase for RTSP server |
| 63 | // ----------------------------------------- |
| 64 | UserAuthenticationDatabase* createUserAuthenticationDatabase(const std::list<std::string> & userPasswordList, const char* realm) |
| 65 | { |
| 66 | UserAuthenticationDatabase* auth = NULL; |
| 67 | if (userPasswordList.size() > 0) |
| 68 | { |
| 69 | auth = new UserAuthenticationDatabase(realm, (realm != NULL) ); |
| 70 | |
| 71 | std::list<std::string>::const_iterator it; |
| 72 | for (it = userPasswordList.begin(); it != userPasswordList.end(); ++it) |
| 73 | { |
| 74 | std::istringstream is(*it); |
| 75 | std::string user; |
| 76 | getline(is, user, ':'); |
| 77 | std::string password; |
| 78 | getline(is, password); |
| 79 | auth->addUserRecord(user.c_str(), password.c_str()); |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | return auth; |
| 84 | } |
| 85 | |
| 86 | // ----------------------------------------- |
| 87 | // create RTSP server |
| 88 | // ----------------------------------------- |
| 89 | RTSPServer* createRTSPServer(UsageEnvironment& env, unsigned short rtspPort, unsigned short rtspOverHTTPPort, int timeout, unsigned int hlsSegment, const std::list<std::string> & userPasswordList, const char* realm, const std::string & webroot) |
| 90 | { |
| 91 | UserAuthenticationDatabase* auth = createUserAuthenticationDatabase(userPasswordList, realm); |
| 92 | RTSPServer* rtspServer = HTTPServer::createNew(env, rtspPort, auth, timeout, hlsSegment, webroot); |
| 93 | if (rtspServer != NULL) |
| 94 | { |
| 95 | // set http tunneling |
| 96 | if (rtspOverHTTPPort) |
| 97 | { |
| 98 | rtspServer->setUpTunnelingOverHTTP(rtspOverHTTPPort); |
| 99 | } |
| 100 | } |
| 101 | return rtspServer; |
| 102 | } |
| 103 | |
| 104 | |
| 105 | // ----------------------------------------- |
| 106 | // create FramedSource server |
| 107 | // ----------------------------------------- |
| 108 | FramedSource* createFramedSource(UsageEnvironment* env, int format, DeviceInterface* videoCapture, int outfd, int queueSize, bool useThread, bool repeatConfig) |
| 109 | { |
| 110 | FramedSource* source = NULL; |
| 111 | if (format == V4L2_PIX_FMT_H264) |
| 112 | { |
| 113 | source = H264_V4L2DeviceSource::createNew(*env, videoCapture, outfd, queueSize, useThread, repeatConfig, false); |
| 114 | } |
| 115 | else if (format == V4L2_PIX_FMT_HEVC) |
| 116 | { |
| 117 | source = H265_V4L2DeviceSource::createNew(*env, videoCapture, outfd, queueSize, useThread, repeatConfig, false); |
| 118 | } |
| 119 | else |
| 120 | { |
| 121 | source = V4L2DeviceSource::createNew(*env, videoCapture, outfd, queueSize, useThread); |
| 122 | } |
| 123 | return source; |
| 124 | } |
| 125 | |
| 126 | // ----------------------------------------- |
| 127 | // add an RTSP session |
| 128 | // ----------------------------------------- |
| 129 | int addSession(RTSPServer* rtspServer, const std::string & sessionName, const std::list<ServerMediaSubsession*> & subSession) |
| 130 | { |
| 131 | int nbSubsession = 0; |
| 132 | if (subSession.empty() == false) |
| 133 | { |
| 134 | UsageEnvironment& env(rtspServer->envir()); |
| 135 | ServerMediaSession* sms = ServerMediaSession::createNew(env, sessionName.c_str()); |
| 136 | if (sms != NULL) |
| 137 | { |
| 138 | std::list<ServerMediaSubsession*>::const_iterator subIt; |
| 139 | for (subIt = subSession.begin(); subIt != subSession.end(); ++subIt) |
| 140 | { |
| 141 | sms->addSubsession(*subIt); |
| 142 | nbSubsession++; |
| 143 | } |
| 144 | |
| 145 | rtspServer->addServerMediaSession(sms); |
| 146 | |
| 147 | char* url = rtspServer->rtspURL(sms); |
| 148 | if (url != NULL) |
| 149 | { |
| 150 | LOG(NOTICE) << "Play this stream using the URL \"" << url << "\"" ; |
| 151 | delete[] url; |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | return nbSubsession; |
| 156 | } |
| 157 | |
| 158 | // ----------------------------------------- |
| 159 | // convert V4L2 pix format to RTP mime |
| 160 | // ----------------------------------------- |
| 161 | std::string getVideoRtpFormat(int format) |
| 162 | { |
| 163 | std::string rtpFormat; |
| 164 | switch(format) |
| 165 | { |
| 166 | case V4L2_PIX_FMT_HEVC : rtpFormat = "video/H265" ; break; |
| 167 | case V4L2_PIX_FMT_H264 : rtpFormat = "video/H264" ; break; |
| 168 | case V4L2_PIX_FMT_MJPEG: rtpFormat = "video/JPEG" ; break; |
| 169 | case V4L2_PIX_FMT_JPEG : rtpFormat = "video/JPEG" ; break; |
| 170 | case V4L2_PIX_FMT_VP8 : rtpFormat = "video/VP8" ; break; |
| 171 | case V4L2_PIX_FMT_VP9 : rtpFormat = "video/VP9" ; break; |
| 172 | case V4L2_PIX_FMT_YUYV : rtpFormat = "video/RAW" ; break; |
| 173 | case V4L2_PIX_FMT_UYVY : rtpFormat = "video/RAW" ; break; |
| 174 | } |
| 175 | |
| 176 | return rtpFormat; |
| 177 | } |
| 178 | |
| 179 | // ----------------------------------------- |
| 180 | // convert string video format to fourcc |
| 181 | // ----------------------------------------- |
| 182 | int decodeVideoFormat(const char* fmt) |
| 183 | { |
| 184 | char fourcc[4]; |
| 185 | memset(&fourcc, 0, sizeof(fourcc)); |
| 186 | if (fmt != NULL) |
| 187 | { |
| 188 | strncpy(fourcc, fmt, 4); |
| 189 | } |
| 190 | return v4l2_fourcc(fourcc[0], fourcc[1], fourcc[2], fourcc[3]); |
| 191 | } |
| 192 | |
| 193 | // ----------------------------------------- |
| 194 | // convert string audio format to pcm |
| 195 | // ----------------------------------------- |
| 196 | #ifdef HAVE_ALSA |
| 197 | snd_pcm_format_t decodeAudioFormat(const std::string& fmt) |
| 198 | { |
| 199 | snd_pcm_format_t audioFmt = SND_PCM_FORMAT_UNKNOWN; |
| 200 | if (fmt == "S16_BE" ) { |
| 201 | audioFmt = SND_PCM_FORMAT_S16_BE; |
| 202 | } else if (fmt == "S16_LE" ) { |
| 203 | audioFmt = SND_PCM_FORMAT_S16_LE; |
| 204 | } else if (fmt == "S24_BE" ) { |
| 205 | audioFmt = SND_PCM_FORMAT_S24_BE; |
| 206 | } else if (fmt == "S24_LE" ) { |
| 207 | audioFmt = SND_PCM_FORMAT_S24_LE; |
| 208 | } else if (fmt == "S32_BE" ) { |
| 209 | audioFmt = SND_PCM_FORMAT_S32_BE; |
| 210 | } else if (fmt == "S32_LE" ) { |
| 211 | audioFmt = SND_PCM_FORMAT_S32_LE; |
| 212 | } else if (fmt == "ALAW" ) { |
| 213 | audioFmt = SND_PCM_FORMAT_A_LAW; |
| 214 | } else if (fmt == "MULAW" ) { |
| 215 | audioFmt = SND_PCM_FORMAT_MU_LAW; |
| 216 | } else if (fmt == "S8" ) { |
| 217 | audioFmt = SND_PCM_FORMAT_S8; |
| 218 | } else if (fmt == "MPEG" ) { |
| 219 | audioFmt = SND_PCM_FORMAT_MPEG; |
| 220 | } |
| 221 | return audioFmt; |
| 222 | } |
| 223 | std::string getAudioRtpFormat(snd_pcm_format_t format, int sampleRate, int channels) |
| 224 | { |
| 225 | std::ostringstream os; |
| 226 | os << "audio/" ; |
| 227 | switch (format) { |
| 228 | case SND_PCM_FORMAT_A_LAW: |
| 229 | os << "PCMA" ; |
| 230 | break; |
| 231 | case SND_PCM_FORMAT_MU_LAW: |
| 232 | os << "PCMU" ; |
| 233 | break; |
| 234 | case SND_PCM_FORMAT_S8: |
| 235 | os << "L8" ; |
| 236 | break; |
| 237 | case SND_PCM_FORMAT_S24_BE: |
| 238 | case SND_PCM_FORMAT_S24_LE: |
| 239 | os << "L24" ; |
| 240 | break; |
| 241 | case SND_PCM_FORMAT_S32_BE: |
| 242 | case SND_PCM_FORMAT_S32_LE: |
| 243 | os << "L32" ; |
| 244 | break; |
| 245 | case SND_PCM_FORMAT_MPEG: |
| 246 | os << "MPEG" ; |
| 247 | break; |
| 248 | default: |
| 249 | os << "L16" ; |
| 250 | break; |
| 251 | } |
| 252 | os << "/" << sampleRate << "/" << channels; |
| 253 | return os.str(); |
| 254 | } |
| 255 | #endif |
| 256 | |
| 257 | // ------------------------------------------------------- |
| 258 | // decode multicast url <group>:<rtp_port>:<rtcp_port> |
| 259 | // ------------------------------------------------------- |
| 260 | void decodeMulticastUrl(const std::string & maddr, in_addr & destinationAddress, unsigned short & rtpPortNum, unsigned short & rtcpPortNum) |
| 261 | { |
| 262 | std::istringstream is(maddr); |
| 263 | std::string ip; |
| 264 | getline(is, ip, ':'); |
| 265 | if (!ip.empty()) |
| 266 | { |
| 267 | destinationAddress.s_addr = inet_addr(ip.c_str()); |
| 268 | } |
| 269 | |
| 270 | std::string port; |
| 271 | getline(is, port, ':'); |
| 272 | rtpPortNum = 20000; |
| 273 | if (!port.empty()) |
| 274 | { |
| 275 | rtpPortNum = atoi(port.c_str()); |
| 276 | } |
| 277 | rtcpPortNum = rtpPortNum+1; |
| 278 | } |
| 279 | |
| 280 | // ------------------------------------------------------- |
| 281 | // split video,audio device |
| 282 | // ------------------------------------------------------- |
| 283 | void decodeDevice(const std::string & device, std::string & videoDev, std::string & audioDev) |
| 284 | { |
| 285 | std::istringstream is(device); |
| 286 | getline(is, videoDev, ','); |
| 287 | getline(is, audioDev); |
| 288 | } |
| 289 | |
| 290 | std::string getDeviceName(const std::string & devicePath) |
| 291 | { |
| 292 | std::string deviceName(devicePath); |
| 293 | size_t pos = deviceName.find_last_of('/'); |
| 294 | if (pos != std::string::npos) { |
| 295 | deviceName.erase(0,pos+1); |
| 296 | } |
| 297 | return deviceName; |
| 298 | } |
| 299 | |
| 300 | |
| 301 | /* --------------------------------------------------------------------------- |
| 302 | ** get a "deviceid" from uevent sys file |
| 303 | ** -------------------------------------------------------------------------*/ |
| 304 | #ifdef HAVE_ALSA |
| 305 | std::string getDeviceId(const std::string& evt) { |
| 306 | std::string deviceid; |
| 307 | std::istringstream f(evt); |
| 308 | std::string key; |
| 309 | while (getline(f, key, '=')) { |
| 310 | std::string value; |
| 311 | if (getline(f, value)) { |
| 312 | if ( (key =="PRODUCT" ) || (key == "PCI_SUBSYS_ID" ) ) { |
| 313 | deviceid = value; |
| 314 | break; |
| 315 | } |
| 316 | } |
| 317 | } |
| 318 | return deviceid; |
| 319 | } |
| 320 | |
| 321 | std::string getV4l2Alsa(const std::string& v4l2device) { |
| 322 | std::string audioDevice(v4l2device); |
| 323 | |
| 324 | std::map<std::string,std::string> videodevices; |
| 325 | std::string video4linuxPath("/sys/class/video4linux" ); |
| 326 | DIR *dp = opendir(video4linuxPath.c_str()); |
| 327 | if (dp != NULL) { |
| 328 | struct dirent *entry = NULL; |
| 329 | while((entry = readdir(dp))) { |
| 330 | std::string devicename; |
| 331 | std::string deviceid; |
| 332 | if (strstr(entry->d_name,"video" ) == entry->d_name) { |
| 333 | std::string ueventPath(video4linuxPath); |
| 334 | ueventPath.append("/" ).append(entry->d_name).append("/device/uevent" ); |
| 335 | std::ifstream ifsd(ueventPath.c_str()); |
| 336 | deviceid = std::string(std::istreambuf_iterator<char>{ifsd}, {}); |
| 337 | deviceid.erase(deviceid.find_last_not_of("\n" )+1); |
| 338 | } |
| 339 | |
| 340 | if (!deviceid.empty()) { |
| 341 | videodevices[entry->d_name] = getDeviceId(deviceid); |
| 342 | } |
| 343 | } |
| 344 | closedir(dp); |
| 345 | } |
| 346 | |
| 347 | std::map<std::string,std::string> audiodevices; |
| 348 | int rcard = -1; |
| 349 | while ( (snd_card_next(&rcard) == 0) && (rcard>=0) ) { |
| 350 | void **hints = NULL; |
| 351 | if (snd_device_name_hint(rcard, "pcm" , &hints) >= 0) { |
| 352 | void **str = hints; |
| 353 | while (*str) { |
| 354 | std::ostringstream os; |
| 355 | os << "/sys/class/sound/card" << rcard << "/device/uevent" ; |
| 356 | |
| 357 | std::ifstream ifs(os.str().c_str()); |
| 358 | std::string deviceid = std::string(std::istreambuf_iterator<char>{ifs}, {}); |
| 359 | deviceid.erase(deviceid.find_last_not_of("\n" )+1); |
| 360 | deviceid = getDeviceId(deviceid); |
| 361 | |
| 362 | if (!deviceid.empty()) { |
| 363 | if (audiodevices.find(deviceid) == audiodevices.end()) { |
| 364 | std::string audioname = snd_device_name_get_hint(*str, "NAME" ); |
| 365 | audiodevices[deviceid] = audioname; |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | str++; |
| 370 | } |
| 371 | |
| 372 | snd_device_name_free_hint(hints); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | auto deviceId = videodevices.find(getDeviceName(v4l2device)); |
| 377 | if (deviceId != videodevices.end()) { |
| 378 | auto audioDeviceIt = audiodevices.find(deviceId->second); |
| 379 | |
| 380 | if (audioDeviceIt != audiodevices.end()) { |
| 381 | audioDevice = audioDeviceIt->second; |
| 382 | std::cout << v4l2device << "=>" << audioDevice << std::endl; |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | |
| 387 | return audioDevice; |
| 388 | } |
| 389 | #endif |
| 390 | |
| 391 | // ----------------------------------------- |
| 392 | // entry point |
| 393 | // ----------------------------------------- |
| 394 | int main(int argc, char** argv) |
| 395 | { |
| 396 | // default parameters |
| 397 | const char *dev_name = "/dev/video0,/dev/video0" ; |
| 398 | unsigned int format = ~0; |
| 399 | std::list<unsigned int> videoformatList; |
| 400 | int width = 0; |
| 401 | int height = 0; |
| 402 | int queueSize = 10; |
| 403 | int fps = 25; |
| 404 | unsigned short rtspPort = 8554; |
| 405 | unsigned short rtspOverHTTPPort = 0; |
| 406 | bool multicast = false; |
| 407 | int verbose = 0; |
| 408 | std::string outputFile; |
| 409 | V4l2Access::IoType ioTypeIn = V4l2Access::IOTYPE_MMAP; |
| 410 | V4l2Access::IoType ioTypeOut = V4l2Access::IOTYPE_MMAP; |
| 411 | int openflags = O_RDWR | O_NONBLOCK; |
| 412 | std::string url = "unicast" ; |
| 413 | std::string murl = "multicast" ; |
| 414 | std::string tsurl = "ts" ; |
| 415 | bool useThread = true; |
| 416 | std::string maddr; |
| 417 | bool repeatConfig = true; |
| 418 | int timeout = 65; |
| 419 | int defaultHlsSegment = 2; |
| 420 | unsigned int hlsSegment = 0; |
| 421 | const char* realm = NULL; |
| 422 | std::list<std::string> userPasswordList; |
| 423 | std::string webroot; |
| 424 | #ifdef HAVE_ALSA |
| 425 | int audioFreq = 44100; |
| 426 | int audioNbChannels = 2; |
| 427 | std::list<snd_pcm_format_t> audioFmtList; |
| 428 | snd_pcm_format_t audioFmt = SND_PCM_FORMAT_UNKNOWN; |
| 429 | #endif |
| 430 | const char* defaultPort = getenv("PORT" ); |
| 431 | if (defaultPort != NULL) { |
| 432 | rtspPort = atoi(defaultPort); |
| 433 | } |
| 434 | |
| 435 | // decode parameters |
| 436 | int c = 0; |
| 437 | while ((c = getopt (argc, argv, "v::Q:O:b:" "I:P:p:m:u:M:ct:S::" "R:U:" "rwBsf::F:W:H:G:" "A:C:a:" "Vh" )) != -1) |
| 438 | { |
| 439 | switch (c) |
| 440 | { |
| 441 | case 'v': verbose = 1; if (optarg && *optarg=='v') verbose++; break; |
| 442 | case 'Q': queueSize = atoi(optarg); break; |
| 443 | case 'O': outputFile = optarg; break; |
| 444 | case 'b': webroot = optarg; break; |
| 445 | |
| 446 | // RTSP/RTP |
| 447 | case 'I': ReceivingInterfaceAddr = inet_addr(optarg); break; |
| 448 | case 'P': rtspPort = atoi(optarg); break; |
| 449 | case 'p': rtspOverHTTPPort = atoi(optarg); break; |
| 450 | case 'u': url = optarg; break; |
| 451 | case 'm': multicast = true; murl = optarg; break; |
| 452 | case 'M': multicast = true; maddr = optarg; break; |
| 453 | case 'c': repeatConfig = false; break; |
| 454 | case 't': timeout = atoi(optarg); break; |
| 455 | case 'S': hlsSegment = optarg ? atoi(optarg) : defaultHlsSegment; break; |
| 456 | |
| 457 | // users |
| 458 | case 'R': realm = optarg; break; |
| 459 | case 'U': userPasswordList.push_back(optarg); break; |
| 460 | |
| 461 | // V4L2 |
| 462 | case 'r': ioTypeIn = V4l2Access::IOTYPE_READWRITE; break; |
| 463 | case 'w': ioTypeOut = V4l2Access::IOTYPE_READWRITE; break; |
| 464 | case 'B': openflags = O_RDWR; break; |
| 465 | case 's': useThread = false; break; |
| 466 | case 'f': format = decodeVideoFormat(optarg); if (format) {videoformatList.push_back(format);}; break; |
| 467 | case 'F': fps = atoi(optarg); break; |
| 468 | case 'W': width = atoi(optarg); break; |
| 469 | case 'H': height = atoi(optarg); break; |
| 470 | case 'G': sscanf(optarg,"%dx%dx%d" , &width, &height, &fps); break; |
| 471 | |
| 472 | // ALSA |
| 473 | #ifdef HAVE_ALSA |
| 474 | case 'A': audioFreq = atoi(optarg); break; |
| 475 | case 'C': audioNbChannels = atoi(optarg); break; |
| 476 | case 'a': audioFmt = decodeAudioFormat(optarg); if (audioFmt != SND_PCM_FORMAT_UNKNOWN) {audioFmtList.push_back(audioFmt);} ; break; |
| 477 | #endif |
| 478 | |
| 479 | // version |
| 480 | case 'V': |
| 481 | std::cout << VERSION << std::endl; |
| 482 | exit(0); |
| 483 | break; |
| 484 | |
| 485 | // help |
| 486 | case 'h': |
| 487 | default: |
| 488 | { |
| 489 | std::cout << argv[0] << " [-v[v]] [-Q queueSize] [-O file]" << std::endl; |
| 490 | std::cout << "\t [-I interface] [-P RTSP port] [-p RTSP/HTTP port] [-m multicast url] [-u unicast url] [-M multicast addr] [-c] [-t timeout] [-T] [-S[duration]]" << std::endl; |
| 491 | std::cout << "\t [-r] [-w] [-s] [-f[format] [-W width] [-H height] [-F fps] [device] [device]" << std::endl; |
| 492 | std::cout << "\t -v : verbose" << std::endl; |
| 493 | std::cout << "\t -vv : very verbose" << std::endl; |
| 494 | std::cout << "\t -Q <length> : Number of frame queue (default " << queueSize << ")" << std::endl; |
| 495 | std::cout << "\t -O <output> : Copy captured frame to a file or a V4L2 device" << std::endl; |
| 496 | std::cout << "\t -b <webroot> : path to webroot" << std::endl; |
| 497 | |
| 498 | std::cout << "\t RTSP/RTP options" << std::endl; |
| 499 | std::cout << "\t -I <addr> : RTSP interface (default autodetect)" << std::endl; |
| 500 | std::cout << "\t -P <port> : RTSP port (default " << rtspPort << ")" << std::endl; |
| 501 | std::cout << "\t -p <port> : RTSP over HTTP port (default " << rtspOverHTTPPort << ")" << std::endl; |
| 502 | std::cout << "\t -U <user>:<pass> : RTSP user and password" << std::endl; |
| 503 | std::cout << "\t -R <realm> : use md5 password 'md5(<username>:<realm>:<password>')" << std::endl; |
| 504 | std::cout << "\t -u <url> : unicast url (default " << url << ")" << std::endl; |
| 505 | std::cout << "\t -m <url> : multicast url (default " << murl << ")" << std::endl; |
| 506 | std::cout << "\t -M <addr> : multicast group:port (default is random_address:20000)" << std::endl; |
| 507 | std::cout << "\t -c : don't repeat config (default repeat config before IDR frame)" << std::endl; |
| 508 | std::cout << "\t -t <timeout> : RTCP expiration timeout in seconds (default " << timeout << ")" << std::endl; |
| 509 | std::cout << "\t -S[<duration>] : enable HLS & MPEG-DASH with segment duration in seconds (default " << defaultHlsSegment << ")" << std::endl; |
| 510 | |
| 511 | std::cout << "\t V4L2 options" << std::endl; |
| 512 | std::cout << "\t -r : V4L2 capture using read interface (default use memory mapped buffers)" << std::endl; |
| 513 | std::cout << "\t -w : V4L2 capture using write interface (default use memory mapped buffers)" << std::endl; |
| 514 | std::cout << "\t -B : V4L2 capture using blocking mode (default use non-blocking mode)" << std::endl; |
| 515 | std::cout << "\t -s : V4L2 capture using live555 mainloop (default use a reader thread)" << std::endl; |
| 516 | std::cout << "\t -f : V4L2 capture using current capture format (-W,-H,-F are ignored)" << std::endl; |
| 517 | std::cout << "\t -f<format> : V4L2 capture using format (-W,-H,-F are used)" << std::endl; |
| 518 | std::cout << "\t -W <width> : V4L2 capture width (default " << width << ")" << std::endl; |
| 519 | std::cout << "\t -H <height> : V4L2 capture height (default " << height << ")" << std::endl; |
| 520 | std::cout << "\t -F <fps> : V4L2 capture framerate (default " << fps << ")" << std::endl; |
| 521 | std::cout << "\t -G <w>x<h>[x<f>] : V4L2 capture format (default " << width << "x" << height << "x" << fps << ")" << std::endl; |
| 522 | |
| 523 | #ifdef HAVE_ALSA |
| 524 | std::cout << "\t ALSA options" << std::endl; |
| 525 | std::cout << "\t -A freq : ALSA capture frequency and channel (default " << audioFreq << ")" << std::endl; |
| 526 | std::cout << "\t -C channels : ALSA capture channels (default " << audioNbChannels << ")" << std::endl; |
| 527 | std::cout << "\t -a fmt : ALSA capture audio format (default S16_BE)" << std::endl; |
| 528 | #endif |
| 529 | |
| 530 | std::cout << "\t Devices :" << std::endl; |
| 531 | std::cout << "\t [V4L2 device][,ALSA device] : V4L2 capture device or/and ALSA capture device (default " << dev_name << ")" << std::endl; |
| 532 | exit(0); |
| 533 | } |
| 534 | } |
| 535 | } |
| 536 | std::list<std::string> devList; |
| 537 | while (optind<argc) |
| 538 | { |
| 539 | devList.push_back(argv[optind]); |
| 540 | optind++; |
| 541 | } |
| 542 | if (devList.empty()) |
| 543 | { |
| 544 | devList.push_back(dev_name); |
| 545 | } |
| 546 | |
| 547 | // default format tries |
| 548 | if ((videoformatList.empty()) && (format!=0)) { |
| 549 | videoformatList.push_back(V4L2_PIX_FMT_H264); |
| 550 | videoformatList.push_back(V4L2_PIX_FMT_MJPEG); |
| 551 | videoformatList.push_back(V4L2_PIX_FMT_JPEG); |
| 552 | } |
| 553 | |
| 554 | #ifdef HAVE_ALSA |
| 555 | // default audio format tries |
| 556 | if (audioFmtList.empty()) { |
| 557 | audioFmtList.push_back(SND_PCM_FORMAT_S16_LE); |
| 558 | audioFmtList.push_back(SND_PCM_FORMAT_S16_BE); |
| 559 | } |
| 560 | #endif |
| 561 | |
| 562 | // init logger |
| 563 | initLogger(verbose); |
| 564 | |
| 565 | // create live555 environment |
| 566 | TaskScheduler* scheduler = BasicTaskScheduler::createNew(); |
| 567 | UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler); |
| 568 | |
| 569 | // split multicast info |
| 570 | struct in_addr destinationAddress; |
| 571 | destinationAddress.s_addr = chooseRandomIPv4SSMAddress(*env); |
| 572 | unsigned short rtpPortNum = 20000; |
| 573 | unsigned short rtcpPortNum = rtpPortNum+1; |
| 574 | unsigned char ttl = 5; |
| 575 | decodeMulticastUrl(maddr, destinationAddress, rtpPortNum, rtcpPortNum); |
| 576 | |
| 577 | // create RTSP server |
| 578 | RTSPServer* rtspServer = createRTSPServer(*env, rtspPort, rtspOverHTTPPort, timeout, hlsSegment, userPasswordList, realm, webroot); |
| 579 | if (rtspServer == NULL) |
| 580 | { |
| 581 | LOG(ERROR) << "Failed to create RTSP server: " << env->getResultMsg(); |
| 582 | } |
| 583 | else |
| 584 | { |
| 585 | V4l2Output* out = NULL; |
| 586 | int nbSource = 0; |
| 587 | std::list<std::string>::iterator devIt; |
| 588 | for ( devIt=devList.begin() ; devIt!=devList.end() ; ++devIt) |
| 589 | { |
| 590 | std::string deviceName(*devIt); |
| 591 | |
| 592 | std::string videoDev; |
| 593 | std::string audioDev; |
| 594 | decodeDevice(deviceName, videoDev, audioDev); |
| 595 | |
| 596 | std::string baseUrl; |
| 597 | if (devList.size() > 1) |
| 598 | { |
| 599 | baseUrl = getDeviceName(videoDev); |
| 600 | baseUrl.append("/" ); |
| 601 | } |
| 602 | StreamReplicator* videoReplicator = NULL; |
| 603 | std::string rtpFormat; |
| 604 | if (!videoDev.empty()) |
| 605 | { |
| 606 | // Init video capture |
| 607 | LOG(NOTICE) << "Create V4L2 Source..." << videoDev; |
| 608 | |
| 609 | V4L2DeviceParameters param(videoDev.c_str(), videoformatList, width, height, fps, verbose, openflags); |
| 610 | V4l2Capture* videoCapture = V4l2Capture::create(param, ioTypeIn); |
| 611 | if (videoCapture) |
| 612 | { |
| 613 | int outfd = -1; |
| 614 | |
| 615 | if (!outputFile.empty()) |
| 616 | { |
| 617 | V4L2DeviceParameters outparam(outputFile.c_str(), videoCapture->getFormat(), videoCapture->getWidth(), videoCapture->getHeight(), 0,verbose); |
| 618 | out = V4l2Output::create(outparam, ioTypeOut); |
| 619 | if (out != NULL) |
| 620 | { |
| 621 | outfd = out->getFd(); |
| 622 | } |
| 623 | } |
| 624 | |
| 625 | rtpFormat.assign(getVideoRtpFormat(videoCapture->getFormat())); |
| 626 | if (rtpFormat.empty()) { |
| 627 | LOG(FATAL) << "No Streaming format supported for device " << videoDev; |
| 628 | delete videoCapture; |
| 629 | } else { |
| 630 | LOG(NOTICE) << "Create Source ..." << videoDev; |
| 631 | FramedSource* videoSource = createFramedSource(env, videoCapture->getFormat(), new DeviceCaptureAccess<V4l2Capture>(videoCapture), outfd, queueSize, useThread, repeatConfig); |
| 632 | if (videoSource == NULL) |
| 633 | { |
| 634 | LOG(FATAL) << "Unable to create source for device " << videoDev; |
| 635 | delete videoCapture; |
| 636 | } |
| 637 | else |
| 638 | { |
| 639 | // extend buffer size if needed |
| 640 | if (videoCapture->getBufferSize() > OutPacketBuffer::maxSize) |
| 641 | { |
| 642 | OutPacketBuffer::maxSize = videoCapture->getBufferSize(); |
| 643 | } |
| 644 | videoReplicator = StreamReplicator::createNew(*env, videoSource, false); |
| 645 | } |
| 646 | } |
| 647 | } |
| 648 | } |
| 649 | |
| 650 | // Init Audio Capture |
| 651 | StreamReplicator* audioReplicator = NULL; |
| 652 | std::string rtpAudioFormat; |
| 653 | #ifdef HAVE_ALSA |
| 654 | if (!audioDev.empty()) |
| 655 | { |
| 656 | // find the ALSA device associated with the V4L2 device |
| 657 | audioDev = getV4l2Alsa(audioDev); |
| 658 | |
| 659 | // Init audio capture |
| 660 | LOG(NOTICE) << "Create ALSA Source..." << audioDev; |
| 661 | |
| 662 | ALSACaptureParameters param(audioDev.c_str(), audioFmtList, audioFreq, audioNbChannels, verbose); |
| 663 | ALSACapture* audioCapture = ALSACapture::createNew(param); |
| 664 | if (audioCapture) |
| 665 | { |
| 666 | FramedSource* audioSource = V4L2DeviceSource::createNew(*env, new DeviceCaptureAccess<ALSACapture>(audioCapture), -1, queueSize, useThread); |
| 667 | if (audioSource == NULL) |
| 668 | { |
| 669 | LOG(FATAL) << "Unable to create source for device " << audioDev; |
| 670 | delete audioCapture; |
| 671 | } |
| 672 | else |
| 673 | { |
| 674 | rtpAudioFormat.assign(getAudioRtpFormat(audioCapture->getFormat(),audioCapture->getSampleRate(), audioCapture->getChannels())); |
| 675 | |
| 676 | // extend buffer size if needed |
| 677 | if (audioCapture->getBufferSize() > OutPacketBuffer::maxSize) |
| 678 | { |
| 679 | OutPacketBuffer::maxSize = audioCapture->getBufferSize(); |
| 680 | } |
| 681 | audioReplicator = StreamReplicator::createNew(*env, audioSource, false); |
| 682 | } |
| 683 | } |
| 684 | } |
| 685 | #endif |
| 686 | |
| 687 | |
| 688 | // Create Multicast Session |
| 689 | if (multicast) |
| 690 | { |
| 691 | LOG(NOTICE) << "RTP address " << inet_ntoa(destinationAddress) << ":" << rtpPortNum; |
| 692 | LOG(NOTICE) << "RTCP address " << inet_ntoa(destinationAddress) << ":" << rtcpPortNum; |
| 693 | |
| 694 | std::list<ServerMediaSubsession*> subSession; |
| 695 | if (videoReplicator) |
| 696 | { |
| 697 | subSession.push_back(MulticastServerMediaSubsession::createNew(*env, destinationAddress, Port(rtpPortNum), Port(rtcpPortNum), ttl, videoReplicator, rtpFormat)); |
| 698 | // increment ports for next sessions |
| 699 | rtpPortNum+=2; |
| 700 | rtcpPortNum+=2; |
| 701 | } |
| 702 | |
| 703 | if (audioReplicator) |
| 704 | { |
| 705 | subSession.push_back(MulticastServerMediaSubsession::createNew(*env, destinationAddress, Port(rtpPortNum), Port(rtcpPortNum), ttl, audioReplicator, rtpAudioFormat)); |
| 706 | |
| 707 | // increment ports for next sessions |
| 708 | rtpPortNum+=2; |
| 709 | rtcpPortNum+=2; |
| 710 | } |
| 711 | nbSource += addSession(rtspServer, baseUrl+murl, subSession); |
| 712 | } |
| 713 | |
| 714 | // Create HLS Session |
| 715 | if (hlsSegment > 0) |
| 716 | { |
| 717 | std::list<ServerMediaSubsession*> subSession; |
| 718 | if (videoReplicator) |
| 719 | { |
| 720 | subSession.push_back(TSServerMediaSubsession::createNew(*env, videoReplicator, rtpFormat, audioReplicator, rtpAudioFormat, hlsSegment)); |
| 721 | } |
| 722 | nbSource += addSession(rtspServer, baseUrl+tsurl, subSession); |
| 723 | |
| 724 | struct in_addr ip; |
| 725 | ip.s_addr = ourIPAddress(*env); |
| 726 | LOG(NOTICE) << "HLS http://" << inet_ntoa(ip) << ":" << rtspPort << "/" << baseUrl+tsurl << ".m3u8" ; |
| 727 | LOG(NOTICE) << "MPEG-DASH http://" << inet_ntoa(ip) << ":" << rtspPort << "/" << baseUrl+tsurl << ".mpd" ; |
| 728 | } |
| 729 | |
| 730 | // Create Unicast Session |
| 731 | std::list<ServerMediaSubsession*> subSession; |
| 732 | if (videoReplicator) |
| 733 | { |
| 734 | subSession.push_back(UnicastServerMediaSubsession::createNew(*env, videoReplicator, rtpFormat)); |
| 735 | } |
| 736 | if (audioReplicator) |
| 737 | { |
| 738 | subSession.push_back(UnicastServerMediaSubsession::createNew(*env, audioReplicator, rtpAudioFormat)); |
| 739 | } |
| 740 | nbSource += addSession(rtspServer, baseUrl+url, subSession); |
| 741 | } |
| 742 | |
| 743 | if (nbSource>0) |
| 744 | { |
| 745 | // main loop |
| 746 | signal(SIGINT,sighandler); |
| 747 | env->taskScheduler().doEventLoop(&quit); |
| 748 | LOG(NOTICE) << "Exiting...." ; |
| 749 | } |
| 750 | |
| 751 | Medium::close(rtspServer); |
| 752 | |
| 753 | if (out) |
| 754 | { |
| 755 | delete out; |
| 756 | } |
| 757 | } |
| 758 | |
| 759 | env->reclaim(); |
| 760 | delete scheduler; |
| 761 | |
| 762 | return 0; |
| 763 | } |
| 764 | |
| 765 | |
| 766 | |
| 767 | |