rtp 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|