rtsp 0.0.1.alpha → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +3 -0
- data/.infinity_test +1 -1
- data/.yardopts +4 -0
- data/ChangeLog.rdoc +9 -0
- data/Gemfile +15 -6
- data/Gemfile.lock +78 -40
- data/LICENSE.rdoc +20 -0
- data/PostInstall.txt +0 -3
- data/README.rdoc +85 -36
- data/Rakefile +33 -49
- data/bin/rtsp_client +129 -0
- data/features/client_changes_state.feature +58 -0
- data/features/client_requests.feature +27 -0
- data/features/control_streams_as_client.feature +26 -0
- data/features/step_definitions/client_changes_state_steps.rb +46 -0
- data/features/step_definitions/client_requests_steps.rb +74 -0
- data/features/step_definitions/control_streams_as_client_steps.rb +34 -0
- data/features/support/env.rb +31 -29
- data/features/support/hooks.rb +3 -0
- data/gemspec.yml +30 -0
- data/lib/ext/logger.rb +8 -0
- data/lib/rtsp.rb +3 -6
- data/lib/rtsp/capturer.rb +105 -0
- data/lib/rtsp/client.rb +446 -204
- data/lib/rtsp/error.rb +6 -0
- data/lib/rtsp/global.rb +63 -0
- data/lib/rtsp/helpers.rb +28 -0
- data/lib/rtsp/message.rb +270 -0
- data/lib/rtsp/response.rb +89 -29
- data/lib/rtsp/transport_parser.rb +64 -0
- data/lib/rtsp/version.rb +4 -0
- data/nsm_test.rb +26 -0
- data/rtsp.gemspec +284 -0
- data/sarix_test.rb +23 -0
- data/soma_test.rb +39 -0
- data/spec/rtsp/client_spec.rb +302 -27
- data/spec/rtsp/helpers_spec.rb +53 -0
- data/spec/rtsp/message_spec.rb +420 -0
- data/spec/rtsp/response_spec.rb +144 -58
- data/spec/rtsp/transport_parser_spec.rb +54 -0
- data/spec/rtsp_spec.rb +3 -3
- data/spec/spec_helper.rb +66 -7
- data/spec/support/fake_rtsp_server.rb +123 -0
- data/tasks/metrics.rake +27 -0
- data/tasks/roodi_config.yml +14 -0
- data/tasks/stats.rake +12 -0
- metadata +174 -183
- data/.autotest +0 -23
- data/History.txt +0 -4
- data/Manifest.txt +0 -26
- data/bin/rtsp +0 -121
- data/features/step_definitions/stream_steps.rb +0 -50
- data/features/stream.feature +0 -17
- data/features/support/common.rb +0 -1
- data/features/support/world.rb +0 -1
- data/lib/rtsp/request_messages.rb +0 -104
- data/lib/rtsp/status_code.rb +0 -7
data/bin/rtsp_client
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib/')
|
8
|
+
require 'rtsp/client'
|
9
|
+
|
10
|
+
optparse = OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: #{__FILE__} [options] url"
|
12
|
+
|
13
|
+
#----------------------------------------------------------------------------
|
14
|
+
# Turn on logging
|
15
|
+
RTSP::Client.configure { |c| c.log = false }
|
16
|
+
opts.on('-d', '--debug', "Turn on RTSP::Client logging.") do
|
17
|
+
RTSP::Client.configure { |c| c.log = true }
|
18
|
+
end
|
19
|
+
|
20
|
+
#----------------------------------------------------------------------------
|
21
|
+
# Get description
|
22
|
+
opts.on('--describe [URL]', "Get description from the given URL.") do |url|
|
23
|
+
if url.nil?
|
24
|
+
puts "Must pass in a URL."
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
|
28
|
+
rtsp_client = RTSP::Client.new(url)
|
29
|
+
p rtsp_client.describe.body.inspect
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
#----------------------------------------------------------------------------
|
34
|
+
# Show available tracks
|
35
|
+
opts.on('--show-tracks [URL]', "Show available tracks from the given URL.") do |url|
|
36
|
+
if url.nil?
|
37
|
+
puts "Must pass in a URL."
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
|
41
|
+
rtsp_client = RTSP::Client.new(url)
|
42
|
+
rtsp_client.describe
|
43
|
+
puts "Aggregate control track: #{rtsp_client.aggregate_control_track}"
|
44
|
+
puts "Media control tracks: #{rtsp_client.media_control_tracks}"
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
|
48
|
+
#----------------------------------------------------------------------------
|
49
|
+
# Stream
|
50
|
+
opts.on('--stream [URL]', "Show available tracks from the given URL.") do |url|
|
51
|
+
if url.nil?
|
52
|
+
puts "Must pass in a URL."
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
rtsp_client = RTSP::Client.new(url) do |connection, capturer|
|
57
|
+
capturer.rtp_file = File.open("#{Dir.pwd}/rtsp_client_stream.rtp", "wb")
|
58
|
+
end
|
59
|
+
|
60
|
+
begin
|
61
|
+
puts "Getting server options..."
|
62
|
+
response = rtsp_client.options
|
63
|
+
puts "\tServer: #{response.server}"
|
64
|
+
puts "\tSupported methods: #{response.public}"
|
65
|
+
puts ""
|
66
|
+
|
67
|
+
puts "Getting description..."
|
68
|
+
response = rtsp_client.describe
|
69
|
+
puts "\tcache_control: #{response.cache_control}" if response.respond_to? "cache_control"
|
70
|
+
puts "\tdate: #{response.date}"
|
71
|
+
puts "\texpires: #{response.expires}" if response.respond_to? "expires"
|
72
|
+
puts "\tcontent_type: #{response.content_type}"
|
73
|
+
puts "\tcontent_base: #{response.content_base}"
|
74
|
+
puts "\tAggregate control track: #{rtsp_client.aggregate_control_track}"
|
75
|
+
puts "\tMedia control tracks: #{rtsp_client.media_control_tracks}"
|
76
|
+
puts ""
|
77
|
+
|
78
|
+
puts "Setting up #{rtsp_client.media_control_tracks.first}"
|
79
|
+
response = rtsp_client.setup rtsp_client.media_control_tracks.first
|
80
|
+
puts "\ttransport: #{response.transport}"
|
81
|
+
puts ""
|
82
|
+
|
83
|
+
if response.message == "OK"
|
84
|
+
puts "Playing #{rtsp_client.aggregate_control_track}"
|
85
|
+
r = rtsp_client.play rtsp_client.aggregate_control_track
|
86
|
+
puts "\trange: #{r.range}"
|
87
|
+
puts "\trtp_info: #{r.rtp_info}"
|
88
|
+
puts ""
|
89
|
+
|
90
|
+
|
91
|
+
1...5.times do
|
92
|
+
print "."
|
93
|
+
sleep 1
|
94
|
+
end
|
95
|
+
puts ""
|
96
|
+
|
97
|
+
if rtsp_client.capturer.rtp_file.size.zero?
|
98
|
+
puts "Captured 0 bytes. Your firewall might be blocking the RTP traffic on port #{rtsp_client.capturer.rtp_port}."
|
99
|
+
else
|
100
|
+
puts "RTP data captured to file: #{rtsp_client.capturer.rtp_file}"
|
101
|
+
end
|
102
|
+
|
103
|
+
puts "Tearing down..."
|
104
|
+
r2 = rtsp_client.teardown(rtsp_client.aggregate_control_track)
|
105
|
+
puts "\tconnection: #{r2.connection}" if r2.respond_to? "connection"
|
106
|
+
else
|
107
|
+
puts "Play call returned: #{response.message}. Exiting."
|
108
|
+
exit
|
109
|
+
end
|
110
|
+
rescue RTSP::Error => ex
|
111
|
+
puts ex.backtrace
|
112
|
+
puts ex.message
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#----------------------------------------------------------------------------
|
117
|
+
# Help
|
118
|
+
opts.on('-h', '--help', "You're looking at it.") do
|
119
|
+
puts opts
|
120
|
+
exit
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
if ARGV.size.zero?
|
125
|
+
puts optparse.help if ARGV.size.zero?
|
126
|
+
exit
|
127
|
+
end
|
128
|
+
optparse.parse!
|
129
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
Feature: Client changes state
|
2
|
+
As an RTSP client user
|
3
|
+
I want to monitor the state of my client
|
4
|
+
So that I can be sure of what my client is doing at any time
|
5
|
+
|
6
|
+
Scenario Outline: State doesn't change after certain requests
|
7
|
+
Given I haven't made any RTSP requests
|
8
|
+
When I issue an "<request_type>" request with "<parameters>"
|
9
|
+
Then the state stays the same
|
10
|
+
Examples:
|
11
|
+
| request_type | parameters |
|
12
|
+
| options | |
|
13
|
+
| describe | |
|
14
|
+
|
15
|
+
Scenario Outline: State changes from Init
|
16
|
+
Given I haven't made any RTSP requests
|
17
|
+
When I issue an "<request_type>" request with "<parameters>"
|
18
|
+
Then the state changes to "<state_result>"
|
19
|
+
Examples:
|
20
|
+
| request_type | parameters | state_result |
|
21
|
+
| setup | url | ready |
|
22
|
+
| teardown | url | init |
|
23
|
+
|
24
|
+
Scenario Outline: State changes from Ready
|
25
|
+
Given I have set up a stream
|
26
|
+
When I issue an "<request_type>" request with "<parameters>"
|
27
|
+
Then the state changes to "<state_result>"
|
28
|
+
Examples:
|
29
|
+
| request_type | parameters | state_result |
|
30
|
+
| play | url | playing |
|
31
|
+
| record | url | recording |
|
32
|
+
| teardown | url | init |
|
33
|
+
| setup | url | ready |
|
34
|
+
|
35
|
+
Scenario Outline: State changes from Playing
|
36
|
+
Given I have set up a stream
|
37
|
+
And I have started playing a stream
|
38
|
+
When I issue an "<request_type>" request with "<parameters>"
|
39
|
+
Then the state changes to "<state_result>"
|
40
|
+
Examples:
|
41
|
+
| request_type | parameters | state_result |
|
42
|
+
| pause | url | ready |
|
43
|
+
| teardown | url | init |
|
44
|
+
| play | url | playing |
|
45
|
+
| setup | url | playing |
|
46
|
+
|
47
|
+
Scenario Outline: State changes from Recording
|
48
|
+
Given I have set up a stream
|
49
|
+
And I have started recording a stream
|
50
|
+
When I issue an "<request_type>" request with "<parameters>"
|
51
|
+
Then the state changes to "<state_result>"
|
52
|
+
Examples:
|
53
|
+
| request_type | parameters | state_result |
|
54
|
+
| pause | url | ready |
|
55
|
+
| teardown | url | init |
|
56
|
+
| record | url | recording |
|
57
|
+
| setup | url | recording |
|
58
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Feature: Client request messages
|
2
|
+
As an RTSP client API user
|
3
|
+
I want to make RTSP requests
|
4
|
+
So that I can build a client using these request messages
|
5
|
+
|
6
|
+
Scenario: OPTIONS
|
7
|
+
Given a known RTSP server
|
8
|
+
When I make a "options" request
|
9
|
+
Then I should receive an RTSP response to that OPTIONS request
|
10
|
+
|
11
|
+
Scenario: DESCRIBE
|
12
|
+
Given a known RTSP server
|
13
|
+
When I make a "describe" request
|
14
|
+
Then I should receive an RTSP response to that DESCRIBE request
|
15
|
+
|
16
|
+
Scenario: ANNOUNCE
|
17
|
+
Given a known RTSP server
|
18
|
+
When I make a "announce" request
|
19
|
+
Then I should receive an RTSP response to that ANNOUNCE request
|
20
|
+
|
21
|
+
Scenario: SETUP
|
22
|
+
Given a known RTSP server
|
23
|
+
When I make a "setup" request with headers:
|
24
|
+
| header | value |
|
25
|
+
| transport | RTP/AVP |
|
26
|
+
Then I should receive an RTSP response to that SETUP request
|
27
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Control stream from an RTSP server
|
2
|
+
As an RTSP consumer
|
3
|
+
I want to be able to control RTSP streams from a server
|
4
|
+
So that I can view its contents as I desire
|
5
|
+
|
6
|
+
@wip
|
7
|
+
Scenario: Play single stream
|
8
|
+
Given an RTSP server at "10.221.222.235" and port 9010 and URL ""
|
9
|
+
When I play a stream from that server
|
10
|
+
Then I should not receive any errors
|
11
|
+
And I should receive data on the same port
|
12
|
+
|
13
|
+
Scenario: Play then pause single stream
|
14
|
+
Given an RTSP server at "10.221.222.235" and port 9010 and URL ""
|
15
|
+
When I play a stream from that server for 10 seconds
|
16
|
+
And I pause that stream
|
17
|
+
Then I should not receive data on the same port
|
18
|
+
|
19
|
+
Scenario: Play two streams individually and simultaneously
|
20
|
+
|
21
|
+
Scenario: Play then pause two streams individually and simultaneously
|
22
|
+
|
23
|
+
Scenario: Play two streams using the aggregate control URL
|
24
|
+
|
25
|
+
Scenario: Play then pause two streams using the aggregate control URL
|
26
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Given /^I haven't made any RTSP requests$/ do
|
2
|
+
RTSP::Client.configure { |config| config.log = false }
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^I have set up a stream$/ do
|
6
|
+
@url = "rtsp://fake-rtsp-server/some_path"
|
7
|
+
@client = RTSP::Client.new(@url) do |connection|
|
8
|
+
connection.socket = @fake_server
|
9
|
+
connection.timeout = 3
|
10
|
+
end
|
11
|
+
@client.setup @url
|
12
|
+
@client.session_state.should == :ready
|
13
|
+
end
|
14
|
+
|
15
|
+
Given /^I have started (playing|recording) a stream$/ do |method|
|
16
|
+
if method == "playing"
|
17
|
+
@client.setup @url
|
18
|
+
@client.play @url
|
19
|
+
elsif method == "recording"
|
20
|
+
@client.record @url
|
21
|
+
end
|
22
|
+
@client.session_state.should == method.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
When /^I issue an "([^"]*)" request with "([^"]*)"$/ do |request_type, params|
|
26
|
+
unless @client
|
27
|
+
url = "rtsp://fake-rtsp-server/some_path"
|
28
|
+
@client = RTSP::Client.new(url) do |connection|
|
29
|
+
connection.socket = @fake_server
|
30
|
+
connection.timeout = 3
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@initial_state = @client.session_state
|
35
|
+
params = params.empty? ? {} : params
|
36
|
+
|
37
|
+
@client.send(request_type.to_sym, params)
|
38
|
+
end
|
39
|
+
|
40
|
+
Then /^the state stays the same$/ do
|
41
|
+
@client.session_state.should == @initial_state
|
42
|
+
end
|
43
|
+
|
44
|
+
Then /^the state changes to "([^"]*)"$/ do |new_state|
|
45
|
+
@client.session_state.should == new_state.to_sym
|
46
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
Given /^a known RTSP server$/ do
|
2
|
+
@server_url = "rtsp://64.202.98.91:554/sa.sdp"
|
3
|
+
@client = RTSP::Client.new(@server_url) do |connection|
|
4
|
+
connection.socket = @fake_server
|
5
|
+
connection.timeout = 3
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^I make a "([^"]*)" request$/ do |method|
|
10
|
+
=begin
|
11
|
+
@response = RTSP::Request.execute({
|
12
|
+
:method => method.to_sym,
|
13
|
+
:resource_url => @server_url }
|
14
|
+
)
|
15
|
+
=end
|
16
|
+
@response = @client.send(method.to_sym)
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^I make a "([^"]*)" request with headers:$/ do |method, headers_table|
|
20
|
+
# table is a Cucumber::Ast::Table
|
21
|
+
headers = {}
|
22
|
+
|
23
|
+
headers_table.hashes.each do |hash|
|
24
|
+
header_type = hash["header"].to_sym
|
25
|
+
headers[header_type] = hash["value"]
|
26
|
+
end
|
27
|
+
|
28
|
+
=begin
|
29
|
+
@response = RTSP::Request.execute({
|
30
|
+
:method => method.to_sym,
|
31
|
+
:headers => headers,
|
32
|
+
:resource_url => @server_url }
|
33
|
+
)
|
34
|
+
=end
|
35
|
+
@response = @client.send(method.to_sym, headers)
|
36
|
+
end
|
37
|
+
|
38
|
+
Then /^I should receive an RTSP response to that OPTIONS request$/ do
|
39
|
+
@response.is_a?(RTSP::Response).should be_true
|
40
|
+
@response.code.should == 200
|
41
|
+
@response.message.should == "OK"
|
42
|
+
@response.cseq.should == 1
|
43
|
+
@response.public.should == "DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE"
|
44
|
+
@response.body.should be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
Then /^I should receive an RTSP response to that DESCRIBE request$/ do
|
48
|
+
@response.is_a?(RTSP::Response).should be_true
|
49
|
+
@response.code.should == 200
|
50
|
+
@response.message.should == "OK"
|
51
|
+
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
52
|
+
@response.cseq.should == 1
|
53
|
+
@response.body.is_a?(SDP::Description).should be_true
|
54
|
+
@response.body.protocol_version.should == "0"
|
55
|
+
@response.body.username.should == "-"
|
56
|
+
end
|
57
|
+
|
58
|
+
Then /^I should receive an RTSP response to that ANNOUNCE request$/ do
|
59
|
+
@response.is_a?(RTSP::Response).should be_true
|
60
|
+
@response.code.should == 200
|
61
|
+
@response.message.should == "OK"
|
62
|
+
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
63
|
+
@response.cseq.should == 1
|
64
|
+
end
|
65
|
+
|
66
|
+
Then /^I should receive an RTSP response to that SETUP request$/ do
|
67
|
+
@response.is_a?(RTSP::Response).should be_true
|
68
|
+
@response.code.should == 200
|
69
|
+
@response.message.should == "OK"
|
70
|
+
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
71
|
+
@response.cseq.should == 1
|
72
|
+
@response.transport.should == ""
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Given /^an RTSP server at "([^"]*)" and port (\d+)$/ do |ip_address, port|
|
2
|
+
@rtp_port = port.to_i
|
3
|
+
@client = RTSP::Client.new(ip_address) do |connection, capturer|
|
4
|
+
capturer.rtp_port = @rtp_port
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
Given /^an RTSP server at "([^"]*)" and port (\d+) and URL "([^"]*)"$/ do |ip_address, port, path|
|
9
|
+
uri = "rtsp://#{ip_address}:#{port}#{path}"
|
10
|
+
@rtp_port = port
|
11
|
+
@client = RTSP::Client.new(uri) do |connection, capturer|
|
12
|
+
capturer.rtp_port = @rtp_port
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
When /^I play a stream from that server$/ do
|
17
|
+
@play_result = lambda { @client.play }
|
18
|
+
end
|
19
|
+
|
20
|
+
Then /^I should not receive any errors$/ do
|
21
|
+
@play_result.should_not raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
Then /^I should receive data on the same port$/ do
|
25
|
+
@client.capturer.rtp_file.should_not be_empty
|
26
|
+
end
|
27
|
+
|
28
|
+
Given /^I know what the describe response looks like$/ do
|
29
|
+
@response_text = @fake_server.describe
|
30
|
+
end
|
31
|
+
|
32
|
+
When /^I ask the server to describe$/ do
|
33
|
+
puts @client.describe
|
34
|
+
end
|
data/features/support/env.rb
CHANGED
@@ -1,37 +1,39 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'socket'
|
3
3
|
require 'timeout'
|
4
|
+
require 'cucumber/rspec/doubles'
|
4
5
|
|
5
6
|
$:.unshift(File.dirname(__FILE__) + '/../../lib')
|
6
7
|
require 'rtsp/client'
|
7
8
|
|
9
|
+
$:.unshift(File.dirname(__FILE__) + '/../../spec/support')
|
10
|
+
require 'fake_rtsp_server'
|
8
11
|
|
9
|
-
DESCRIBE_RESPONSE =
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
x-Accept-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
a=x-qt-text-
|
29
|
-
a=x-qt-text-
|
30
|
-
a=
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
a=
|
35
|
-
a=
|
36
|
-
|
37
|
-
RESP
|
12
|
+
DESCRIBE_RESPONSE = %Q{ RTSP/1.0 200 OK\r
|
13
|
+
Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r
|
14
|
+
Cseq: 2\r
|
15
|
+
Cache-Control: no-cache\r
|
16
|
+
Content-length: 406\r
|
17
|
+
Date: Sun, 23 Jan 2011 00:36:45 GMT\r
|
18
|
+
Expires: Sun, 23 Jan 2011 00:36:45 GMT\r
|
19
|
+
Content-Type: application/sdp\r
|
20
|
+
x-Accept-Retransmit: our-retransmit\r
|
21
|
+
x-Accept-Dynamic-Rate: 1\r
|
22
|
+
Content-Base: rtsp://64.202.98.91:554/gs.sdp/\r
|
23
|
+
\r\n\r
|
24
|
+
v=0\r
|
25
|
+
o=- 545877020 467920391 IN IP4 127.0.0.1\r
|
26
|
+
s=Groove Salad from SomaFM [aacPlus]\r
|
27
|
+
i=Downtempo Ambient Groove\r
|
28
|
+
c=IN IP4 0.0.0.0\r
|
29
|
+
t=0 0\r
|
30
|
+
a=x-qt-text-cmt:Orban Opticodec-PC\r\n
|
31
|
+
a=x-qt-text-nam:Groove Salad from SomaFM [aacPlus]\r
|
32
|
+
a=x-qt-text-inf:Downtempo Ambient Groove\r
|
33
|
+
a=control:*\r
|
34
|
+
m=audio 0 RTP/AVP 96\r
|
35
|
+
b=AS:48\r
|
36
|
+
a=rtpmap:96 MP4A-LATM/44100/2\r
|
37
|
+
a=fmtp:96 cpresent=0;config=400027200000\r
|
38
|
+
a=control:trackID=1\r
|
39
|
+
\r\n}
|