summaryrefslogtreecommitdiff
path: root/bin/capture+encode+serve
blob: 16a2ed0883222a257fffd93d2361432d4a53f6cd (plain)
  1. #!/usr/bin/perl
  2. # Send live video/audio media as RTP streams, published via RTSP
  3. use v5.12;
  4. use warnings;
  5. use Glib qw( TRUE FALSE );
  6. use Glib::Object::Introspection;
  7. use IPC::System::Simple qw(capturex);
  8. BEGIN {
  9. Glib::Object::Introspection->setup(
  10. basename => 'Gst',
  11. version => '1.0',
  12. package => 'Gst',
  13. );
  14. Glib::Object::Introspection->setup(
  15. basename => 'GstRtspServer',
  16. version => '1.0',
  17. package => 'Gst',
  18. );
  19. }
  20. my $ADDRESS = shift || $ENV{'ADDRESS'} || '127.0.0.1';
  21. my $PORT = shift || $ENV{'PORT'} || '8554';
  22. my $VDEVICES = shift || $ENV{'VDEVICES'} || '';
  23. my $ADEVICES = shift || $ENV{'ADEVICES'} || '';
  24. my $VFORMAT = shift || $ENV{'VFORMAT'} || 'VP8'; # H264 VP8 RAW - default: VP8
  25. my $AFORMAT = shift || $ENV{'AFORMAT'} || 'OPUS'; # AMR OPUS RAW - default: OPUS
  26. my @VDEVICES = $VDEVICES ? split ' ', $VDEVICES : sort split ' ', capturex('find', qw(/dev -maxdepth 1 -type c -name video*));
  27. # FIXME: Detect/blacklist and skip faulty devices
  28. #my @ADEVICES = grep { /^hw:/ } capturex( 'arecord', qw(-L) );
  29. my @ADEVICES = split ' ', $ADEVICES;
  30. chomp @ADEVICES;
  31. #use Data::Dump; die dd @ADEVICES;
  32. my $HEIGHT = 240;
  33. my $FRAMERATE = 25;
  34. my $AUDIORATE = 48000;
  35. my $VBITRATE = 256000;
  36. my $VCAPS = "video/x-raw,height=$HEIGHT";
  37. my $ACAPS = "audio/x-raw,rate=$AUDIORATE,channels=2,depth=16";
  38. # * http://stackoverflow.com/a/42237307
  39. my $ABUFFERS = 20000;
  40. # * force threads using queues - see http://stackoverflow.com/a/30738533
  41. # * generous queue sizes inspired by https://wiki.xiph.org/GST_cookbook
  42. my $QUEUE = "queue max-size-bytes=100000000 max-size-time=0";
  43. my %VFORMAT = (
  44. H264 => {
  45. # * let x264 use low-latency sliced-threads (i.e. don't disable treads)
  46. VENC => "x264enc speed-preset=ultrafast tune=zerolatency bitrate=800 byte-stream=true key-int-max=15 intra-refresh=true option-string=\"slice-max-size=8192:vbv-maxrate=80:vbv-bufsize=10\" ! video/x-h264,profile=baseline ! $QUEUE ! rtph264pay",
  47. },
  48. VP8 => {
  49. VENC => "vp8enc threads=4 cpu-used=15 deadline=1000000 end-usage=1 target-bitrate=$VBITRATE undershoot=95 keyframe-max-dist=999999 max-quantizer=56 deadline=5000 static-threshold=500 ! video/x-vp8 ! $QUEUE ! rtpvp8pay",
  50. },
  51. RAW => {
  52. VENC => "rtpvrawpay",
  53. },
  54. );
  55. my %AFORMAT = (
  56. AMR => {
  57. AENC => "amrnbenc ! $QUEUE ! rtpamrpay",
  58. },
  59. OPUS => {
  60. AENC => "opusenc ! $QUEUE ! rtpopuspay",
  61. },
  62. RAW => {
  63. AENC => "rtpL16pay",
  64. },
  65. );
  66. our $nextpayload = 0;
  67. sub cam {
  68. my $device = shift;
  69. my $payload = "pay" . $nextpayload++;
  70. my $factory = Gst::RTSPMediaFactory->new();
  71. $factory->set_launch("( v4l2src device=$device ! $QUEUE ! videoconvert ! $VCAPS ! $QUEUE ! $VFORMAT{$VFORMAT}{'VENC'} name=$payload )");
  72. $factory->set_shared(TRUE);
  73. say "media ($device): " . $factory->get_launch();
  74. # $factory->set_latency(5);
  75. #say "latency ($device): " . $factory->get_latency();
  76. return $factory;
  77. }
  78. sub mic {
  79. my $device = shift;
  80. my $payload = "pay" . $nextpayload++;
  81. my $factory = Gst::RTSPMediaFactory->new();
  82. $factory->set_launch("( alsasrc device=$device buffer-time=$ABUFFERS ! $QUEUE ! audioconvert ! $QUEUE ! $AFORMAT{$AFORMAT}{'AENC'} name=$payload )");
  83. $factory->set_shared(TRUE);
  84. #say "media ($device): " . $factory->get_launch();
  85. # $factory->set_latency(5);
  86. #say "latency ($device): " . $factory->get_latency();
  87. return $factory;
  88. }
  89. Gst::init([ $0, @ARGV ]);
  90. my $loop = Glib::MainLoop->new( undef, FALSE );
  91. # create a server instance
  92. my $server = Gst::RTSPServer->new();
  93. $server->set_address($ADDRESS);
  94. $server->set_service($PORT);
  95. # get the mount points for this server, every server has a default
  96. # object that be used to map uri mount points to media factories
  97. my $mounts = $server->get_mount_points();
  98. # attach media to URIs
  99. my @mounts;
  100. for my $i ( 0 .. $#VDEVICES ) {
  101. my $mount = "/cam$i";
  102. $mounts->add_factory($mount, cam($VDEVICES[$i]));
  103. push @mounts, $mount;
  104. };
  105. for my $i ( 0 .. $#ADEVICES ) {
  106. my $mount = "/mic$i";
  107. $mounts->add_factory($mount, mic($ADEVICES[$i]));
  108. push @mounts, $mount;
  109. };
  110. # don't need the ref to the mapper anymore
  111. undef $mounts;
  112. # attach the server to the default maincontext
  113. my $retval = $server->attach(undef);
  114. # start serving
  115. say "streams ready at the following URLs:";
  116. for (@mounts) {
  117. say "rtsp://$ADDRESS:$PORT$_";
  118. }
  119. $loop->run;