Author: adrian
Date: Sun Jan 31 06:44:42 2010
New Revision: 8430
URL: http://svn.slimdevices.com/jive?rev=8430&view=rev
Log:
Bug: N/A
Description: update rtmp handler to include amf0 serialisation code so that
applets can use the handler directly without server support
Modified:
7.5/trunk/squeezeplay/src/squeezeplay/share/jive/audio/Rtmp.lua
Modified: 7.5/trunk/squeezeplay/src/squeezeplay/share/jive/audio/Rtmp.lua
URL:
http://svn.slimdevices.com/jive/7.5/trunk/squeezeplay/src/squeezeplay/share/jive/audio/Rtmp.lua?rev=8430&r1=8429&r2=8430&view=diff
==============================================================================
--- 7.5/trunk/squeezeplay/src/squeezeplay/share/jive/audio/Rtmp.lua (original)
+++ 7.5/trunk/squeezeplay/src/squeezeplay/share/jive/audio/Rtmp.lua Sun Jan 31
06:44:42 2010
@@ -7,9 +7,11 @@
--
-- (c) Triode, 2009, [email protected]
--
--- The implementation here contains just the low level state machine for
processing the rtmp protocol.
--- It relies on the server module to create serialised amf0 objects necessary
to establish the stream and
--- returns amf0 response packets to the server for decoding. It makes the
following assumptions:
+-- The implementation (api v2) here contains both the low level state machine
for processing the rtmp protocol and
+-- serialisation code for generating amf0 request objects. Parsing of amf0
responses is not implemented here and requires
+-- server support.
+--
+-- It makes the following assumptions:
--
-- 1) streams use a streamingId of 1 (it ignores the streamingId inside the
amf0 _result reponse to a createStream message)
-- 2) only implements single byte chunk headers (chunk id < 63)
@@ -18,7 +20,7 @@
-- Due to the way the stream object is created it is necessary to switch
methods in the stream object's meta table
-- so that the Rtmp read and write methods here are used (this is done in
Playback.lua)
-local string, table, math = string, table, math
+local string, table, math, pairs, type = string, table, math, pairs, type
local Stream = require("squeezeplay.stream")
@@ -28,6 +30,8 @@
local log = require("jive.utils.log").logger("audio.decode")
module(...)
+
+local FLASH_VER = "LNX 10,0,22,87"
-- session params (can't be stored in the object as we reuse the streambuf
object)
@@ -41,7 +45,8 @@
function init(self, slimprotoObj)
slimproto = slimprotoObj
- slimproto:capability("Rtmp", 1)
+ -- api version 2 includes abilty to format and serialise amf objects
+ slimproto:capability("Rtmp", 2)
end
@@ -60,6 +65,17 @@
end
+local function packNumber(v, len, le)
+ local t = {}
+ for i = 1, len do
+ t[#t + 1] = string.char(v & 0xFF)
+ v = v >> 8
+ end
+ local str = table.concat(t)
+ return le and str or string.reverse(str)
+end
+
+
local function changeState(newstate)
log:info(state, " -> ", newstate)
state = newstate
@@ -99,9 +115,19 @@
ackWindow, nextAck, receivedBytes = 10240, 10240, 0
state = "reset"
- -- extract the pre built rtmp packets within the header
+ -- extract the pre built rtmp packets or params within the header
for k, v in string.gmatch(header, "(%w+)=([a-zA-Z0-9%/%+]+%=*)&") do
rtmpMessages[k] = mime.unb64("", v)
+ end
+
+ -- create serialised amf packets if params rather than prebuild packets
extracted
+ if rtmpMessages["streamname"] then
+ rtmpMessages["create"] = createStreamPacket()
+ rtmpMessages["play"] =
playPacket(rtmpMessages["streamname"], rtmpMessages["live"],
rtmpMessages["start"])
+ rtmpMessages["connect"] = connectPacket(rtmpMessages["app"],
rtmpMessages["swfurl"] or "", rtmpMessages["tcurl"])
+ if rtmpMessages["subname"] then
+ rtmpMessages["subscribe"] =
subscribePacket(rtmpMessages["subname"])
+ end
end
-- create the handshake token
@@ -270,14 +296,17 @@
end
if state ~= "Playing" then
+ if state ~= "Buffering" then
+ if rtmpMessages["meta"] ~= nil then
+ slimproto:send({ opcode =
"RESP", headers = "" })
+ end
+ changeState("Buffering")
+ end
-- don't start playing live streams
immediately as it causes stutter
if rtmpMessages["subscribe"] and
rtmp["timestamp"] < 4500 then
return 0
else
changeState("Playing")
- if rtmpMessages["meta"] ~= nil then
- slimproto:send({ opcode =
"RESP", headers = "" })
- end
end
end
@@ -596,4 +625,131 @@
end
-
+-- amf packet formatting and serialisation code
+-- this is added for api version 2 so we don't rely on server code to generate
serialsed amf0
+
+-- emulate pack "d" to serialise numbers as doubles
+-- this only implements most signficant 28 bits of the mantissa, rest are 0
+function _todouble(v)
+ local s, e, m
+
+ if v == 0 then
+ return string.char(0, 0, 0, 0, 0, 0, 0, 0)
+ end
+
+ s = v > 0 and 0 or 1
+ v = v > 0 and v or -v
+ e = 0
+
+ local i = v
+ while i >= 2 do
+ i = i / 2
+ e = e + 1
+ end
+
+ m = v / math.pow(2, e - 28)
+ e = e + 1023
+
+ return string.char(
+ (s << 7) |
+ ((e & 0x7f0) >> 4),
+ ((e & 0x00f) << 4) |
+ ((m & 0x0f000000) >> 24),
+ (m & 0x00ff0000) >> 16,
+ (m & 0x0000ff00) >> 8,
+ (m & 0x000000ff),
+ 0, 0, 0)
+end
+
+
+function amfFormatNumber(n)
+ return string.char(0x00) .. _todouble(n)
+end
+
+
+function amfFormatBool(b)
+ return string.char(0x01, b and 0x01 or 0x00)
+end
+
+
+function amfFormatString(s)
+ return string.char(0x02) .. packNumber(string.len(s), 2) .. s
+end
+
+
+function amfFormatNull()
+ return string.char(0x05)
+end
+
+
+function amfFormatObject(o)
+ local res = string.char(0x03)
+ for k, v in pairs(o) do
+ res = res .. packNumber(string.len(k), 2) .. k
+ if type(v) == 'number' then
+ res = res .. amfFormatNumber(v)
+ elseif type(v) == 'string' then
+ res = res .. amfFormatString(v)
+ else
+ res = res .. amfFormatNull()
+ end
+ end
+ return res .. string.char(0x00, 0x00, 0x09)
+end
+
+
+function formatRtmp(chan, type, streamId, body)
+ return
+ string.char(chan & 0x7f) .. -- chan X, format 0
+ string.char(0x00, 0x00, 0x00) .. -- timestamp (not
implemented)
+ packNumber(string.len(body), 3) .. -- length
+ string.char(type & 0xff) .. -- type
+ packNumber(streamId, 4, true) .. -- streamId
+ body -- body
+end
+
+
+function connectPacket(app, swfurl, tcurl)
+ return formatRtmp(0x03, 20, 0,
+ amfFormatString('connect') ..
+ amfFormatNumber(1) ..
+ amfFormatObject({
+ app = app,
+ swfUrl = swfurl,
+ tcUrl = tcurl,
+ audioCodecs = 0x0404,
+ videoCodecs = 0x0000,
+ flashVer = FLASH_VER
+ })
+ )
+end
+
+
+function createStreamPacket()
+ return formatRtmp(0x03, 20, 0,
+ amfFormatString('createStream') ..
+ amfFormatNumber(2) ..
+ amfFormatNull()
+ )
+end
+
+
+function subscribePacket(subscribe)
+ return formatRtmp(0x03, 20, 0,
+ amfFormatString('FCSubscribe') ..
+ amfFormatNumber(0) ..
+ amfFormatNull() ..
+ amfFormatString(subscribe)
+ )
+end
+
+
+function playPacket(streamname, live, start)
+ return formatRtmp(0x08, 20, 1,
+ amfFormatString('play') ..
+ amfFormatNumber(0) ..
+ amfFormatNull() ..
+ amfFormatString(streamname) ..
+ amfFormatNumber(live and -1000 or (start or 0) * 1000)
+ )
+end
_______________________________________________
Jive-checkins mailing list
[email protected]
http://lists.slimdevices.com/mailman/listinfo/jive-checkins