rtp 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +0 -3
- data/History.rdoc +42 -0
- data/README.rdoc +60 -15
- data/Rakefile +9 -4
- data/lib/rtp.rb +0 -3
- data/lib/rtp/logger.rb +11 -0
- data/lib/rtp/packet.rb +39 -3
- data/lib/rtp/receiver.rb +255 -152
- data/lib/rtp/version.rb +1 -1
- data/rtp.gemspec +2 -1
- data/spec/rtp/coverage/assets/0.7.1/application.css +1110 -0
- data/spec/rtp/coverage/assets/0.7.1/application.js +626 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/favicon_green.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/favicon_red.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/favicon_yellow.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/loading.gif +0 -0
- data/spec/rtp/coverage/assets/0.7.1/magnify.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/spec/rtp/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/spec/rtp/coverage/index.html +146 -0
- data/spec/rtp/receiver_spec.rb +241 -258
- data/spec/rtp_spec.rb +1 -1
- data/spec/spec_helper.rb +4 -13
- metadata +152 -23
- data/tasks/roodi.rake +0 -10
- data/tasks/roodi_config.yaml +0 -14
- data/tasks/rspec.rake +0 -10
- data/tasks/stats.rake +0 -12
- data/tasks/yard.rake +0 -8
data/Gemfile
CHANGED
data/History.rdoc
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
=== 0.1.0 / 2012-11-14
|
|
2
|
+
|
|
3
|
+
Lots of big changes this release! Only a minor bump though, since I'm assuming
|
|
4
|
+
that I'm about the only one using this at this point and I'm not going to mess
|
|
5
|
+
anyone else up by making these changes. This is how the initial release should
|
|
6
|
+
have been.
|
|
7
|
+
|
|
8
|
+
New features:
|
|
9
|
+
* RTP::Receiver#start (used to be #run--see below) now takes an optional block
|
|
10
|
+
that will yield a parsed RTP::Packet. This allows for inspecting packets
|
|
11
|
+
as they come in. If a block is given, no data will be written to the capture
|
|
12
|
+
file, so as to conserve I/O.
|
|
13
|
+
* Added ability to receive on multicast sockets. To do so, just set the
|
|
14
|
+
:ip_address option on init to the multicast address you want to use.
|
|
15
|
+
* Turned logging off by default. To turn on, just do <tt>RTP::Logger = true</tt>.
|
|
16
|
+
* Updated RTP::Logger to use log_switch 0.4.0's log_class_name feature.
|
|
17
|
+
* Setting RTP::Receiver#rtp_port will now set #rtcp_port. There's no
|
|
18
|
+
functionality behind the #rtcp_port yet; just a numerical placeholder, really.
|
|
19
|
+
* Use Kernel#at_exit to make sure capture files get closed and deleted.
|
|
20
|
+
|
|
21
|
+
Refactorings:
|
|
22
|
+
* RTP::Receiver.new now takes an options Hash instead of a bunch of params.
|
|
23
|
+
* Merged RTP::Receiver#run and RTP::Receiver#start_listener into
|
|
24
|
+
RTP::Receiver#start. There wasn't any benefit of having them separate.
|
|
25
|
+
* Removed all packet sorting provisions--pretty sure this shouldn't happen at
|
|
26
|
+
this level. This means RTP::Receiver#write_buffer_to_file is gone.
|
|
27
|
+
* Added a @packets Queue, dedicated to writing packets out to the capture file.
|
|
28
|
+
Besides getting out of the way of the receiving I/O, this also abstracts away
|
|
29
|
+
the writing to file logic from the receiving and parsing logic. In a related
|
|
30
|
+
manner...
|
|
31
|
+
* #strip_headers is an RTP::Receiver accessor now, so you can set
|
|
32
|
+
this to true if you only want to capture/yield the RTP payload.
|
|
33
|
+
* RTP::Receiver#init_server is now RTP::Receiver#init_socket.
|
|
34
|
+
* Removed port retrying for RTP::Receiver; if the port requested for use by the
|
|
35
|
+
Receiver is in use, I don't think it should be the role of the Receiver to try
|
|
36
|
+
to keep trying--you asked for port X--if it's not available, you should be
|
|
37
|
+
told so. Thus, users of Receivers should now implement this if they so desire.
|
|
38
|
+
* RTP::Receiver users are no longer required to tell the object what type of
|
|
39
|
+
IP addressing to use--this is now inferred from the IP address given to use.
|
|
40
|
+
Default is set to 0.0.0.0; if an address in the range 224.0.0.0 -
|
|
41
|
+
239.255.255.255 is given, multicasat settings are assumed.
|
|
42
|
+
|
|
1
43
|
=== 0.0.1 / 2012-03-02
|
|
2
44
|
|
|
3
45
|
* Initial release.
|
data/README.rdoc
CHANGED
|
@@ -5,40 +5,85 @@ http://github.com/turboladen/rtp
|
|
|
5
5
|
== DESCRIPTION
|
|
6
6
|
|
|
7
7
|
This is a RTP library that provides the most basic features of the Real-Time
|
|
8
|
-
Transport Protocol (RTP, {RFC 3550}[http://tools.ietf.org/html/rfc3550]. While
|
|
8
|
+
Transport Protocol (RTP, {RFC 3550}[http://tools.ietf.org/html/rfc3550]). While
|
|
9
9
|
the protocol allows for extensions that define new ways for transporting encoded
|
|
10
10
|
audio/video, this library doesn't yet deal with any specific media types. My
|
|
11
11
|
current goal is simply for transporting a single RTP stream of data and allowing
|
|
12
|
-
you to
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
you to inspect its packets.
|
|
13
|
+
|
|
14
|
+
== FEATURES/PROBLEMS
|
|
15
|
+
|
|
16
|
+
Features:
|
|
17
|
+
|
|
18
|
+
* Receive a single RTP stream over UDP or TCP, unicast or multicast
|
|
19
|
+
* Parse RTP packet headers
|
|
20
|
+
* Inspect packets on the fly
|
|
16
21
|
|
|
17
22
|
Future goals will include:
|
|
18
23
|
|
|
19
|
-
*
|
|
20
|
-
* Implementing RTCP
|
|
24
|
+
* RTCP
|
|
21
25
|
* Multiplexed sessions
|
|
22
26
|
* Decoding of basic audio/video codec headers
|
|
23
27
|
* Decoding of some popular A/V extension headers (h.264, et al.)
|
|
24
28
|
* An easy way to hand over your data to an app that will decode your A/V stream(s)
|
|
25
|
-
|
|
26
|
-
== FEATURES/PROBLEMS
|
|
27
|
-
|
|
28
|
-
Features:
|
|
29
|
-
|
|
30
|
-
* Receive a single RTP stream over UDP
|
|
31
|
-
* Parse RTP packet headers
|
|
29
|
+
* Sending an RTP stream
|
|
32
30
|
|
|
33
31
|
== SYNOPSIS
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
=== Streaming to file
|
|
34
|
+
|
|
35
|
+
With its default settings, an {RTP::Receiver} will open a UDP socket on port 6790,
|
|
36
|
+
within a Thread, on 0.0.0.0 and capture the data it receives to a file.
|
|
37
|
+
|
|
38
|
+
receiver = RTP::Receiver.new # Uses a Tempfile by default
|
|
39
|
+
receiver.rtp_port # => 6790
|
|
40
|
+
receiver.transport_protocol # => :UDP
|
|
41
|
+
receiver.multicast? # => false
|
|
42
|
+
receiver.unicast? # => true
|
|
43
|
+
receiver.start
|
|
44
|
+
sleep 5
|
|
45
|
+
receiver.stop
|
|
46
|
+
receiver.capture_file.size # => 20040
|
|
47
|
+
|
|
48
|
+
To init a Receiver with some other options...
|
|
49
|
+
|
|
50
|
+
options = {
|
|
51
|
+
rtp_port: 9000,
|
|
52
|
+
transport_protocol: :TCP,
|
|
53
|
+
ip_address: '239.255.255.251', # Multicast!
|
|
54
|
+
strip_headers: true, # Strips RTP headers before writing to file
|
|
55
|
+
capture_file: File.new('data.rtp', 'wb')
|
|
56
|
+
}
|
|
57
|
+
receiver = RTP::Receiver.new(options)
|
|
58
|
+
receiver.start
|
|
59
|
+
sleep 5
|
|
60
|
+
receiver.stop
|
|
61
|
+
receiver.capture_file.size # => 18848
|
|
62
|
+
|
|
63
|
+
=== Packet inspecting
|
|
64
|
+
|
|
65
|
+
To inspect (and do what you like with) packets as they come in...
|
|
66
|
+
|
|
67
|
+
receiver = RTP::Receiver.new
|
|
68
|
+
receiver.start do |packet|
|
|
69
|
+
puts "Sequence number: #{packet.sequence_number}" # => Sequence number: 2931830
|
|
70
|
+
end
|
|
71
|
+
sleep 5
|
|
72
|
+
receiver.stop
|
|
73
|
+
receiver.capture_file.size # => 0
|
|
74
|
+
|
|
75
|
+
Notice how no data was written to file when the block was passed to +#start+. The
|
|
76
|
+
idea there is to save on I/O; if you're inspecting packets, it's not fair to
|
|
77
|
+
assume you want to save the data to a file. If you do, however, in that case,
|
|
78
|
+
you can simply do so within the block.
|
|
36
79
|
|
|
37
80
|
== REQUIREMENTS
|
|
38
81
|
|
|
39
82
|
* (Tested) Rubies
|
|
40
83
|
* 1.9.2
|
|
41
84
|
* 1.9.3
|
|
85
|
+
* JRuby 1.9 mode
|
|
86
|
+
* Rubinius 1.9 mode
|
|
42
87
|
* RubyGems:
|
|
43
88
|
* bindata
|
|
44
89
|
* log_switch
|
data/Rakefile
CHANGED
|
@@ -5,11 +5,16 @@ require 'yard'
|
|
|
5
5
|
# Load all extra rake task definitions
|
|
6
6
|
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].each { |ext| load ext }
|
|
7
7
|
|
|
8
|
-
task default: :install
|
|
9
8
|
|
|
10
|
-
YARD::Rake::YardocTask.new
|
|
11
|
-
|
|
9
|
+
YARD::Rake::YardocTask.new do |t|
|
|
10
|
+
t.files = %w(lib/**/*.rb - *.rdoc)
|
|
11
|
+
t.options = %w[--private --protected --verbose]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
RSpec::Core::RakeTask.new do |t|
|
|
15
|
+
t.ruby_opts = %w[-w]
|
|
16
|
+
end
|
|
12
17
|
|
|
13
18
|
# Alias for rubygems-test
|
|
14
19
|
task test: :spec
|
|
15
|
-
|
|
20
|
+
task default: :test
|
data/lib/rtp.rb
CHANGED
data/lib/rtp/logger.rb
ADDED
data/lib/rtp/packet.rb
CHANGED
|
@@ -1,25 +1,56 @@
|
|
|
1
1
|
require 'bindata'
|
|
2
2
|
|
|
3
3
|
module RTP
|
|
4
|
+
|
|
5
|
+
# Decodes a single RTP packet into a Hash, so the packet can inspected and
|
|
6
|
+
# used accordingly. Form more info on types, see
|
|
7
|
+
# {bindata}[http://bindata.rubyforge.org].
|
|
4
8
|
class Packet < BinData::Record
|
|
5
9
|
endian :big
|
|
6
10
|
|
|
11
|
+
#---------------------------------------------------------------------------
|
|
12
|
+
# RTP Header
|
|
13
|
+
#---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
# @return [BinData::Bit2]
|
|
7
16
|
bit2 :version
|
|
17
|
+
|
|
18
|
+
# @return [BinData::Bit1]
|
|
8
19
|
bit1 :padding
|
|
20
|
+
|
|
21
|
+
# @return [BinData::Bit1]
|
|
9
22
|
bit1 :extension
|
|
23
|
+
|
|
24
|
+
# @return [BinData::Bit4]
|
|
10
25
|
bit4 :csrc_count
|
|
11
26
|
|
|
27
|
+
# @return [BinData::Bit1]
|
|
12
28
|
bit1 :marker
|
|
29
|
+
|
|
30
|
+
# @return [BinData::Bit7]
|
|
13
31
|
bit7 :payload_type
|
|
14
32
|
|
|
33
|
+
# @return [BinData::Uint16be]
|
|
15
34
|
uint16 :sequence_number
|
|
35
|
+
|
|
36
|
+
# @return [BinData::Uint32be]
|
|
16
37
|
uint32 :timestamp
|
|
38
|
+
|
|
39
|
+
# @return [BinData::Uint32be]
|
|
17
40
|
uint32 :ssrc_id
|
|
41
|
+
|
|
42
|
+
# @return [BinData::Array]
|
|
18
43
|
array :csrc_ids, :type => :uint32, :initial_length => lambda { csrc_count }
|
|
19
44
|
|
|
45
|
+
#---------------------------------------------------------------------------
|
|
20
46
|
# Extension header is variable length if :extension == 1
|
|
21
|
-
|
|
22
|
-
|
|
47
|
+
#---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
# @return [BinData::Uint16be]
|
|
50
|
+
uint16 :extension_id, onlyif: :has_extension?
|
|
51
|
+
|
|
52
|
+
# @return [BinData::Uint16be]
|
|
53
|
+
uint16 :extension_length, onlyif: :has_extension?
|
|
23
54
|
|
|
24
55
|
=begin
|
|
25
56
|
# h.264 payload
|
|
@@ -37,7 +68,12 @@ module RTP
|
|
|
37
68
|
bit5 :nal_unit_payload_type
|
|
38
69
|
=end
|
|
39
70
|
count_bytes_remaining :bytes_remaining
|
|
71
|
+
|
|
72
|
+
# @return [BinData::String]
|
|
40
73
|
string :rtp_payload, read_length: lambda { bytes_remaining }
|
|
74
|
+
|
|
75
|
+
def has_extension?
|
|
76
|
+
extension == 1
|
|
77
|
+
end
|
|
41
78
|
end
|
|
42
79
|
end
|
|
43
|
-
|
data/lib/rtp/receiver.rb
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
require 'tempfile'
|
|
2
2
|
require 'socket'
|
|
3
|
+
require 'timeout'
|
|
3
4
|
|
|
4
|
-
require_relative '
|
|
5
|
+
require_relative 'logger'
|
|
5
6
|
require_relative 'error'
|
|
6
7
|
require_relative 'packet'
|
|
7
8
|
|
|
9
|
+
|
|
8
10
|
module RTP
|
|
9
11
|
|
|
10
|
-
# Objects of this type
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# In this version, objects of this type don't do much other than just capture
|
|
15
|
-
# the data to a file; in later versions, objects of this type will be able
|
|
16
|
-
# to provide a "sink" and allow for ensuring that the received RTP packets
|
|
17
|
-
# will be reassembled in the correct order, as they're written to file
|
|
18
|
-
# (objects of this type don't don't currently allow for checking RTP sequence
|
|
19
|
-
# numbers on the data that's been received).
|
|
12
|
+
# Objects of this type receive RTP data over a socket and either save them to
|
|
13
|
+
# a file, or yield the packets to a given block. This is useful with other
|
|
14
|
+
# protocols, like RTSP.
|
|
20
15
|
class Receiver
|
|
16
|
+
include LogSwitch::Mixin
|
|
21
17
|
|
|
22
18
|
# Name of the file the data will be captured to unless #rtp_file is set.
|
|
23
19
|
DEFAULT_CAPFILE_NAME = "rtp_capture.raw"
|
|
@@ -25,196 +21,303 @@ module RTP
|
|
|
25
21
|
# Maximum number of bytes to receive on the socket.
|
|
26
22
|
MAX_BYTES_TO_RECEIVE = 1500
|
|
27
23
|
|
|
28
|
-
#
|
|
29
|
-
|
|
24
|
+
# TTL value that will be used if receiving on a multicast socket.
|
|
25
|
+
MULTICAST_TTL = 4
|
|
26
|
+
|
|
27
|
+
# @return [File] The file to capture the RTP data to.
|
|
28
|
+
attr_reader :capture_file
|
|
30
29
|
|
|
31
|
-
# @
|
|
32
|
-
|
|
33
|
-
attr_accessor :rtp_file
|
|
30
|
+
# @return [Array<Time>] The packet receipt timestamps.
|
|
31
|
+
attr_reader :packet_timestamps
|
|
34
32
|
|
|
35
|
-
# @
|
|
36
|
-
|
|
33
|
+
# @return [Fixnum] The port on which to capture RTP data.
|
|
34
|
+
attr_reader :rtp_port
|
|
37
35
|
|
|
38
|
-
# @return [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# @param [Fixnum] rtp_port The port on which to capture the RTP data.
|
|
42
|
-
# @return [Fixnum]
|
|
43
|
-
attr_accessor :rtp_port
|
|
36
|
+
# @return [Fixnum] Added for clarifying the roles of ports; not
|
|
37
|
+
# currently used though.
|
|
38
|
+
attr_reader :rtcp_port
|
|
44
39
|
|
|
45
|
-
# @
|
|
46
|
-
#
|
|
40
|
+
# @return [Symbol] The type of socket to use for capturing the RTP data.
|
|
41
|
+
# +:UDP+ or +:TCP+.
|
|
47
42
|
attr_accessor :transport_protocol
|
|
48
43
|
|
|
49
|
-
# @
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# @
|
|
54
|
-
# the
|
|
55
|
-
# @
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
# @return [String] The IP address to receive RTP data on.
|
|
45
|
+
attr_accessor :ip_address
|
|
46
|
+
|
|
47
|
+
# @param [Hash] options
|
|
48
|
+
# @option options [Fixnum] :rtp_port The port on which to capture RTP data.
|
|
49
|
+
# +rtcp_port+ will be set to the next port above this.
|
|
50
|
+
# @option options [Symbol] :transport_protocol The type of socket to use for
|
|
51
|
+
# capturing the data. +:UDP+ or +:TCP+.
|
|
52
|
+
# @option options [String] :ip_address The IP address to open the socket on.
|
|
53
|
+
# If this is a multicast address, multicast options will be set.
|
|
54
|
+
# @option options [Boolean] :strip_headers If set to true, RTP headers will
|
|
55
|
+
# be stripped from packets before they're written to the capture file.
|
|
56
|
+
# @option options [File] :capture_file The file object to capture the RTP
|
|
57
|
+
# data to.
|
|
58
|
+
def initialize(options={})
|
|
59
|
+
@rtp_port = options[:rtp_port] || 6970
|
|
60
|
+
@rtcp_port = @rtp_port + 1
|
|
61
|
+
@transport_protocol = options[:transport_protocol] || :UDP
|
|
62
|
+
@ip_address = options[:ip_address] || '0.0.0.0'
|
|
63
|
+
@strip_headers = options[:strip_headers] || false
|
|
64
|
+
@capture_file = options[:capture_file] ||
|
|
65
|
+
Tempfile.new(DEFAULT_CAPFILE_NAME)
|
|
66
|
+
|
|
67
|
+
at_exit do
|
|
68
|
+
unless @capture_file.closed?
|
|
69
|
+
log "Closing and deleting capture capture file..."
|
|
70
|
+
@capture_file.close
|
|
71
|
+
@capture_file.unlink
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
@socket = nil
|
|
62
76
|
@listener = nil
|
|
63
|
-
@
|
|
64
|
-
@
|
|
65
|
-
@
|
|
77
|
+
@packet_writer = nil
|
|
78
|
+
@packets = Queue.new
|
|
79
|
+
@packet_timestamps = []
|
|
66
80
|
end
|
|
67
81
|
|
|
68
|
-
#
|
|
82
|
+
# Starts the packet writer (buffer) and listener.
|
|
69
83
|
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
# If a block is given, this will yield each parsed packet as an RTP::Packet.
|
|
85
|
+
# This lets you inspect packets as they come in:
|
|
86
|
+
# @example
|
|
87
|
+
# receiver = RTP::Receiver.new
|
|
88
|
+
# receiver.start do |packet|
|
|
89
|
+
# puts packet["sequence_number"]
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# @yield [RTP::Packet] Each parsed packet that comes in over the wire.
|
|
93
|
+
#
|
|
94
|
+
# @return [Boolean] true if started successfully.
|
|
95
|
+
def start(&block)
|
|
96
|
+
return false if running?
|
|
97
|
+
log "Starting receiving on port #{@rtp_port}..."
|
|
98
|
+
|
|
99
|
+
@packet_writer = start_packet_writer(&block)
|
|
100
|
+
@packet_writer.abort_on_exception = true
|
|
101
|
+
|
|
102
|
+
@socket = init_socket(@transport_protocol, @rtp_port, @ip_address)
|
|
103
|
+
|
|
104
|
+
@listener = start_listener(@socket)
|
|
105
|
+
@listener.abort_on_exception = true
|
|
106
|
+
|
|
107
|
+
running?
|
|
108
|
+
end
|
|
81
109
|
|
|
82
|
-
|
|
110
|
+
# Stops the listener and packet writer threads.
|
|
111
|
+
#
|
|
112
|
+
# @return [Boolean] true if stopped successfully.
|
|
113
|
+
def stop
|
|
114
|
+
return false if !running?
|
|
115
|
+
|
|
116
|
+
log "Stopping #{self.class} on port #{@rtp_port}..."
|
|
117
|
+
stop_listener
|
|
118
|
+
log "listening? #{listening?}"
|
|
119
|
+
|
|
120
|
+
stop_packet_writer
|
|
121
|
+
log "writing packets? #{writing_packets?}"
|
|
122
|
+
log "running? #{running?}"
|
|
123
|
+
|
|
124
|
+
!running?
|
|
83
125
|
end
|
|
84
126
|
|
|
85
|
-
#
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
start_listener
|
|
127
|
+
# @return [Boolean] true if the listener thread is running; false if not.
|
|
128
|
+
def listening?
|
|
129
|
+
!@listener.nil? ? @listener.alive? : false
|
|
89
130
|
end
|
|
90
131
|
|
|
132
|
+
# @return [Boolean] true if ready to write packets to file.
|
|
133
|
+
def writing_packets?
|
|
134
|
+
!@packet_writer.nil? ? @packet_writer.alive? : false
|
|
135
|
+
end
|
|
91
136
|
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def start_listener
|
|
97
|
-
return @listener if listening?
|
|
137
|
+
# @return [Boolean] true if the Receiver is listening and writing packets.
|
|
138
|
+
def running?
|
|
139
|
+
listening? && writing_packets?
|
|
140
|
+
end
|
|
98
141
|
|
|
99
|
-
|
|
100
|
-
|
|
142
|
+
# Updates the rtp_port and sets the rtcp_port to be this +1.
|
|
143
|
+
def rtp_port=(port)
|
|
144
|
+
@rtp_port = port
|
|
145
|
+
@rtcp_port = @rtp_port + 1
|
|
146
|
+
end
|
|
101
147
|
|
|
148
|
+
# @return [Boolean] true if +ip_address+ is a multicast address or not.
|
|
149
|
+
def multicast?
|
|
150
|
+
first_octet = @ip_address.match(/^(\d\d\d?)/).to_s.to_i
|
|
151
|
+
first_octet >= 224 && first_octet <= 239
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @return [Boolean] true if +ip_address+ is a unicast address or not.
|
|
155
|
+
def unicast?
|
|
156
|
+
!multicast?
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @return [Symbol] The IP addressing type to use for capturing the data.
|
|
160
|
+
# +:multicast+ or +:unicast:.
|
|
161
|
+
def ip_addressing_type
|
|
162
|
+
multicast? ? :multicast : :unicast
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
# This starts a new Thread for reading packets off of the list of packets
|
|
168
|
+
# that has been read in by the listener. If no block is given, this writes
|
|
169
|
+
# all received packets (in the @packets Queue) to the +capture_file+. If a
|
|
170
|
+
# block is given, it yields each packet, parsed as an RTP::Packet. If
|
|
171
|
+
# +strip_headers+ is set, it only writes/yields the RTP payload to the file.
|
|
172
|
+
#
|
|
173
|
+
# @yield [RTP::Packet] Each parsed packet that comes in over the wire.
|
|
174
|
+
# @return [Thread] The packet writer thread.
|
|
175
|
+
def start_packet_writer
|
|
176
|
+
return @packet_writer if @packet_writer
|
|
177
|
+
|
|
178
|
+
# If a block is given for packet inspection, perhaps we should save
|
|
179
|
+
# some I/O ano not write the packet to file?
|
|
180
|
+
Thread.start do
|
|
102
181
|
loop do
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
@sequence_list << packet["sequence_number"].to_i
|
|
112
|
-
|
|
113
|
-
if @strip_headers
|
|
114
|
-
@payload_data[packet["sequence_number"].to_i] = packet["rtp_payload"]
|
|
115
|
-
else
|
|
116
|
-
@payload_data[packet["sequence_number"].to_i] = data
|
|
117
|
-
end
|
|
118
|
-
rescue Errno::EAGAIN;
|
|
119
|
-
write_buffer_to_file
|
|
120
|
-
end # rescue error when no data is available to read.
|
|
182
|
+
packet = RTP::Packet.read(@packets.pop)
|
|
183
|
+
data_to_write = @strip_headers ? packet.rtp_payload : packet
|
|
184
|
+
|
|
185
|
+
if block_given?
|
|
186
|
+
yield data_to_write
|
|
187
|
+
else
|
|
188
|
+
@capture_file.write(data_to_write)
|
|
189
|
+
end
|
|
121
190
|
end
|
|
122
191
|
end
|
|
192
|
+
end
|
|
123
193
|
|
|
124
|
-
|
|
125
|
-
|
|
194
|
+
# Initializes a socket of the requested type.
|
|
195
|
+
#
|
|
196
|
+
# @param [Symbol] protocol The protocol on which to receive RTP data.
|
|
197
|
+
# @param [Fixnum] port The port on which to receive RTP data.
|
|
198
|
+
# @param [String] ip_address The IP on which to receive RTP data.
|
|
199
|
+
# @return [UDPSocket, TCPServer]
|
|
200
|
+
# @raise [RTP::Error] If +protocol+ was not set to +:UDP+ or +:TCP+.
|
|
201
|
+
def init_socket(protocol, port, ip_address)
|
|
202
|
+
log "Setting up #{protocol} socket on #{ip_address}:#{port}"
|
|
126
203
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
204
|
+
if protocol == :UDP
|
|
205
|
+
socket = UDPSocket.open
|
|
206
|
+
socket.bind(ip_address, port)
|
|
207
|
+
elsif protocol == :TCP
|
|
208
|
+
socket = TCPServer.new(ip_address, port)
|
|
209
|
+
else
|
|
210
|
+
raise RTP::Error,
|
|
211
|
+
"Unknown protocol requested: #{protocol}. Options are :TCP or :UDP"
|
|
131
212
|
end
|
|
132
213
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
end
|
|
214
|
+
set_socket_time_options(socket)
|
|
215
|
+
setup_multicast_socket(socket, multicast_address) if multicast?
|
|
136
216
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
217
|
+
@rtp_port = port
|
|
218
|
+
log "#{protocol} server setup to receive on port #{@rtp_port}"
|
|
219
|
+
|
|
220
|
+
socket
|
|
140
221
|
end
|
|
141
222
|
|
|
142
|
-
#
|
|
223
|
+
# Starts the thread that receives the RTP data, then takes that data and
|
|
224
|
+
# pushes it on to +@packets+ for processing.
|
|
143
225
|
#
|
|
144
|
-
# @
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
226
|
+
# @param [IPSocket] socket The socket to listen on.
|
|
227
|
+
# @return [Thread] The listener thread.
|
|
228
|
+
def start_listener(socket)
|
|
229
|
+
return @listener if @listener
|
|
148
230
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
231
|
+
Thread.start(socket) do
|
|
232
|
+
loop do
|
|
233
|
+
msg = socket.recvmsg(MAX_BYTES_TO_RECEIVE)
|
|
234
|
+
data = msg.first
|
|
235
|
+
log "Received data at size: #{data.size}"
|
|
236
|
+
|
|
237
|
+
log "RTP timestamp from socket info: #{msg.last.timestamp}"
|
|
238
|
+
@packet_timestamps << msg.last.timestamp
|
|
239
|
+
@packets << data
|
|
240
|
+
end
|
|
241
|
+
end
|
|
156
242
|
end
|
|
157
243
|
|
|
158
244
|
# Kills the +@listener+ thread and sets the variable to nil.
|
|
245
|
+
#
|
|
246
|
+
# @return [Boolean] true if it stopped listening.
|
|
159
247
|
def stop_listener
|
|
160
|
-
|
|
248
|
+
log "Stopping listener..."
|
|
249
|
+
@socket.close if @socket
|
|
161
250
|
@listener.kill if listening?
|
|
162
251
|
@listener = nil
|
|
252
|
+
log "Listener stopped."
|
|
253
|
+
|
|
254
|
+
!listening?
|
|
163
255
|
end
|
|
164
256
|
|
|
165
|
-
#
|
|
257
|
+
# Waits for all packets to be written out before killing. AFter 10 seconds
|
|
258
|
+
# it'll force close the writer.
|
|
166
259
|
#
|
|
167
|
-
# @
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
260
|
+
# @return [Boolean] true if it stopped the packet writer.
|
|
261
|
+
def stop_packet_writer
|
|
262
|
+
log "Stopping packet writer..."
|
|
263
|
+
wait_for = 10
|
|
171
264
|
|
|
172
265
|
begin
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
RTP.log "RTP port #{port} in use, trying #{port + 1}..."
|
|
180
|
-
port += 1
|
|
181
|
-
port_retries += 1
|
|
182
|
-
retry until port_retries == MAX_PORT_NUMBER_RETRIES + 1
|
|
183
|
-
port = 9000
|
|
184
|
-
raise
|
|
266
|
+
timeout(wait_for) do
|
|
267
|
+
sleep 0.2 until @packets.empty?
|
|
268
|
+
end
|
|
269
|
+
rescue Timeout::Error
|
|
270
|
+
log "Packet buffer not empty after #{wait_for} seconds. Trying to stop listener..."
|
|
271
|
+
stop_listener
|
|
185
272
|
end
|
|
186
273
|
|
|
187
|
-
@
|
|
188
|
-
|
|
274
|
+
@packet_writer.kill if writing_packets?
|
|
275
|
+
@packet_writer = nil
|
|
276
|
+
log "Packet writer stopped."
|
|
277
|
+
|
|
278
|
+
@capture_file.close
|
|
189
279
|
|
|
190
|
-
|
|
280
|
+
!writing_packets?
|
|
191
281
|
end
|
|
192
282
|
|
|
193
|
-
# Sets
|
|
283
|
+
# Sets SO_TIMESTAMP socket option to true. Sets SO_RCVTIMEO to 2.
|
|
194
284
|
#
|
|
195
|
-
# @param [
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
285
|
+
# @param [Socket] socket The Socket to set options on.
|
|
286
|
+
def set_socket_time_options(socket)
|
|
287
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_TIMESTAMP, true)
|
|
288
|
+
optval = [0, 1].pack("l_2")
|
|
289
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval)
|
|
290
|
+
end
|
|
199
291
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
raise
|
|
212
|
-
end
|
|
292
|
+
# Sets Socket options to allow for multicasting. If ENV["RUBY_UPNP_ENV"] is
|
|
293
|
+
# equal to "testing", then it doesn't turn off multicast looping.
|
|
294
|
+
#
|
|
295
|
+
# @param [Socket] socket The socket to set the options on.
|
|
296
|
+
# @param [String] multicast_address The IP address to set the options on.
|
|
297
|
+
def setup_multicast_socket(socket, multicast_address)
|
|
298
|
+
set_membership(socket,
|
|
299
|
+
IPAddr.new(multicast_address).hton + IPAddr.new('0.0.0.0').hton)
|
|
300
|
+
set_multicast_ttl(socket, MULTICAST_TTL)
|
|
301
|
+
set_ttl(socket, MULTICAST_TTL)
|
|
302
|
+
end
|
|
213
303
|
|
|
214
|
-
|
|
215
|
-
|
|
304
|
+
# @param [Socket] socket The socket to set the options on.
|
|
305
|
+
# @param [String] membership The network byte ordered String that represents
|
|
306
|
+
# the IP(s) that should join the membership group.
|
|
307
|
+
def set_membership(socket, membership)
|
|
308
|
+
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, membership)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# @param [Socket] socket The socket to set the options on.
|
|
312
|
+
# @param [Fixnum] ttl TTL to set IP_MULTICAST_TTL to.
|
|
313
|
+
def set_multicast_ttl(socket, ttl)
|
|
314
|
+
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, [ttl].pack('i'))
|
|
315
|
+
end
|
|
216
316
|
|
|
217
|
-
|
|
317
|
+
# @param [Socket] socket The socket to set the options on.
|
|
318
|
+
# @param [Fixnum] ttl TTL to set IP_TTL to.
|
|
319
|
+
def set_ttl(socket, ttl)
|
|
320
|
+
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, [ttl].pack('i'))
|
|
218
321
|
end
|
|
219
322
|
end
|
|
220
|
-
end
|
|
323
|
+
end
|